am a785a59c: am 718e0033: Merge "SIP: add SERVER_UNREACHABLE error code." into gingerbread

Merge commit 'a785a59c831256f274627f8f8eb77f9d54508916'

* commit 'a785a59c831256f274627f8f8eb77f9d54508916':
  SIP: add SERVER_UNREACHABLE error code.
diff --git a/Android.mk b/Android.mk
index 6dc39eb..570fbf8 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 \
@@ -129,7 +131,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 \
@@ -159,6 +160,7 @@
 	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 \
 	core/java/com/trustedlogic/trustednfc/android/ILlcpConnectionlessSocket.aidl \
 	core/java/com/trustedlogic/trustednfc/android/ILlcpServiceSocket.aidl \
 	core/java/com/trustedlogic/trustednfc/android/ILlcpSocket.aidl \
@@ -167,6 +169,8 @@
 	core/java/com/trustedlogic/trustednfc/android/INfcTag.aidl \
 	core/java/com/trustedlogic/trustednfc/android/IP2pInitiator.aidl \
 	core/java/com/trustedlogic/trustednfc/android/IP2pTarget.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 \
@@ -356,7 +360,7 @@
 			framework \
 
 framework_docs_LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-framework_docs_LOCAL_DROIDDOC_HTML_DIR := $(LOCAL_PATH)/docs/html $(OUT_DOCS)/gen
+framework_docs_LOCAL_DROIDDOC_HTML_DIR := docs/html
 # The since flag (-since N.xml API_LEVEL) is used to add API Level information
 # to the reference documentation. Must be in order of oldest to newest.
 framework_docs_LOCAL_DROIDDOC_OPTIONS := \
@@ -369,8 +373,9 @@
     -since ./frameworks/base/api/6.xml 6 \
     -since ./frameworks/base/api/7.xml 7 \
     -since ./frameworks/base/api/8.xml 8 \
-    -werror -hide 13 \
-    -overview $(LOCAL_PATH)/core/java/overview.html
+    -since ./frameworks/base/api/current.xml HC \
+		-werror -hide 113 \
+		-overview $(LOCAL_PATH)/core/java/overview.html
 
 framework_docs_LOCAL_ADDITIONAL_JAVA_DIR:= $(call intermediates-dir-for,JAVA_LIBRARIES,framework)
 
@@ -428,7 +433,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)
@@ -463,11 +470,13 @@
 		-nodocs
 
 LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
-LOCAL_DROIDDOC_CUSTOM_ASSET_DIR:=assets-sdk
+
+LOCAL_UNINSTALLABLE_MODULE := true
 
 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))
 
@@ -490,7 +499,8 @@
 		-parsecomments
 
 LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
-LOCAL_DROIDDOC_CUSTOM_ASSET_DIR:=assets-sdk
+
+LOCAL_UNINSTALLABLE_MODULE := true
 
 include $(BUILD_DROIDDOC)
 
@@ -521,14 +531,13 @@
 		-proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \
 		-todo $(OUT_DOCS)/$(LOCAL_MODULE)-docs-todo.html \
 		-sdkvalues $(OUT_DOCS) \
-		-hdf android.whichdoc offline 
+		-hdf android.whichdoc offline
 
 ifeq ($(framework_docs_SDK_PREVIEW),true)
   LOCAL_DROIDDOC_OPTIONS += -hdf sdk.preview true
 endif
 
 LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
-LOCAL_DROIDDOC_CUSTOM_ASSET_DIR:=assets-sdk
 
 include $(BUILD_DROIDDOC)
 
@@ -541,7 +550,6 @@
 $(full_target): $(static_doc_index_redirect)
 $(full_target): $(framework_built)
 
-
 # ==== docs for the web (on the google app engine server) =======================
 include $(CLEAR_VARS)
 
@@ -565,12 +573,11 @@
 		-hdf template.showLanguageMenu true
 
 LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
-LOCAL_DROIDDOC_CUSTOM_ASSET_DIR:=assets-sdk
 
 include $(BUILD_DROIDDOC)
 
 # explicitly specify that online-sdk depends on framework-res and any generated docs
-$(full_target): framework-res-package-target $(ALL_GENERATED_DOCS)
+$(full_target): framework-res-package-target
 
 # ==== docs that have all of the stuff that's @hidden =======================
 include $(CLEAR_VARS)
@@ -587,11 +594,10 @@
 LOCAL_MODULE := hidden
 LOCAL_DROIDDOC_OPTIONS:=\
 		$(framework_docs_LOCAL_DROIDDOC_OPTIONS) \
-        -title "Android SDK - Including hidden APIs."
-#        -hidden
+		-title "Android SDK - Including hidden APIs."
+#		-hidden
 
 LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
-LOCAL_DROIDDOC_CUSTOM_ASSET_DIR:=assets-sdk
 
 include $(BUILD_DROIDDOC)
 
@@ -601,10 +607,14 @@
 ext_dirs := \
 	../../external/nist-sip/java \
 	../../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)
 
@@ -612,6 +622,7 @@
 
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core
+LOCAL_JAVA_RESOURCE_DIRS := $(ext_res_dirs)
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := ext
 
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 5618eaa..e5f1f90 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -67,7 +67,20 @@
 $(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)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libandroid_runtime_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libandroid_runtime.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libandroid_runtime.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libandroid_runtime.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libhwui_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libhwui.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libhwui.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libhwui.so)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/storage/*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content/IClipboard.P)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/android/internal/telephony/ITelephonyRegistry.P)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates)
+$(call add-clean-step, rm -rf out/target/common/docs/api-stubs*)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/api/9.xml b/api/9.xml
index 9ff1287..3e7653c 100644
--- a/api/9.xml
+++ b/api/9.xml
@@ -15845,17 +15845,6 @@
  visibility="public"
 >
 </method>
-<method name="getInstanceCount"
- return="long"
- abstract="false"
- native="false"
- synchronized="false"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getIntent"
  return="android.content.Intent"
  abstract="false"
@@ -58571,16 +58560,6 @@
 <parameter name="bitmap" type="android.graphics.Bitmap">
 </parameter>
 </constructor>
-<constructor name="Canvas"
- type="android.graphics.Canvas"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="gl" type="javax.microedition.khronos.opengles.GL">
-</parameter>
-</constructor>
 <method name="clipPath"
  return="boolean"
  abstract="false"
@@ -59497,17 +59476,6 @@
 <parameter name="paint" type="android.graphics.Paint">
 </parameter>
 </method>
-<method name="freeGlCaches"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getClipBounds"
  return="boolean"
  abstract="false"
@@ -59554,17 +59522,6 @@
  visibility="public"
 >
 </method>
-<method name="getGL"
- return="javax.microedition.khronos.opengles.GL"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getHeight"
  return="int"
  abstract="false"
@@ -59928,21 +59885,6 @@
 <parameter name="matrix" type="android.graphics.Matrix">
 </parameter>
 </method>
-<method name="setViewport"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="width" type="int">
-</parameter>
-<parameter name="height" type="int">
-</parameter>
-</method>
 <method name="skew"
  return="void"
  abstract="false"
@@ -74148,7 +74090,7 @@
  type="float"
  transient="false"
  volatile="false"
- value="0.0010f"
+ value="0.001f"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -147760,17 +147702,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"
@@ -147782,30 +147713,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"
@@ -147832,17 +147739,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"
@@ -148039,17 +147935,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"
@@ -148253,17 +148138,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"
@@ -148290,124 +148164,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"
@@ -164674,7 +164430,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"
@@ -164685,7 +164441,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"
@@ -195596,6 +195352,39 @@
 <parameter name="defStyle" type="int">
 </parameter>
 </constructor>
+<field name="CHOICE_MODE_MULTIPLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CHOICE_MODE_NONE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CHOICE_MODE_SINGLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <method name="afterTextChanged"
  return="void"
  abstract="false"
@@ -204052,39 +203841,6 @@
 <parameter name="y" type="int">
 </parameter>
 </method>
-<field name="CHOICE_MODE_MULTIPLE"
- type="int"
- transient="false"
- volatile="false"
- value="2"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="CHOICE_MODE_NONE"
- type="int"
- transient="false"
- volatile="false"
- value="0"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="CHOICE_MODE_SINGLE"
- type="int"
- transient="false"
- volatile="false"
- value="1"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 </class>
 <class name="ListView.FixedViewInfo"
  extends="java.lang.Object"
diff --git a/api/current.xml b/api/current.xml
index c4c19c9..08e8670 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -1471,6 +1471,28 @@
  visibility="public"
 >
 </field>
+<field name="animator_fade_in"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="17432609"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="animator_fade_out"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="17432610"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="anticipate_interpolator"
  type="int"
  transient="false"
@@ -2077,6 +2099,171 @@
  visibility="public"
 >
 </field>
+<field name="actionBarSize"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843567"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionBarStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843535"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionBarTabBarStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843576"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionBarTabStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843575"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionBarTabTextStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843577"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ 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="actionLayout"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843584"
+ 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="actionModeCloseButtonStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843580"
+ 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="actionOverflowButtonStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843578"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionViewClass"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843585"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="activatedBackgroundIndicator"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843586"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="activityCloseEnterAnimation"
  type="int"
  transient="false"
@@ -2121,6 +2308,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"
@@ -2154,6 +2352,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"
@@ -2242,6 +2451,28 @@
  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="animateLayoutChanges"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843574"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="animateOnClick"
  type="int"
  transient="false"
@@ -2330,6 +2561,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"
@@ -2561,6 +2803,28 @@
  visibility="public"
 >
 </field>
+<field name="breadCrumbShortTitle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843593"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="breadCrumbTitle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843592"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="bufferType"
  type="int"
  transient="false"
@@ -2990,6 +3254,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"
@@ -3133,6 +3408,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"
@@ -3397,6 +3683,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"
@@ -3595,6 +3892,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 +4464,105 @@
  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="fragmentCloseEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843561"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentCloseExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843562"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentNextEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843563"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentNextExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843564"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentOpenEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843559"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentOpenExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843560"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentPrevEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843565"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentPrevExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843566"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="freezesText"
  type="int"
  transient="false"
@@ -4167,6 +4574,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 +4607,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 +4904,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"
@@ -4596,6 +5036,17 @@
  visibility="public"
 >
 </field>
+<field name="iconifiedByDefault"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843583"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="id"
  type="int"
  transient="false"
@@ -4706,6 +5157,50 @@
  visibility="public"
 >
 </field>
+<field name="imeSubtypeExtraValue"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843570"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="imeSubtypeLocale"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843568"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="imeSubtypeMode"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843569"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="immersive"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843456"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="inAnimation"
  type="int"
  transient="false"
@@ -5047,6 +5542,17 @@
  visibility="public"
 >
 </field>
+<field name="itemPadding"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843579"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="itemTextAppearance"
  type="int"
  transient="false"
@@ -6312,6 +6818,17 @@
  visibility="public"
 >
 </field>
+<field name="listChoiceBackgroundIndicator"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843572"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="listChoiceIndicatorMultiple"
  type="int"
  transient="false"
@@ -6345,6 +6862,28 @@
  visibility="public"
 >
 </field>
+<field name="listDividerAlertDialog"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843594"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="listPopupWindowStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843588"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="listPreferredItemHeight"
  type="int"
  transient="false"
@@ -6400,6 +6939,17 @@
  visibility="public"
 >
 </field>
+<field name="logo"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843454"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="longClickable"
  type="int"
  transient="false"
@@ -6411,6 +6961,17 @@
  visibility="public"
 >
 </field>
+<field name="loopViews"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843596"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="manageSpaceActivity"
  type="int"
  transient="false"
@@ -6565,6 +7126,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"
@@ -6697,6 +7269,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"
@@ -6862,6 +7445,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"
@@ -6895,39 +7489,6 @@
  visibility="public"
 >
 </field>
-<field name="overscrollFooter"
- type="int"
- transient="false"
- volatile="false"
- value="16843459"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="overscrollHeader"
- type="int"
- transient="false"
- volatile="false"
- value="16843458"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="overscrollMode"
- type="int"
- transient="false"
- volatile="false"
- value="16843457"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="padding"
  type="int"
  transient="false"
@@ -7214,6 +7775,17 @@
  visibility="public"
 >
 </field>
+<field name="popupMenuStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843589"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="popupWindowStyle"
  type="int"
  transient="false"
@@ -7302,6 +7874,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"
@@ -7456,6 +8039,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"
@@ -8226,6 +8820,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"
@@ -8336,6 +8941,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"
@@ -8413,6 +9029,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"
@@ -8457,6 +9084,17 @@
  visibility="public"
 >
 </field>
+<field name="spinnerMode"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843573"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="spinnerStyle"
  type="int"
  transient="false"
@@ -8468,6 +9106,17 @@
  visibility="public"
 >
 </field>
+<field name="splitMotionEvents"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843571"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="src"
  type="int"
  transient="false"
@@ -8556,6 +9205,17 @@
  visibility="public"
 >
 </field>
+<field name="state_activated"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843587"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="state_active"
  type="int"
  transient="false"
@@ -8765,6 +9425,28 @@
  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="subtitleTextStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843582"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="suggestActionMsg"
  type="int"
  transient="false"
@@ -9139,6 +9821,17 @@
  visibility="public"
 >
 </field>
+<field name="textAppearanceLargePopupMenu"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843590"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="textAppearanceMedium"
  type="int"
  transient="false"
@@ -9205,6 +9898,17 @@
  visibility="public"
 >
 </field>
+<field name="textAppearanceSmallPopupMenu"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843591"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="textCheckMark"
  type="int"
  transient="false"
@@ -9238,6 +9942,17 @@
  visibility="public"
 >
 </field>
+<field name="textColorAlertDialogListItem"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843595"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="textColorHighlight"
  type="int"
  transient="false"
@@ -9645,6 +10360,28 @@
  visibility="public"
 >
 </field>
+<field name="titleTextStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843581"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ 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"
@@ -9667,6 +10404,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"
@@ -9843,6 +10591,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"
@@ -9876,6 +10635,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"
@@ -10184,6 +10976,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="windowActionBarOverlay"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843558"
+ 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"
@@ -10393,6 +11218,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"
@@ -14042,6 +14889,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"
@@ -14427,6 +15285,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"
@@ -14967,6 +15836,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"
@@ -15077,6 +15957,28 @@
  visibility="public"
 >
 </field>
+<field name="simple_list_item_activated_1"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="17367075"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="simple_list_item_activated_2"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="17367076"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="simple_list_item_checked"
  type="int"
  transient="false"
@@ -15110,6 +16012,17 @@
  visibility="public"
 >
 </field>
+<field name="simple_selectable_list_item"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="17367074"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="simple_spinner_dropdown_item"
  type="int"
  transient="false"
@@ -15155,6 +16068,23 @@
 >
 </field>
 </class>
+<class name="R.menu"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="R.menu"
+ type="android.R.menu"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+</class>
 <class name="R.plurals"
  extends="java.lang.Object"
  abstract="false"
@@ -15535,6 +16465,17 @@
  visibility="public"
 >
 </field>
+<field name="selectTextMode"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="17039393"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="unknownName"
  type="int"
  transient="false"
@@ -15959,6 +16900,28 @@
  visibility="public"
 >
 </field>
+<field name="TextAppearance_Widget_PopupMenu_Large"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973984"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TextAppearance_Widget_PopupMenu_Small"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973985"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="TextAppearance_Widget_TabWidget"
  type="int"
  transient="false"
@@ -16069,6 +17032,50 @@
  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_Holo"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973974"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="Theme_Holo_NoActionBar"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973976"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="Theme_Holo_NoActionBar_Fullscreen"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973977"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Theme_InputMethod"
  type="int"
  transient="false"
@@ -16091,6 +17098,39 @@
  visibility="public"
 >
 </field>
+<field name="Theme_Light_Holo"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973975"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="Theme_Light_Holo_NoActionBar"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973978"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="Theme_Light_Holo_NoActionBar_Fullscreen"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973979"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Theme_Light_NoTitleBar"
  type="int"
  transient="false"
@@ -16168,6 +17208,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"
@@ -16256,6 +17307,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"
@@ -16278,6 +17340,39 @@
  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_ActionButton_CloseMode"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973983"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="Widget_ActionButton_Overflow"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973982"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Widget_AutoCompleteTextView"
  type="int"
  transient="false"
@@ -16421,6 +17516,17 @@
  visibility="public"
 >
 </field>
+<field name="Widget_FragmentBreadCrumbs"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973986"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Widget_Gallery"
  type="int"
  transient="false"
@@ -16476,6 +17582,17 @@
  visibility="public"
 >
 </field>
+<field name="Widget_ListPopupWindow"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973980"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Widget_ListView"
  type="int"
  transient="false"
@@ -16520,6 +17637,17 @@
  visibility="public"
 >
 </field>
+<field name="Widget_PopupMenu"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973981"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Widget_PopupWindow"
  type="int"
  transient="false"
@@ -16652,6 +17780,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"
@@ -19032,8 +20171,2592 @@
 </constructor>
 </class>
 </package>
+<package name="android.animation"
+>
+<class name="Animator"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.lang.Cloneable">
+</implements>
+<constructor name="Animator"
+ type="android.animation.Animator"
+ 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.Animator.AnimatorListener">
+</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="clone"
+ return="android.animation.Animator"
+ 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="getDuration"
+ return="long"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getListeners"
+ return="java.util.ArrayList&lt;android.animation.Animator.AnimatorListener&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getStartDelay"
+ return="long"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isRunning"
+ return="boolean"
+ abstract="true"
+ 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.Animator.AnimatorListener">
+</parameter>
+</method>
+<method name="setDuration"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+</method>
+<method name="setInterpolator"
+ return="void"
+ abstract="true"
+ 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="setStartDelay"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="startDelay" type="long">
+</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>
+<method name="setupEndValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setupStartValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</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="Animator.AnimatorListener"
+ 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.Animator">
+</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.Animator">
+</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.Animator">
+</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.Animator">
+</parameter>
+</method>
+</interface>
+<class name="AnimatorInflater"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="AnimatorInflater"
+ type="android.animation.AnimatorInflater"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="loadAnimator"
+ return="android.animation.Animator"
+ 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>
+</class>
+<class name="AnimatorListenerAdapter"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.animation.Animator.AnimatorListener">
+</implements>
+<constructor name="AnimatorListenerAdapter"
+ type="android.animation.AnimatorListenerAdapter"
+ 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.Animator">
+</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.Animator">
+</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.Animator">
+</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.Animator">
+</parameter>
+</method>
+</class>
+<class name="AnimatorSet"
+ extends="android.animation.Animator"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="AnimatorSet"
+ type="android.animation.AnimatorSet"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getChildAnimations"
+ return="java.util.ArrayList&lt;android.animation.Animator&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDuration"
+ return="long"
+ 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="isRunning"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="play"
+ return="android.animation.AnimatorSet.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="anim" type="android.animation.Animator">
+</parameter>
+</method>
+<method name="playSequentially"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="items" type="android.animation.Animator...">
+</parameter>
+</method>
+<method name="playTogether"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="items" type="android.animation.Animator...">
+</parameter>
+</method>
+<method name="setDuration"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" 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="interpolator" type="android.view.animation.Interpolator">
+</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>
+</class>
+<class name="AnimatorSet.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.Animator">
+</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.Animator">
+</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.Animator">
+</parameter>
+</method>
+</class>
+<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"
+>
+<implements name="java.lang.Cloneable">
+</implements>
+<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="java.lang.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="java.lang.Integer">
+</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="java.lang.Double">
+</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="clone"
+ return="android.animation.Keyframe"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<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="LayoutTransition"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="LayoutTransition"
+ type="android.animation.LayoutTransition"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="addChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
+<method name="addTransitionListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.animation.LayoutTransition.TransitionListener">
+</parameter>
+</method>
+<method name="getAnimator"
+ return="android.animation.Animator"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transitionType" type="int">
+</parameter>
+</method>
+<method name="getDuration"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transitionType" type="int">
+</parameter>
+</method>
+<method name="getInterpolator"
+ return="android.view.animation.Interpolator"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transitionType" type="int">
+</parameter>
+</method>
+<method name="getStagger"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transitionType" type="int">
+</parameter>
+</method>
+<method name="getStartDelay"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transitionType" type="int">
+</parameter>
+</method>
+<method name="getTransitionListeners"
+ return="java.util.List&lt;android.animation.LayoutTransition.TransitionListener&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hideChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
+<method name="removeChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
+<method name="removeTransitionListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.animation.LayoutTransition.TransitionListener">
+</parameter>
+</method>
+<method name="setAnimator"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transitionType" type="int">
+</parameter>
+<parameter name="animator" type="android.animation.Animator">
+</parameter>
+</method>
+<method name="setDuration"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+</method>
+<method name="setDuration"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transitionType" type="int">
+</parameter>
+<parameter name="duration" 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="transitionType" type="int">
+</parameter>
+<parameter name="interpolator" type="android.view.animation.Interpolator">
+</parameter>
+</method>
+<method name="setStagger"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transitionType" type="int">
+</parameter>
+<parameter name="duration" type="long">
+</parameter>
+</method>
+<method name="setStartDelay"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transitionType" type="int">
+</parameter>
+<parameter name="delay" type="long">
+</parameter>
+</method>
+<method name="showChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
+<field name="APPEARING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CHANGE_APPEARING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CHANGE_DISAPPEARING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DISAPPEARING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<interface name="LayoutTransition.TransitionListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="endTransition"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transition" type="android.animation.LayoutTransition">
+</parameter>
+<parameter name="container" type="android.view.ViewGroup">
+</parameter>
+<parameter name="view" type="android.view.View">
+</parameter>
+<parameter name="transitionType" type="int">
+</parameter>
+</method>
+<method name="startTransition"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transition" type="android.animation.LayoutTransition">
+</parameter>
+<parameter name="container" type="android.view.ViewGroup">
+</parameter>
+<parameter name="view" type="android.view.View">
+</parameter>
+<parameter name="transitionType" type="int">
+</parameter>
+</method>
+</interface>
+<class name="ObjectAnimator"
+ extends="android.animation.ValueAnimator"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ObjectAnimator"
+ type="android.animation.ObjectAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="ObjectAnimator"
+ type="android.animation.ObjectAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="values" type="T...">
+</parameter>
+</constructor>
+<constructor name="ObjectAnimator"
+ type="android.animation.ObjectAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="values" type="android.animation.PropertyValuesHolder...">
+</parameter>
+</constructor>
+<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="getTarget"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</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>
+</class>
+<class name="PropertyValuesHolder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.lang.Cloneable">
+</implements>
+<constructor name="PropertyValuesHolder"
+ type="android.animation.PropertyValuesHolder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="values" type="T...">
+</parameter>
+</constructor>
+<constructor name="PropertyValuesHolder"
+ type="android.animation.PropertyValuesHolder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="values" type="T...">
+</parameter>
+</constructor>
+<method name="clone"
+ return="android.animation.PropertyValuesHolder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<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="setEvaluator"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="evaluator" type="android.animation.TypeEvaluator">
+</parameter>
+</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="setValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="values" type="T...">
+</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>
+<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>
+<class name="ValueAnimator"
+ extends="android.animation.Animator"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ValueAnimator"
+ type="android.animation.ValueAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="ValueAnimator"
+ type="android.animation.ValueAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+<parameter name="values" type="T...">
+</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.ValueAnimator.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="getAnimatedValue"
+ return="java.lang.Object"
+ 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="getCurrentPlayTime"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDuration"
+ 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="getInterpolator"
+ return="android.view.animation.Interpolator"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ 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="getValues"
+ return="android.animation.PropertyValuesHolder[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isRunning"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="removeAllUpdateListeners"
+ return="void"
+ 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.ValueAnimator.AnimatorUpdateListener">
+</parameter>
+</method>
+<method name="reverse"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</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="setDuration"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" 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="setValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="values" type="android.animation.PropertyValuesHolder...">
+</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="T...">
+</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="ValueAnimator.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.ValueAnimator">
+</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="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>
+<parameter name="position" type="int">
+</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="getHeight"
+ 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="getSelectedTab"
+ return="android.app.ActionBar.Tab"
+ 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="hide"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isShowing"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</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"
+>
+</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="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>
+<method name="show"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</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="getCustomView"
+ return="android.view.View"
+ 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="getTag"
+ return="java.lang.Object"
+ 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="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="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="setTabListener"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.app.ActionBar.TabListener">
+</parameter>
+</method>
+<method name="setTag"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="obj" type="java.lang.Object">
+</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>
+<interface name="ActionBar.TabListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onTabSelected"
+ 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="ft" type="android.app.FragmentTransaction">
+</parameter>
+</method>
+<method name="onTabUnselected"
+ 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="ft" type="android.app.FragmentTransaction">
+</parameter>
+</method>
+</interface>
 <class name="Activity"
  extends="android.view.ContextThemeWrapper"
  abstract="false"
@@ -19046,7 +22769,7 @@
 </implements>
 <implements name="android.view.KeyEvent.Callback">
 </implements>
-<implements name="android.view.LayoutInflater.Factory">
+<implements name="android.view.LayoutInflater.Factory2">
 </implements>
 <implements name="android.view.View.OnCreateContextMenuListener">
 </implements>
@@ -19179,6 +22902,49 @@
 <parameter name="ev" type="android.view.MotionEvent">
 </parameter>
 </method>
+<method name="dump"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fd" type="java.io.FileDescriptor">
+</parameter>
+<parameter name="writer" type="java.io.PrintWriter">
+</parameter>
+<parameter name="args" type="java.lang.String[]">
+</parameter>
+</method>
+<method name="findFragmentById"
+ return="android.app.Fragment"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="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="deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+</method>
 <method name="findViewById"
  return="android.view.View"
  abstract="false"
@@ -19244,6 +23010,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"
@@ -19310,12 +23087,12 @@
  visibility="public"
 >
 </method>
-<method name="getInstanceCount"
- return="long"
+<method name="getFragmentManager"
+ return="android.app.FragmentManager"
  abstract="false"
  native="false"
  synchronized="false"
- static="true"
+ static="false"
  final="false"
  deprecated="not deprecated"
  visibility="public"
@@ -19354,6 +23131,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"
@@ -19488,6 +23276,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"
@@ -19528,7 +23338,7 @@
  synchronized="false"
  static="false"
  final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="uri" type="android.net.Uri">
@@ -19572,6 +23382,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"
@@ -19801,6 +23624,25 @@
 <parameter name="attrs" type="android.util.AttributeSet">
 </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="parent" type="android.view.View">
+</parameter>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+</method>
 <method name="onDestroy"
  return="void"
  abstract="false"
@@ -20158,6 +24000,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"
@@ -20271,6 +24126,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="deprecated"
+ visibility="public"
+>
+</method>
 <method name="openOptionsMenu"
  return="void"
  abstract="false"
@@ -20297,6 +24163,47 @@
 <parameter name="exitAnim" type="int">
 </parameter>
 </method>
+<method name="popBackStack"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+</method>
+<method name="popBackStack"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="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="deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
 <method name="registerForContextMenu"
  return="void"
  abstract="false"
@@ -20688,6 +24595,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"
@@ -20720,6 +24640,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"
@@ -20794,7 +24731,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="c" type="android.database.Cursor">
@@ -20839,7 +24776,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="c" type="android.database.Cursor">
@@ -20952,6 +24889,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"
@@ -22822,6 +26770,17 @@
  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="setAdapter"
  return="android.app.AlertDialog.Builder"
  abstract="false"
@@ -23654,6 +27613,17 @@
 <parameter name="id" type="int">
 </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="getContext"
  return="android.content.Context"
  abstract="false"
@@ -23731,6 +27701,17 @@
  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="isShowing"
  return="boolean"
  abstract="false"
@@ -24092,6 +28073,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"
@@ -24500,6 +28494,251 @@
 </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>
+<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="getShowsDialog"
+ return="boolean"
+ 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="setShowsDialog"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="showsDialog" type="boolean">
+</parameter>
+</method>
+<method name="setStyle"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="style" type="int">
+</parameter>
+<parameter name="theme" type="int">
+</parameter>
+</method>
+<method name="show"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+<parameter name="activity" type="android.app.Activity">
+</parameter>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+</method>
+<method name="show"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="manager" type="android.app.FragmentManager">
+</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="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="DownloadManager"
  extends="java.lang.Object"
  abstract="false"
@@ -25320,6 +29559,1419 @@
 </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="dump"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="prefix" type="java.lang.String">
+</parameter>
+<parameter name="fd" type="java.io.FileDescriptor">
+</parameter>
+<parameter name="writer" type="java.io.PrintWriter">
+</parameter>
+<parameter name="args" type="java.lang.String[]">
+</parameter>
+</method>
+<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="getArguments"
+ return="android.os.Bundle"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getFragmentManager"
+ return="android.app.FragmentManager"
+ 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="getTargetFragment"
+ return="android.app.Fragment"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTargetRequestCode"
+ return="int"
+ 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>
+</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>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+</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="isInLayout"
+ 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="onCreateAnimator"
+ return="android.animation.Animator"
+ 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="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="setArguments"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="args" type="android.os.Bundle">
+</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="setTargetFragment"
+ 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="requestCode" type="int">
+</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>
+<class name="Fragment.InstantiationException"
+ extends="android.util.AndroidRuntimeException"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Fragment.InstantiationException"
+ type="android.app.Fragment.InstantiationException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="msg" type="java.lang.String">
+</parameter>
+<parameter name="cause" type="java.lang.Exception">
+</parameter>
+</constructor>
+</class>
+<class name="FragmentBreadCrumbs"
+ extends="android.view.ViewGroup"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.app.FragmentManager.OnBackStackChangedListener">
+</implements>
+<constructor name="FragmentBreadCrumbs"
+ type="android.app.FragmentBreadCrumbs"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="FragmentBreadCrumbs"
+ type="android.app.FragmentBreadCrumbs"
+ 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="FragmentBreadCrumbs"
+ type="android.app.FragmentBreadCrumbs"
+ 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="onBackStackChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onLayout"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="changed" type="boolean">
+</parameter>
+<parameter name="l" type="int">
+</parameter>
+<parameter name="t" type="int">
+</parameter>
+<parameter name="r" type="int">
+</parameter>
+<parameter name="b" type="int">
+</parameter>
+</method>
+<method name="setActivity"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="a" type="android.app.Activity">
+</parameter>
+</method>
+<method name="setTitle"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="title" type="java.lang.CharSequence">
+</parameter>
+<parameter name="shortTitle" type="java.lang.CharSequence">
+</parameter>
+</method>
+</class>
+<interface name="FragmentManager"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="addOnBackStackChangedListener"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.app.FragmentManager.OnBackStackChangedListener">
+</parameter>
+</method>
+<method name="countBackStackEntries"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="dump"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="prefix" type="java.lang.String">
+</parameter>
+<parameter name="fd" type="java.io.FileDescriptor">
+</parameter>
+<parameter name="writer" type="java.io.PrintWriter">
+</parameter>
+<parameter name="args" type="java.lang.String[]">
+</parameter>
+</method>
+<method name="findFragmentById"
+ return="android.app.Fragment"
+ abstract="true"
+ 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="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+</method>
+<method name="getBackStackEntry"
+ return="android.app.FragmentManager.BackStackEntry"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="index" type="int">
+</parameter>
+</method>
+<method name="getFragment"
+ return="android.app.Fragment"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="bundle" type="android.os.Bundle">
+</parameter>
+<parameter name="key" type="java.lang.String">
+</parameter>
+</method>
+<method name="openTransaction"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="popBackStack"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="popBackStack"
+ return="boolean"
+ 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="flags" type="int">
+</parameter>
+</method>
+<method name="popBackStack"
+ return="boolean"
+ abstract="true"
+ 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="putFragment"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="bundle" type="android.os.Bundle">
+</parameter>
+<parameter name="key" type="java.lang.String">
+</parameter>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+</method>
+<method name="removeOnBackStackChangedListener"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.app.FragmentManager.OnBackStackChangedListener">
+</parameter>
+</method>
+<field name="POP_BACK_STACK_INCLUSIVE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
+<interface name="FragmentManager.BackStackEntry"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getBreadCrumbShortTitle"
+ return="java.lang.CharSequence"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getBreadCrumbTitle"
+ return="java.lang.CharSequence"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getId"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</interface>
+<interface name="FragmentManager.OnBackStackChangedListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onBackStackChanged"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</interface>
+<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="isEmpty"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="remove"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+</method>
+<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="setBreadCrumbShortTitle"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="res" type="int">
+</parameter>
+</method>
+<method name="setBreadCrumbShortTitle"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="setBreadCrumbTitle"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="res" type="int">
+</parameter>
+</method>
+<method name="setBreadCrumbTitle"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</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_ENTER_MASK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4096"
+ 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_FRAGMENT_CLOSE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8194"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_FRAGMENT_NEXT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4099"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_FRAGMENT_OPEN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4097"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_FRAGMENT_PREV"
+ 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_UNSET"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
 <class name="Instrumentation"
  extends="java.lang.Object"
  abstract="false"
@@ -26769,6 +32421,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"
@@ -27252,6 +33271,17 @@
  visibility="public"
 >
 </field>
+<field name="FLAG_HIGH_PRIORITY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="128"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FLAG_INSISTENT"
  type="int"
  transient="false"
@@ -27458,6 +33488,27 @@
  visibility="public"
 >
 </field>
+<field name="tickerIcons"
+ type="android.graphics.Bitmap[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="tickerSubtitle"
+ type="java.lang.CharSequence"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="tickerText"
  type="java.lang.CharSequence"
  transient="false"
@@ -27468,6 +33519,16 @@
  visibility="public"
 >
 </field>
+<field name="tickerTitle"
+ type="java.lang.CharSequence"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="vibrate"
  type="long[]"
  transient="false"
@@ -28442,6 +34503,17 @@
  visibility="public"
 >
 </field>
+<field name="FLAG_QUERY_REFINEMENT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="INTENT_ACTION_GLOBAL_SEARCH"
  type="java.lang.String"
  transient="false"
@@ -28541,6 +34613,17 @@
  visibility="public"
 >
 </field>
+<field name="SUGGEST_COLUMN_FLAGS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;suggest_flags&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="SUGGEST_COLUMN_FORMAT"
  type="java.lang.String"
  transient="false"
@@ -30482,6 +36565,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"
@@ -30771,6 +36865,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"
@@ -30797,6 +36902,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"
@@ -30823,6 +36941,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"
@@ -30899,6 +37095,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"
@@ -30929,6 +37142,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"
@@ -30944,6 +37172,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"
@@ -31038,6 +37356,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"
@@ -32021,6 +38350,66 @@
 <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="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="viewId" type="int">
+</parameter>
+</method>
+<method name="partiallyUpdateAppWidget"
+ 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>
+</method>
+<method name="partiallyUpdateAppWidget"
+ 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>
+</method>
 <method name="updateAppWidget"
  return="void"
  abstract="false"
@@ -32437,6 +38826,111 @@
 </package>
 <package name="android.bluetooth"
 >
+<class name="BluetoothA2dp"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.bluetooth.BluetoothProfile">
+</implements>
+<method name="getConnectedDevices"
+ return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getConnectionState"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="device" type="android.bluetooth.BluetoothDevice">
+</parameter>
+</method>
+<method name="getDevicesMatchingConnectionStates"
+ return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="states" type="int[]">
+</parameter>
+</method>
+<method name="isA2dpPlaying"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="device" type="android.bluetooth.BluetoothDevice">
+</parameter>
+</method>
+<field name="ACTION_CONNECTION_STATE_CHANGED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_PLAYING_STATE_CHANGED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_NOT_PLAYING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="11"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_PLAYING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="10"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="BluetoothAdapter"
  extends="java.lang.Object"
  abstract="false"
@@ -32469,6 +38963,21 @@
 <parameter name="address" type="java.lang.String">
 </parameter>
 </method>
+<method name="closeProfileProxy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="profile" type="int">
+</parameter>
+<parameter name="proxy" type="android.bluetooth.BluetoothProfile">
+</parameter>
+</method>
 <method name="disable"
  return="boolean"
  abstract="false"
@@ -32535,6 +39044,23 @@
  visibility="public"
 >
 </method>
+<method name="getProfileProxy"
+ return="boolean"
+ 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="listener" type="android.bluetooth.BluetoothProfile.ServiceListener">
+</parameter>
+<parameter name="profile" type="int">
+</parameter>
+</method>
 <method name="getRemoteDevice"
  return="android.bluetooth.BluetoothDevice"
  abstract="false"
@@ -32633,6 +39159,17 @@
  visibility="public"
 >
 </method>
+<field name="ACTION_CONNECTION_STATE_CHANGED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="ACTION_DISCOVERY_FINISHED"
  type="java.lang.String"
  transient="false"
@@ -32721,6 +39258,17 @@
  visibility="public"
 >
 </field>
+<field name="EXTRA_CONNECTION_STATE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.bluetooth.adapter.extra.CONNECTION_STATE&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="EXTRA_DISCOVERABLE_DURATION"
  type="java.lang.String"
  transient="false"
@@ -32743,6 +39291,17 @@
  visibility="public"
 >
 </field>
+<field name="EXTRA_PREVIOUS_CONNECTION_STATE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="EXTRA_PREVIOUS_SCAN_MODE"
  type="java.lang.String"
  transient="false"
@@ -32820,6 +39379,50 @@
  visibility="public"
 >
 </field>
+<field name="STATE_CONNECTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_CONNECTING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_DISCONNECTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_DISCONNECTING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="STATE_OFF"
  type="int"
  transient="false"
@@ -34061,6 +40664,306 @@
 >
 </field>
 </class>
+<class name="BluetoothHeadset"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.bluetooth.BluetoothProfile">
+</implements>
+<method name="getConnectedDevices"
+ return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getConnectionState"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="device" type="android.bluetooth.BluetoothDevice">
+</parameter>
+</method>
+<method name="getDevicesMatchingConnectionStates"
+ return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="states" type="int[]">
+</parameter>
+</method>
+<method name="isAudioConnected"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="device" type="android.bluetooth.BluetoothDevice">
+</parameter>
+</method>
+<method name="startVoiceRecognition"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="device" type="android.bluetooth.BluetoothDevice">
+</parameter>
+</method>
+<method name="stopVoiceRecognition"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="device" type="android.bluetooth.BluetoothDevice">
+</parameter>
+</method>
+<field name="ACTION_AUDIO_STATE_CHANGED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_CONNECTION_STATE_CHANGED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_AUDIO_CONNECTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="10"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_AUDIO_DISCONNECTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="11"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<interface name="BluetoothProfile"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getConnectedDevices"
+ return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getConnectionState"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="device" type="android.bluetooth.BluetoothDevice">
+</parameter>
+</method>
+<method name="getDevicesMatchingConnectionStates"
+ return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="states" type="int[]">
+</parameter>
+</method>
+<field name="A2DP"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_PREVIOUS_STATE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.bluetooth.profile.extra.PREVIOUS_STATE&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_STATE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.bluetooth.profile.extra.STATE&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="HEADSET"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_CONNECTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_CONNECTING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_DISCONNECTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATE_DISCONNECTING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
+<interface name="BluetoothProfile.ServiceListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onServiceConnected"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="profile" type="int">
+</parameter>
+<parameter name="proxy" type="android.bluetooth.BluetoothProfile">
+</parameter>
+</method>
+<method name="onServiceDisconnected"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="profile" type="int">
+</parameter>
+</method>
+</interface>
 <class name="BluetoothServerSocket"
  extends="java.lang.Object"
  abstract="false"
@@ -34632,6 +41535,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"
@@ -34862,6 +41830,571 @@
 </parameter>
 </method>
 </class>
+<class name="ClipData"
+ extends="android.content.ClipDescription"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ClipData"
+ type="android.content.ClipData"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="label" type="java.lang.CharSequence">
+</parameter>
+<parameter name="mimeTypes" type="java.lang.String[]">
+</parameter>
+<parameter name="icon" type="android.graphics.Bitmap">
+</parameter>
+<parameter name="item" type="android.content.ClipData.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.ClipData.Item">
+</parameter>
+</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.ClipData.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="newIntent"
+ return="android.content.ClipData"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ 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="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="newPlainText"
+ return="android.content.ClipData"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ 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="text" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="newRawUri"
+ return="android.content.ClipData"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ 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="uri" type="android.net.Uri">
+</parameter>
+</method>
+<method name="newUri"
+ return="android.content.ClipData"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="resolver" type="android.content.ContentResolver">
+</parameter>
+<parameter name="label" type="java.lang.CharSequence">
+</parameter>
+<parameter name="icon" type="android.graphics.Bitmap">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</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="ClipData.Item"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ClipData.Item"
+ type="android.content.ClipData.Item"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</parameter>
+</constructor>
+<constructor name="ClipData.Item"
+ type="android.content.ClipData.Item"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</constructor>
+<constructor name="ClipData.Item"
+ type="android.content.ClipData.Item"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</constructor>
+<constructor name="ClipData.Item"
+ type="android.content.ClipData.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="coerceToText"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="getIntent"
+ return="android.content.Intent"
+ abstract="false"
+ 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>
+<class name="ClipDescription"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="ClipDescription"
+ type="android.content.ClipDescription"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="label" type="java.lang.CharSequence">
+</parameter>
+<parameter name="mimeTypes" type="java.lang.String[]">
+</parameter>
+</constructor>
+<constructor name="ClipDescription"
+ type="android.content.ClipDescription"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="o" type="android.content.ClipDescription">
+</parameter>
+</constructor>
+<method name="compareMimeTypes"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="concreteType" type="java.lang.String">
+</parameter>
+<parameter name="desiredType" type="java.lang.String">
+</parameter>
+</method>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="filterMimeTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+</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="getMimeType"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="index" type="int">
+</parameter>
+</method>
+<method name="getMimeTypeCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hasMimeType"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+</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>
+<field name="MIMETYPE_TEXT_INTENT"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;text/vnd.android.intent&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MIMETYPE_TEXT_PLAIN"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;text/plain&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MIMETYPE_TEXT_URILIST"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;text/uri-list&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</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.ClipData"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPrimaryClipDescription"
+ return="android.content.ClipDescription"
+ 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="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.ClipData">
+</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>
 <interface name="ComponentCallbacks"
  abstract="true"
  static="false"
@@ -35235,6 +42768,21 @@
  visibility="public"
 >
 </method>
+<method name="getStreamTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+</method>
 <method name="getType"
  return="java.lang.String"
  abstract="true"
@@ -35371,6 +42919,48 @@
 <exception name="FileNotFoundException" type="java.io.FileNotFoundException">
 </exception>
 </method>
+<method name="openPipeHelper"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<parameter name="args" type="T">
+</parameter>
+<parameter name="func" type="android.content.ContentProvider.PipeDataWriter&lt;T&gt;">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
+<method name="openTypedAssetFile"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
 <method name="query"
  return="android.database.Cursor"
  abstract="true"
@@ -35431,6 +43021,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"
@@ -35451,6 +43052,35 @@
 </parameter>
 </method>
 </class>
+<interface name="ContentProvider.PipeDataWriter"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="writeDataToPipe"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="output" type="android.os.ParcelFileDescriptor">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<parameter name="args" type="T">
+</parameter>
+</method>
+</interface>
 <class name="ContentProviderClient"
  extends="java.lang.Object"
  abstract="false"
@@ -35523,6 +43153,23 @@
  visibility="public"
 >
 </method>
+<method name="getStreamTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
 <method name="getType"
  return="java.lang.String"
  abstract="false"
@@ -35593,6 +43240,27 @@
 <exception name="RemoteException" type="android.os.RemoteException">
 </exception>
 </method>
+<method name="openTypedAssetFileDescriptor"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
 <method name="query"
  return="android.database.Cursor"
  abstract="false"
@@ -36363,6 +44031,21 @@
 <parameter name="authority" type="java.lang.String">
 </parameter>
 </method>
+<method name="getStreamTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+</method>
 <method name="getSyncAdapterTypes"
  return="android.content.SyncAdapterType[]"
  abstract="false"
@@ -36560,6 +44243,25 @@
 <exception name="FileNotFoundException" type="java.io.FileNotFoundException">
 </exception>
 </method>
+<method name="openTypedAssetFileDescriptor"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
 <method name="query"
  return="android.database.Cursor"
  abstract="false"
@@ -37225,6 +44927,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"
@@ -38275,6 +45988,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"
@@ -39713,6 +47445,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"
@@ -40039,6 +47790,240 @@
 </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="registerContentObserver"
+ 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>
+<parameter name="observer" type="android.database.ContentObserver">
+</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"
@@ -42618,6 +50603,17 @@
  visibility="public"
 >
 </field>
+<field name="ACTION_PASTE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.action.PASTE&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="ACTION_PICK"
  type="java.lang.String"
  transient="false"
@@ -42959,6 +50955,17 @@
  visibility="public"
 >
 </field>
+<field name="CATEGORY_APP_MARKET"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.category.APP_MARKET&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="CATEGORY_BROWSABLE"
  type="java.lang.String"
  transient="false"
@@ -45016,6 +53023,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"
@@ -45555,6 +53739,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"
@@ -45697,6 +53896,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"
@@ -46414,6 +54628,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"
 >
@@ -46679,6 +55032,28 @@
  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"
+ volatile="false"
+ value="512"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FLAG_MULTIPROCESS"
  type="int"
  transient="false"
@@ -47221,6 +55596,17 @@
  visibility="public"
 >
 </field>
+<field name="FLAG_SUPPORTS_XLARGE_SCREENS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="524288"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FLAG_SYSTEM"
  type="int"
  transient="false"
@@ -52456,6 +60842,17 @@
  visibility="public"
 >
 </field>
+<field name="SCREENLAYOUT_SIZE_XLARGE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="TOUCHSCREEN_FINGER"
  type="int"
  transient="false"
@@ -54336,6 +62733,17 @@
 <parameter name="column" type="int">
 </parameter>
 </method>
+<method name="getNotificationUri"
+ return="android.net.Uri"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getPosition"
  return="int"
  abstract="false"
@@ -54373,6 +62781,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"
@@ -54380,7 +62801,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="protected"
 >
 <parameter name="columnIndex" type="int">
@@ -54448,7 +62869,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="protected"
 >
 <parameter name="columnIndex" type="int">
@@ -54734,7 +63155,7 @@
  volatile="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="protected"
 >
 </field>
@@ -54870,7 +63291,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="columnIndex" type="int">
@@ -54883,7 +63304,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="columnIndex" type="int">
@@ -54896,7 +63317,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="columnIndex" type="int">
@@ -54922,7 +63343,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="columnIndex" type="int">
@@ -55396,6 +63817,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"
@@ -55636,6 +64070,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"
@@ -55993,6 +64482,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"
@@ -56000,7 +64504,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -56015,7 +64519,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -56030,7 +64534,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -56045,7 +64549,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -56060,7 +64564,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -56471,6 +64975,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"
@@ -56482,6 +64999,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"
@@ -56790,6 +65318,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"
@@ -56821,6 +65370,21 @@
 <parameter name="sqlString" type="java.lang.String">
 </parameter>
 </method>
+<method name="appendSelectionArgs"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="originalValues" type="java.lang.String[]">
+</parameter>
+<parameter name="newValues" type="java.lang.String[]">
+</parameter>
+</method>
 <method name="appendValueToSql"
  return="void"
  abstract="false"
@@ -56853,6 +65417,38 @@
 <parameter name="value" type="java.lang.Object">
 </parameter>
 </method>
+<method name="blobFileDescriptorForQuery"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="db" type="android.database.sqlite.SQLiteDatabase">
+</parameter>
+<parameter name="query" type="java.lang.String">
+</parameter>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+</method>
+<method name="blobFileDescriptorForQuery"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="prog" type="android.database.sqlite.SQLiteStatement">
+</parameter>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+</method>
 <method name="createDbFromSqlStatements"
  return="void"
  abstract="false"
@@ -57290,6 +65886,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"
@@ -57476,6 +66085,116 @@
 <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_DDL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATEMENT_OTHER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="99"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATEMENT_PRAGMA"
+ 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_UNPREPARED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="9"
+ 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"
@@ -57710,6 +66429,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"
@@ -58193,6 +66944,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"
@@ -58304,7 +67163,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">
@@ -58316,6 +67175,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"
@@ -58453,6 +67326,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"
@@ -58466,6 +67350,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"
@@ -58522,6 +67419,17 @@
 <parameter name="whereArgs" type="java.lang.String[]">
 </parameter>
 </method>
+<method name="enableWriteAheadLogging"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="endTransaction"
  return="void"
  abstract="false"
@@ -58578,6 +67486,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"
@@ -58618,7 +67537,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -58699,6 +67618,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"
@@ -58750,7 +67680,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="table" type="java.lang.String">
@@ -58765,7 +67695,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="table" type="java.lang.String">
@@ -58816,6 +67746,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"
@@ -58846,6 +67795,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"
@@ -59065,6 +68031,19 @@
 <parameter name="lockingEnabled" type="boolean">
 </parameter>
 </method>
+<method name="setMaxSqlCacheSize"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cacheSize" type="int">
+</parameter>
+</method>
 <method name="setMaximumSize"
  return="long"
  abstract="false"
@@ -59267,6 +68246,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"
@@ -59366,6 +68356,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"
@@ -59525,6 +68569,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"
@@ -59602,6 +68664,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"
@@ -59610,6 +68699,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"
@@ -59727,7 +68829,7 @@
  synchronized="false"
  static="false"
  final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -60150,6 +69252,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"
@@ -60180,6 +69309,28 @@
  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="simpleQueryForBlobFileDescriptor"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="simpleQueryForLong"
  return="long"
  abstract="false"
@@ -60203,6 +69354,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"
@@ -64768,6 +73946,146 @@
 >
 </field>
 </class>
+<class name="BitmapRegionDecoder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="decodeRegion"
+ return="android.graphics.Bitmap"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rect" type="android.graphics.Rect">
+</parameter>
+<parameter name="options" type="android.graphics.BitmapFactory.Options">
+</parameter>
+</method>
+<method name="getHeight"
+ 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="isRecycled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="newInstance"
+ return="android.graphics.BitmapRegionDecoder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="data" type="byte[]">
+</parameter>
+<parameter name="offset" type="int">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+<parameter name="isShareable" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="newInstance"
+ return="android.graphics.BitmapRegionDecoder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fd" type="java.io.FileDescriptor">
+</parameter>
+<parameter name="isShareable" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="newInstance"
+ return="android.graphics.BitmapRegionDecoder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="is" type="java.io.InputStream">
+</parameter>
+<parameter name="isShareable" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="newInstance"
+ return="android.graphics.BitmapRegionDecoder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pathName" type="java.lang.String">
+</parameter>
+<parameter name="isShareable" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="recycle"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
 <class name="BitmapShader"
  extends="android.graphics.Shader"
  abstract="false"
@@ -65009,16 +74327,6 @@
 <parameter name="bitmap" type="android.graphics.Bitmap">
 </parameter>
 </constructor>
-<constructor name="Canvas"
- type="android.graphics.Canvas"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="gl" type="javax.microedition.khronos.opengles.GL">
-</parameter>
-</constructor>
 <method name="clipPath"
  return="boolean"
  abstract="false"
@@ -65792,7 +75100,7 @@
 <method name="drawText"
  return="void"
  abstract="false"
- native="true"
+ native="false"
  synchronized="false"
  static="false"
  final="false"
@@ -65935,17 +75243,6 @@
 <parameter name="paint" type="android.graphics.Paint">
 </parameter>
 </method>
-<method name="freeGlCaches"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getClipBounds"
  return="boolean"
  abstract="false"
@@ -65999,8 +75296,8 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
- visibility="public"
+ deprecated="deprecated"
+ visibility="protected"
 >
 </method>
 <method name="getHeight"
@@ -66060,6 +75357,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"
@@ -66366,21 +75674,6 @@
 <parameter name="matrix" type="android.graphics.Matrix">
 </parameter>
 </method>
-<method name="setViewport"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="width" type="int">
-</parameter>
-<parameter name="height" type="int">
-</parameter>
-</method>
 <method name="skew"
  return="void"
  abstract="false"
@@ -69778,7 +79071,7 @@
 <method name="setShadowLayer"
  return="void"
  abstract="false"
- native="true"
+ native="false"
  synchronized="false"
  static="false"
  final="false"
@@ -75745,6 +85038,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"
@@ -76639,6 +85943,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"
@@ -81901,6 +91235,19 @@
  visibility="public"
 >
 </method>
+<method name="onCurrentInputMethodSubtypeChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="newSubtype" type="android.view.inputmethod.InputMethodSubtype">
+</parameter>
+</method>
 <method name="onDisplayCompletions"
  return="void"
  abstract="false"
@@ -82504,6 +91851,19 @@
 <parameter name="binding" type="android.view.inputmethod.InputBinding">
 </parameter>
 </method>
+<method name="changeInputMethodSubtype"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="subtype" type="android.view.inputmethod.InputMethodSubtype">
+</parameter>
+</method>
 <method name="hideSoftInput"
  return="void"
  abstract="false"
@@ -82811,6 +92171,24 @@
 </parameter>
 <parameter name="modeId" type="int">
 </parameter>
+<parameter name="width" type="int">
+</parameter>
+<parameter name="height" type="int">
+</parameter>
+</constructor>
+<constructor name="Keyboard"
+ type="android.inputmethodservice.Keyboard"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="xmlLayoutResId" type="int">
+</parameter>
+<parameter name="modeId" type="int">
+</parameter>
 </constructor>
 <constructor name="Keyboard"
  type="android.inputmethodservice.Keyboard"
@@ -89585,6 +98963,78 @@
 <parameter name="quality" type="int">
 </parameter>
 </method>
+<method name="hasProfile"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="quality" type="int">
+</parameter>
+</method>
+<method name="hasProfile"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cameraId" type="int">
+</parameter>
+<parameter name="quality" type="int">
+</parameter>
+</method>
+<field name="QUALITY_1080P"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_480P"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_720P"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_CIF"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="QUALITY_HIGH"
  type="int"
  transient="false"
@@ -89607,6 +99057,94 @@
  visibility="public"
 >
 </field>
+<field name="QUALITY_QCIF"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_TIME_LAPSE_1080P"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1006"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_TIME_LAPSE_480P"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1004"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_TIME_LAPSE_720P"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1005"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_TIME_LAPSE_CIF"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1003"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_TIME_LAPSE_HIGH"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1001"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_TIME_LAPSE_LOW"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1000"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="QUALITY_TIME_LAPSE_QCIF"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1002"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="audioBitRate"
  type="int"
  transient="false"
@@ -91703,6 +101241,32 @@
 <exception name="IllegalStateException" type="java.lang.IllegalStateException">
 </exception>
 </method>
+<method name="setAuxiliaryOutputFile"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fd" type="java.io.FileDescriptor">
+</parameter>
+</method>
+<method name="setAuxiliaryOutputFile"
+ 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>
+</method>
 <method name="setCamera"
  return="void"
  abstract="false"
@@ -91716,6 +101280,19 @@
 <parameter name="c" type="android.hardware.Camera">
 </parameter>
 </method>
+<method name="setCaptureRate"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fps" type="double">
+</parameter>
+</method>
 <method name="setMaxDuration"
  return="void"
  abstract="false"
@@ -98665,7 +108242,7 @@
  type="android.net.SSLCertificateSocketFactory"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="handshakeTimeoutMillis" type="int">
@@ -99140,6 +108717,21 @@
  visibility="public"
 >
 </method>
+<method name="getBooleanQueryParameter"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="key" type="java.lang.String">
+</parameter>
+<parameter name="defaultValue" type="boolean">
+</parameter>
+</method>
 <method name="getEncodedAuthority"
  return="java.lang.String"
  abstract="true"
@@ -99296,6 +108888,17 @@
 <parameter name="key" type="java.lang.String">
 </parameter>
 </method>
+<method name="getQueryParameterNames"
+ 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="getQueryParameters"
  return="java.util.List&lt;java.lang.String&gt;"
  abstract="false"
@@ -99542,6 +109145,17 @@
  visibility="public"
 >
 </method>
+<method name="clearQuery"
+ return="android.net.Uri.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="encodedAuthority"
  return="android.net.Uri.Builder"
  abstract="false"
@@ -100765,7 +110379,7 @@
  type="android.net.http.SslCertificate"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="issuedTo" type="java.lang.String">
@@ -100832,7 +110446,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -100854,7 +110468,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -123026,6 +132640,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"
@@ -129072,6 +138697,19 @@
 <exception name="IOException" type="java.io.IOException">
 </exception>
 </method>
+<method name="createPipe"
+ return="android.os.ParcelFileDescriptor[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
 <method name="describeContents"
  return="int"
  abstract="false"
@@ -129083,6 +138721,23 @@
  visibility="public"
 >
 </method>
+<method name="fromData"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="data" type="byte[]">
+</parameter>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
 <method name="fromSocket"
  return="android.os.ParcelFileDescriptor"
  abstract="false"
@@ -131393,6 +141048,53 @@
 </parameter>
 </method>
 </class>
+<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"
@@ -131401,6 +141103,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"
@@ -131429,6 +141153,28 @@
 <exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
 </exception>
 </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"
@@ -131446,6 +141192,19 @@
 <parameter name="listener" type="android.os.storage.OnObbStateChangeListener">
 </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"
@@ -131463,6 +141222,124 @@
 <parameter name="listener" type="android.os.storage.OnObbStateChangeListener">
 </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"
@@ -132258,6 +142135,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"
@@ -132376,6 +142395,28 @@
  visibility="public"
 >
 </method>
+<method name="getExtras"
+ return="android.os.Bundle"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ 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"
@@ -132808,6 +142849,17 @@
 <parameter name="defaultValue" type="java.lang.Object">
 </parameter>
 </method>
+<method name="peekExtras"
+ return="android.os.Bundle"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="persistBoolean"
  return="boolean"
  abstract="false"
@@ -132938,6 +142990,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"
@@ -133256,6 +143321,8 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<implements name="android.preference.PreferenceFragment.OnPreferenceStartFragmentCallback">
+</implements>
 <constructor name="PreferenceActivity"
  type="android.preference.PreferenceActivity"
  static="false"
@@ -133271,6 +143338,622 @@
  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="hasHeaders"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="invalidateHeaders"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isMultiPane"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not 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="onGetInitialHeader"
+ return="android.preference.PreferenceActivity.Header"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onGetNewHeader"
+ return="android.preference.PreferenceActivity.Header"
+ 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="setListFooter"
+ 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="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="showBreadCrumbs"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="title" type="java.lang.CharSequence">
+</parameter>
+<parameter name="shortTitle" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="startPreferenceFragment"
+ 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="push" type="boolean">
+</parameter>
+</method>
+<method name="startPreferenceFragment"
+ 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="ft" type="android.app.FragmentTransaction">
+</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>
+<parameter name="args" type="android.os.Bundle">
+</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>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="switchToHeader"
+ 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>
+</method>
+<method name="switchToHeaderInner"
+ 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>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+<parameter name="next" type="boolean">
+</parameter>
+</method>
+<field name="EXTRA_NO_HEADERS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;:android:no_headers&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_SHOW_FRAGMENT"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;:android:show_fragment&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_SHOW_FRAGMENT_ARGUMENTS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;:android:show_fragment_args&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="HEADER_ID_UNDEFINED"
+ type="long"
+ transient="false"
+ volatile="false"
+ value="-1L"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="PreferenceActivity.Header"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="PreferenceActivity.Header"
+ type="android.preference.PreferenceActivity.Header"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="readFromParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="in" type="android.os.Parcel">
+</parameter>
+</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>
+<field name="breadCrumbShortTitle"
+ type="java.lang.CharSequence"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="breadCrumbTitle"
+ type="java.lang.CharSequence"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="extras"
+ type="android.os.Bundle"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragment"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentArguments"
+ type="android.os.Bundle"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="iconRes"
+ type="int"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="id"
+ type="long"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="intent"
+ type="android.content.Intent"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="summary"
+ type="java.lang.CharSequence"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="title"
+ type="java.lang.CharSequence"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</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"
 >
@@ -133354,51 +144037,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"
@@ -134359,9 +145020,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"
@@ -134745,7 +145406,7 @@
  value="&quot;url&quot;"
  static="true"
  final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </field>
@@ -137804,6 +148465,28 @@
  visibility="public"
 >
 </field>
+<field name="DIRECTORY_PARAM_KEY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;directory&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="LIMIT_PARAM_KEY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;limit&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="ContactsContract.AggregationExceptions"
  extends="java.lang.Object"
@@ -138079,6 +148762,17 @@
 <parameter name="type" type="int">
 </parameter>
 </method>
+<field name="ADDRESS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;data1&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="CONTENT_FILTER_URI"
  type="android.net.Uri"
  transient="false"
@@ -139913,6 +150607,91 @@
 >
 </field>
 </class>
+<interface name="ContactsContract.ContactNameColumns"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<field name="DISPLAY_NAME_ALTERNATIVE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;display_name_alt&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DISPLAY_NAME_PRIMARY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;display_name&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DISPLAY_NAME_SOURCE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;display_name_source&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PHONETIC_NAME"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;phonetic_name&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PHONETIC_NAME_STYLE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;phonetic_name_style&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SORT_KEY_ALTERNATIVE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;sort_key_alt&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SORT_KEY_PRIMARY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;sort_key&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
 <interface name="ContactsContract.ContactOptionsColumns"
  abstract="true"
  static="true"
@@ -139983,6 +150762,17 @@
  deprecated="not deprecated"
  visibility="protected"
 >
+<field name="CONTACT_CHAT_CAPABILITY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;contact_chat_capability&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="CONTACT_PRESENCE"
  type="java.lang.String"
  transient="false"
@@ -140060,6 +150850,8 @@
 >
 <implements name="android.provider.BaseColumns">
 </implements>
+<implements name="android.provider.ContactsContract.ContactNameColumns">
+</implements>
 <implements name="android.provider.ContactsContract.ContactOptionsColumns">
 </implements>
 <implements name="android.provider.ContactsContract.ContactStatusColumns">
@@ -140255,6 +151047,10 @@
 >
 <implements name="android.provider.BaseColumns">
 </implements>
+<implements name="android.provider.ContactsContract.ContactOptionsColumns">
+</implements>
+<implements name="android.provider.ContactsContract.ContactStatusColumns">
+</implements>
 <implements name="android.provider.ContactsContract.ContactsColumns">
 </implements>
 <field name="CONTENT_DIRECTORY"
@@ -140293,6 +151089,68 @@
 >
 </field>
 </class>
+<class name="ContactsContract.Contacts.Entity"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.provider.BaseColumns">
+</implements>
+<implements name="android.provider.ContactsContract.BaseSyncColumns">
+</implements>
+<implements name="android.provider.ContactsContract.ContactNameColumns">
+</implements>
+<implements name="android.provider.ContactsContract.ContactOptionsColumns">
+</implements>
+<implements name="android.provider.ContactsContract.ContactStatusColumns">
+</implements>
+<implements name="android.provider.ContactsContract.ContactsColumns">
+</implements>
+<implements name="android.provider.ContactsContract.DataColumns">
+</implements>
+<implements name="android.provider.ContactsContract.RawContactsColumns">
+</implements>
+<implements name="android.provider.ContactsContract.StatusColumns">
+</implements>
+<implements name="android.provider.ContactsContract.SyncColumns">
+</implements>
+<field name="CONTENT_DIRECTORY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;entities&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DATA_ID"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;data_id&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RAW_CONTACT_ID"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;raw_contact_id&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="ContactsContract.Contacts.Photo"
  extends="java.lang.Object"
  abstract="false"
@@ -140303,7 +151161,7 @@
 >
 <implements name="android.provider.BaseColumns">
 </implements>
-<implements name="android.provider.ContactsContract.DataColumns">
+<implements name="android.provider.ContactsContract.DataColumnsWithJoins">
 </implements>
 <field name="CONTENT_DIRECTORY"
  type="java.lang.String"
@@ -140316,6 +151174,17 @@
  visibility="public"
 >
 </field>
+<field name="PHOTO"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;data15&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <interface name="ContactsContract.ContactsColumns"
  abstract="true"
@@ -140379,6 +151248,28 @@
  visibility="public"
 >
 </field>
+<field name="PHOTO_THUMBNAIL_URI"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;photo_thumb_uri&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PHOTO_URI"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;photo_uri&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </interface>
 <class name="ContactsContract.Data"
  extends="java.lang.Object"
@@ -140621,6 +151512,17 @@
  visibility="public"
 >
 </field>
+<field name="IS_READ_ONLY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;is_read_only&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="IS_SUPER_PRIMARY"
  type="java.lang.String"
  transient="false"
@@ -140708,6 +151610,8 @@
 >
 <implements name="android.provider.BaseColumns">
 </implements>
+<implements name="android.provider.ContactsContract.ContactNameColumns">
+</implements>
 <implements name="android.provider.ContactsContract.ContactOptionsColumns">
 </implements>
 <implements name="android.provider.ContactsContract.ContactStatusColumns">
@@ -140721,6 +151625,441 @@
 <implements name="android.provider.ContactsContract.StatusColumns">
 </implements>
 </interface>
+<class name="ContactsContract.Directory"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.provider.BaseColumns">
+</implements>
+<method name="notifyDirectoryChange"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="resolver" type="android.content.ContentResolver">
+</parameter>
+</method>
+<field name="ACCOUNT_NAME"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;accountName&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACCOUNT_TYPE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;accountType&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CONTENT_ITEM_TYPE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;vnd.android.cursor.item/contact_directory&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CONTENT_TYPE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;vnd.android.cursor.dir/contact_directories&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CONTENT_URI"
+ type="android.net.Uri"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DEFAULT"
+ type="long"
+ transient="false"
+ volatile="false"
+ value="0L"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DIRECTORY_AUTHORITY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;authority&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DISPLAY_NAME"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;displayName&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXPORT_SUPPORT"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;exportSupport&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXPORT_SUPPORT_ANY_ACCOUNT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXPORT_SUPPORT_NONE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXPORT_SUPPORT_SAME_ACCOUNT_ONLY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="LOCAL_INVISIBLE"
+ type="long"
+ transient="false"
+ volatile="false"
+ value="1L"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PACKAGE_NAME"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;packageName&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PHOTO_SUPPORT"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;photoSupport&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PHOTO_SUPPORT_FULL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PHOTO_SUPPORT_FULL_SIZE_ONLY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PHOTO_SUPPORT_NONE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PHOTO_SUPPORT_THUMBNAIL_ONLY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SHORTCUT_SUPPORT"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;shortcutSupport&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SHORTCUT_SUPPORT_DATA_ITEMS_ONLY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SHORTCUT_SUPPORT_FULL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SHORTCUT_SUPPORT_NONE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TYPE_RESOURCE_ID"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;typeResourceId&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<interface name="ContactsContract.DisplayNameSources"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<field name="EMAIL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="10"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NICKNAME"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="35"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ORGANIZATION"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="30"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PHONE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="20"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STRUCTURED_NAME"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="40"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="UNDEFINED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
+<interface name="ContactsContract.FullNameStyle"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<field name="CHINESE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CJK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="JAPANESE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KOREAN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="UNDEFINED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="WESTERN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
 <class name="ContactsContract.Groups"
  extends="java.lang.Object"
  abstract="false"
@@ -140798,6 +152137,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"
@@ -140809,6 +152159,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"
@@ -141363,6 +152724,58 @@
 >
 </field>
 </interface>
+<interface name="ContactsContract.PhoneticNameStyle"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<field name="JAPANESE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KOREAN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PINYIN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="UNDEFINED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
 <class name="ContactsContract.Presence"
  extends="android.provider.ContactsContract.StatusUpdates"
  abstract="false"
@@ -141545,6 +152958,8 @@
 >
 <implements name="android.provider.BaseColumns">
 </implements>
+<implements name="android.provider.ContactsContract.ContactNameColumns">
+</implements>
 <implements name="android.provider.ContactsContract.ContactOptionsColumns">
 </implements>
 <implements name="android.provider.ContactsContract.RawContactsColumns">
@@ -141608,7 +153023,7 @@
  value="1"
  static="true"
  final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </field>
@@ -141755,6 +153170,17 @@
  visibility="public"
 >
 </field>
+<field name="RAW_CONTACT_IS_READ_ONLY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;raw_contact_is_read_only&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </interface>
 <class name="ContactsContract.RawContactsEntity"
  extends="java.lang.Object"
@@ -141960,6 +153386,50 @@
  visibility="public"
 >
 </field>
+<field name="CAPABILITY_HAS_CAMERA"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CAPABILITY_HAS_VIDEO"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CAPABILITY_HAS_VOICE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CHAT_CAPABILITY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;chat_capability&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="DO_NOT_DISTURB"
  type="int"
  transient="false"
@@ -143402,6 +154872,21 @@
 <parameter name="volumeName" type="java.lang.String">
 </parameter>
 </method>
+<method name="getContentUriForAudioId"
+ return="android.net.Uri"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="volumeName" type="java.lang.String">
+</parameter>
+<parameter name="audioId" type="int">
+</parameter>
+</method>
 <field name="CONTENT_TYPE"
  type="java.lang.String"
  transient="false"
@@ -146279,6 +157764,17 @@
  visibility="public"
 >
 </field>
+<field name="SELECTED_INPUT_METHOD_SUBTYPE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;selected_input_method_subtype&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="SETTINGS_CLASSNAME"
  type="java.lang.String"
  transient="false"
@@ -147000,6 +158496,17 @@
  visibility="public"
 >
 </field>
+<field name="AUTO_TIME_ZONE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;auto_time_zone&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="BLUETOOTH_DISCOVERABILITY"
  type="java.lang.String"
  transient="false"
@@ -150869,7 +162376,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"
@@ -155711,6 +167218,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"
@@ -158631,6 +170168,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"
@@ -158975,17 +170531,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"
@@ -158997,30 +170542,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"
@@ -159047,17 +170568,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"
@@ -159243,8 +170753,8 @@
 <parameter name="columnIndex" type="int">
 </parameter>
 </method>
-<method name="getWantsAllOnMoveCalls"
- return="boolean"
+<method name="getType"
+ return="int"
  abstract="false"
  native="false"
  synchronized="false"
@@ -159253,8 +170763,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"
@@ -159468,17 +170980,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"
@@ -159505,124 +171006,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"
@@ -161775,15 +173158,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"
@@ -161794,7 +173185,7 @@
 </method>
 <method name="hasText"
  return="boolean"
- abstract="false"
+ abstract="true"
  native="false"
  synchronized="false"
  static="false"
@@ -161805,7 +173196,7 @@
 </method>
 <method name="setText"
  return="void"
- abstract="false"
+ abstract="true"
  native="false"
  synchronized="false"
  static="false"
@@ -164630,6 +176021,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"
@@ -165690,7 +177104,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>
@@ -174077,6 +185491,18 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="cause" type="java.lang.Throwable">
+</parameter>
+</constructor>
+<constructor name="AndroidException"
+ type="android.util.AndroidException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
 <parameter name="cause" type="java.lang.Exception">
 </parameter>
 </constructor>
@@ -174114,6 +185540,18 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="cause" type="java.lang.Throwable">
+</parameter>
+</constructor>
+<constructor name="AndroidRuntimeException"
+ type="android.util.AndroidRuntimeException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
 <parameter name="cause" type="java.lang.Exception">
 </parameter>
 </constructor>
@@ -175122,7 +186560,7 @@
  abstract="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <constructor name="EventLogTags"
@@ -175277,6 +186715,483 @@
 </parameter>
 </method>
 </class>
+<class name="JsonReader"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.io.Closeable">
+</implements>
+<constructor name="JsonReader"
+ type="android.util.JsonReader"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="in" type="java.io.Reader">
+</parameter>
+</constructor>
+<method name="beginArray"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="beginObject"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endArray"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endObject"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="hasNext"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextBoolean"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextDouble"
+ return="double"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextInt"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextLong"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextNull"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextString"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="peek"
+ return="android.util.JsonToken"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="setLenient"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="lenient" type="boolean">
+</parameter>
+</method>
+<method name="skipValue"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="syntaxError"
+ return="java.io.IOException"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="message" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+</class>
+<class name="JsonToken"
+ extends="java.lang.Enum"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="valueOf"
+ return="android.util.JsonToken"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</method>
+<method name="values"
+ return="android.util.JsonToken[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="JsonWriter"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.io.Closeable">
+</implements>
+<constructor name="JsonWriter"
+ type="android.util.JsonWriter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="out" type="java.io.Writer">
+</parameter>
+</constructor>
+<method name="beginArray"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="beginObject"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endArray"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endObject"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="flush"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="name"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nullValue"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="setIndent"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="indent" type="java.lang.String">
+</parameter>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="double">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="long">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+</class>
 <class name="Log"
  extends="java.lang.Object"
  abstract="false"
@@ -176002,7 +187917,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"
@@ -176013,7 +187928,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"
@@ -176280,6 +188195,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"
@@ -177885,6 +189813,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"
@@ -178135,6 +190288,219 @@
 >
 </field>
 </class>
+<class name="DragEvent"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAction"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getClipData"
+ return="android.content.ClipData"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getClipDescription"
+ return="android.content.ClipDescription"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="obtain"
+ return="android.view.DragEvent"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="action" type="int">
+</parameter>
+<parameter name="x" type="float">
+</parameter>
+<parameter name="y" type="float">
+</parameter>
+<parameter name="description" type="android.content.ClipDescription">
+</parameter>
+<parameter name="data" type="android.content.ClipData">
+</parameter>
+</method>
+<method name="obtain"
+ return="android.view.DragEvent"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="source" type="android.view.DragEvent">
+</parameter>
+</method>
+<method name="recycle"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="ACTION_DRAG_ENDED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_ENTERED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_EXITED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_LOCATION"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_STARTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DROP"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="FocusFinder"
  extends="java.lang.Object"
  abstract="false"
@@ -182361,6 +194727,17 @@
  visibility="public"
 >
 </method>
+<method name="getFactory2"
+ return="android.view.LayoutInflater.Factory2"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getFilter"
  return="android.view.LayoutInflater.Filter"
  abstract="false"
@@ -182453,6 +194830,25 @@
 <exception name="ClassNotFoundException" type="java.lang.ClassNotFoundException">
 </exception>
 </method>
+<method name="onCreateView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="parent" type="android.view.View">
+</parameter>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+<exception name="ClassNotFoundException" type="java.lang.ClassNotFoundException">
+</exception>
+</method>
 <method name="setFactory"
  return="void"
  abstract="false"
@@ -182466,6 +194862,19 @@
 <parameter name="factory" type="android.view.LayoutInflater.Factory">
 </parameter>
 </method>
+<method name="setFactory2"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="factory" type="android.view.LayoutInflater.Factory2">
+</parameter>
+</method>
 <method name="setFilter"
  return="void"
  abstract="false"
@@ -182505,6 +194914,35 @@
 </parameter>
 </method>
 </interface>
+<interface name="LayoutInflater.Factory2"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.view.LayoutInflater.Factory">
+</implements>
+<method name="onCreateView"
+ return="android.view.View"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.View">
+</parameter>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+</method>
+</interface>
 <interface name="LayoutInflater.Filter"
  abstract="true"
  static="true"
@@ -183032,6 +195470,17 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<method name="getActionView"
+ return="android.view.View"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getAlphabeticShortcut"
  return="char"
  abstract="true"
@@ -183208,6 +195657,19 @@
  visibility="public"
 >
 </method>
+<method name="setActionView"
+ return="android.view.MenuItem"
+ 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="setAlphabeticShortcut"
  return="android.view.MenuItem"
  abstract="true"
@@ -183340,6 +195802,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"
@@ -183392,6 +195867,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"
@@ -184382,6 +196890,19 @@
 <parameter name="y" type="float">
 </parameter>
 </method>
+<method name="transform"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="matrix" type="android.graphics.Matrix">
+</parameter>
+</method>
 <method name="writeToParcel"
  return="void"
  abstract="false"
@@ -184996,6 +197517,28 @@
  visibility="public"
 >
 </method>
+<method name="getCurrentSpanX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCurrentSpanY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getEventTime"
  return="long"
  abstract="false"
@@ -185040,6 +197583,28 @@
  visibility="public"
 >
 </method>
+<method name="getPreviousSpanX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPreviousSpanY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getScaleFactor"
  return="float"
  abstract="false"
@@ -186619,6 +199184,19 @@
 <parameter name="focusableMode" type="int">
 </parameter>
 </method>
+<method name="addOnLayoutChangeListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.view.View.OnLayoutChangeListener">
+</parameter>
+</method>
 <method name="addTouchables"
  return="void"
  abstract="false"
@@ -186879,6 +199457,19 @@
 <parameter name="hint" type="int">
 </parameter>
 </method>
+<method name="dispatchDragEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.DragEvent">
+</parameter>
+</method>
 <method name="dispatchDraw"
  return="void"
  abstract="false"
@@ -186970,6 +199561,19 @@
 <parameter name="container" type="android.util.SparseArray&lt;android.os.Parcelable&gt;">
 </parameter>
 </method>
+<method name="dispatchSetActivated"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="activated" type="boolean">
+</parameter>
+</method>
 <method name="dispatchSetPressed"
  return="void"
  abstract="false"
@@ -187176,6 +199780,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"
@@ -187620,6 +200235,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"
@@ -187697,8 +200323,8 @@
  visibility="public"
 >
 </method>
-<method name="getOverscrollMode"
- return="int"
+<method name="getOnLayoutChangeListeners"
+ return="java.util.List&lt;android.view.View.OnLayoutChangeListener&gt;"
  abstract="false"
  native="false"
  synchronized="false"
@@ -187763,6 +200389,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"
@@ -187818,6 +200466,61 @@
  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="getRotationX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getRotationY"
+ 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"
@@ -187963,6 +200666,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"
@@ -188064,6 +200789,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"
@@ -188196,6 +200943,17 @@
 <parameter name="drawable" type="android.graphics.drawable.Drawable">
 </parameter>
 </method>
+<method name="isActivated"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isClickable"
  return="boolean"
  abstract="false"
@@ -188284,6 +201042,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="isHorizontalFadingEdgeEnabled"
  return="boolean"
  abstract="false"
@@ -188394,6 +201163,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"
@@ -188655,6 +201435,19 @@
 <parameter name="hint" type="int">
 </parameter>
 </method>
+<method name="onDragEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="event" type="android.view.DragEvent">
+</parameter>
+</method>
 <method name="onDraw"
  return="void"
  abstract="false"
@@ -188668,6 +201461,19 @@
 <parameter name="canvas" type="android.graphics.Canvas">
 </parameter>
 </method>
+<method name="onDrawDragThumbnail"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
 <method name="onDrawScrollBars"
  return="void"
  abstract="false"
@@ -188861,7 +201667,7 @@
 <parameter name="heightMeasureSpec" type="int">
 </parameter>
 </method>
-<method name="onOverscrolled"
+<method name="onMeasureDragThumbnail"
  return="void"
  abstract="false"
  native="false"
@@ -188871,14 +201677,6 @@
  deprecated="not deprecated"
  visibility="protected"
 >
-<parameter name="scrollX" type="int">
-</parameter>
-<parameter name="scrollY" type="int">
-</parameter>
-<parameter name="clampedX" type="boolean">
-</parameter>
-<parameter name="clampedY" type="boolean">
-</parameter>
 </method>
 <method name="onRestoreInstanceState"
  return="void"
@@ -189033,35 +201831,6 @@
 <parameter name="visibility" type="int">
 </parameter>
 </method>
-<method name="overscrollBy"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-<parameter name="deltaX" type="int">
-</parameter>
-<parameter name="deltaY" type="int">
-</parameter>
-<parameter name="scrollX" type="int">
-</parameter>
-<parameter name="scrollY" type="int">
-</parameter>
-<parameter name="scrollRangeX" type="int">
-</parameter>
-<parameter name="scrollRangeY" type="int">
-</parameter>
-<parameter name="maxOverscrollX" type="int">
-</parameter>
-<parameter name="maxOverscrollY" type="int">
-</parameter>
-<parameter name="isTouchEvent" type="boolean">
-</parameter>
-</method>
 <method name="performClick"
  return="boolean"
  abstract="false"
@@ -189241,6 +202010,19 @@
 <parameter name="action" type="java.lang.Runnable">
 </parameter>
 </method>
+<method name="removeOnLayoutChangeListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.view.View.OnLayoutChangeListener">
+</parameter>
+</method>
 <method name="requestFocus"
  return="boolean"
  abstract="false"
@@ -189444,6 +202226,32 @@
 <parameter name="event" type="android.view.accessibility.AccessibilityEvent">
 </parameter>
 </method>
+<method name="setActivated"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="activated" type="boolean">
+</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"
@@ -189496,6 +202304,19 @@
 <parameter name="resid" type="int">
 </parameter>
 </method>
+<method name="setBottom"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="bottom" type="int">
+</parameter>
+</method>
 <method name="setClickable"
  return="void"
  abstract="false"
@@ -189522,6 +202343,21 @@
 <parameter name="contentDescription" type="java.lang.CharSequence">
 </parameter>
 </method>
+<method name="setDragThumbnailDimension"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="width" type="int">
+</parameter>
+<parameter name="height" type="int">
+</parameter>
+</method>
 <method name="setDrawingCacheBackgroundColor"
  return="void"
  abstract="false"
@@ -189717,6 +202553,19 @@
 <parameter name="params" type="android.view.ViewGroup.LayoutParams">
 </parameter>
 </method>
+<method name="setLeft"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="left" type="int">
+</parameter>
+</method>
 <method name="setLongClickable"
  return="void"
  abstract="false"
@@ -189901,19 +202750,6 @@
 <parameter name="l" type="android.view.View.OnTouchListener">
 </parameter>
 </method>
-<method name="setOverscrollMode"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="overscrollMode" type="int">
-</parameter>
-</method>
 <method name="setPadding"
  return="void"
  abstract="false"
@@ -189933,6 +202769,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"
@@ -189946,6 +202808,58 @@
 <parameter name="pressed" type="boolean">
 </parameter>
 </method>
+<method name="setRight"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="right" type="int">
+</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="setRotationX"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rotationX" type="float">
+</parameter>
+</method>
+<method name="setRotationY"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rotationY" type="float">
+</parameter>
+</method>
 <method name="setSaveEnabled"
  return="void"
  abstract="false"
@@ -189959,6 +202873,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"
@@ -190052,6 +203005,19 @@
 <parameter name="tag" type="java.lang.Object">
 </parameter>
 </method>
+<method name="setTop"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="top" type="int">
+</parameter>
+</method>
 <method name="setTouchDelegate"
  return="void"
  abstract="false"
@@ -190065,6 +203031,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"
@@ -190130,6 +203122,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"
@@ -190141,6 +203159,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"
@@ -190536,39 +203567,6 @@
  visibility="public"
 >
 </field>
-<field name="OVERSCROLL_ALWAYS"
- type="int"
- transient="false"
- volatile="false"
- value="0"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="OVERSCROLL_IF_CONTENT_SCROLLS"
- type="int"
- transient="false"
- volatile="false"
- value="1"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="OVERSCROLL_NEVER"
- type="int"
- transient="false"
- volatile="false"
- value="2"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET"
  type="int[]"
  transient="false"
@@ -190884,6 +203882,53 @@
 >
 </field>
 </class>
+<class name="View.DragThumbnailBuilder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="View.DragThumbnailBuilder"
+ type="android.view.View.DragThumbnailBuilder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+</constructor>
+<method name="onDrawThumbnail"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
+<method name="onProvideThumbnailMetrics"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="thumbnailSize" type="android.graphics.Point">
+</parameter>
+<parameter name="thumbnailTouchPoint" type="android.graphics.Point">
+</parameter>
+</method>
+</class>
 <class name="View.MeasureSpec"
  extends="java.lang.Object"
  abstract="false"
@@ -191082,6 +204127,43 @@
 </parameter>
 </method>
 </interface>
+<interface name="View.OnLayoutChangeListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onLayoutChange"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="v" type="android.view.View">
+</parameter>
+<parameter name="left" type="int">
+</parameter>
+<parameter name="top" type="int">
+</parameter>
+<parameter name="right" type="int">
+</parameter>
+<parameter name="bottom" type="int">
+</parameter>
+<parameter name="oldLeft" type="int">
+</parameter>
+<parameter name="oldTop" type="int">
+</parameter>
+<parameter name="oldRight" type="int">
+</parameter>
+<parameter name="oldBottom" type="int">
+</parameter>
+</method>
+</interface>
 <interface name="View.OnLongClickListener"
  abstract="true"
  static="true"
@@ -191331,28 +204413,6 @@
  visibility="public"
 >
 </method>
-<method name="getScaledOverflingDistance"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getScaledOverscrollDistance"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getScaledPagingTouchSlop"
  return="int"
  abstract="false"
@@ -192107,6 +205167,19 @@
 <parameter name="container" type="android.util.SparseArray&lt;android.os.Parcelable&gt;">
 </parameter>
 </method>
+<method name="dispatchSetActivated"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="activated" type="boolean">
+</parameter>
+</method>
 <method name="dispatchSetSelected"
  return="void"
  abstract="false"
@@ -192150,6 +205223,19 @@
 <parameter name="drawingTime" type="long">
 </parameter>
 </method>
+<method name="endViewTransition"
+ 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="focusSearch"
  return="android.view.View"
  abstract="false"
@@ -192360,6 +205446,17 @@
  visibility="public"
 >
 </method>
+<method name="getLayoutTransition"
+ return="android.animation.LayoutTransition"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getPersistentDrawingCache"
  return="int"
  abstract="false"
@@ -192458,6 +205555,17 @@
  visibility="protected"
 >
 </method>
+<method name="isMotionEventSplittingEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="measureChild"
  return="void"
  abstract="false"
@@ -192921,6 +206029,32 @@
 <parameter name="animationListener" type="android.view.animation.Animation.AnimationListener">
 </parameter>
 </method>
+<method name="setLayoutTransition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transition" type="android.animation.LayoutTransition">
+</parameter>
+</method>
+<method name="setMotionEventSplittingEnabled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="split" type="boolean">
+</parameter>
+</method>
 <method name="setOnHierarchyChangeListener"
  return="void"
  abstract="false"
@@ -192973,6 +206107,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"
@@ -192984,6 +206133,19 @@
  visibility="public"
 >
 </method>
+<method name="startViewTransition"
+ 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="updateViewLayout"
  return="void"
  abstract="false"
@@ -193663,6 +206825,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"
@@ -194339,6 +207516,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"
@@ -194350,6 +207540,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"
@@ -194911,6 +208114,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"
@@ -195015,6 +208237,39 @@
  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_BAR_OVERLAY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="9"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FEATURE_ACTION_MODE_OVERLAY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="10"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FEATURE_CONTEXT_MENU"
  type="int"
  transient="false"
@@ -195396,6 +208651,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"
@@ -195885,6 +209153,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"
@@ -197771,6 +211050,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"
@@ -197808,6 +211098,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"
@@ -201781,6 +215088,19 @@
 <parameter name="binding" type="android.view.inputmethod.InputBinding">
 </parameter>
 </method>
+<method name="changeInputMethodSubtype"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="subtype" type="android.view.inputmethod.InputMethodSubtype">
+</parameter>
+</method>
 <method name="createSession"
  return="void"
  abstract="true"
@@ -202104,6 +215424,17 @@
  visibility="public"
 >
 </method>
+<method name="getSubtypes"
+ return="java.util.ArrayList&lt;android.view.inputmethod.InputMethodSubtype&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="loadIcon"
  return="android.graphics.drawable.Drawable"
  abstract="false"
@@ -202376,6 +215707,17 @@
  visibility="public"
 >
 </method>
+<method name="showInputMethodSubtypePicker"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="showSoftInput"
  return="boolean"
  abstract="false"
@@ -202790,6 +216132,108 @@
 </parameter>
 </method>
 </interface>
+<class name="InputMethodSubtype"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getExtraValue"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getIconResId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getLocale"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getModeResId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getNameResId"
+ return="int"
+ 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="parcelableFlags" 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>
 </package>
 <package name="android.webkit"
 >
@@ -205171,6 +218615,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"
@@ -205453,7 +218908,7 @@
  synchronized="true"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -205545,17 +219000,6 @@
  visibility="public"
 >
 </method>
-<method name="getUseWebViewBackgroundForOverscrollBackground"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getUseWideViewPort"
  return="boolean"
  abstract="false"
@@ -205797,6 +219241,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"
@@ -205999,7 +219456,7 @@
  synchronized="true"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="flag" type="boolean">
@@ -206148,19 +219605,6 @@
 <parameter name="use" type="boolean">
 </parameter>
 </method>
-<method name="setUseWebViewBackgroundForOverscrollBackground"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="view" type="boolean">
-</parameter>
-</method>
 <method name="setUseWideViewPort"
  return="void"
  abstract="false"
@@ -206742,6 +220186,22 @@
 <parameter name="defStyle" type="int">
 </parameter>
 </constructor>
+<constructor name="WebView"
+ type="android.webkit.WebView"
+ 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>
+<parameter name="privateBrowsing" type="boolean">
+</parameter>
+</constructor>
 <method name="addJavascriptInterface"
  return="void"
  abstract="false"
@@ -206792,6 +220252,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"
@@ -207151,6 +220633,17 @@
  visibility="public"
 >
 </method>
+<method name="getVisibleTitleHeight"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getZoomControls"
  return="android.view.View"
  abstract="false"
@@ -207208,6 +220701,17 @@
  visibility="public"
 >
 </method>
+<method name="isPrivateBrowsingEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="loadData"
  return="void"
  abstract="false"
@@ -207527,6 +221031,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"
@@ -207676,6 +221210,19 @@
 <parameter name="client" type="android.webkit.WebViewClient">
 </parameter>
 </method>
+<method name="showFindDialog"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.String">
+</parameter>
+</method>
 <method name="stopLoading"
  return="void"
  abstract="false"
@@ -208124,7 +221671,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="view" type="android.webkit.WebView">
@@ -208531,6 +222078,17 @@
 <parameter name="after" type="int">
 </parameter>
 </method>
+<method name="clearChoices"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="clearTextFilter"
  return="void"
  abstract="false"
@@ -208553,6 +222111,61 @@
  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"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCheckedItemPosition"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCheckedItemPositions"
+ return="android.util.SparseBooleanArray"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getChoiceMode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getListPaddingBottom"
  return="int"
  abstract="false"
@@ -208696,6 +222309,19 @@
  visibility="protected"
 >
 </method>
+<method name="isItemChecked"
+ 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="isScrollingCacheEnabled"
  return="boolean"
  abstract="false"
@@ -208775,6 +222401,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"
@@ -208874,6 +222522,19 @@
 <parameter name="views" type="java.util.List&lt;android.view.View&gt;">
 </parameter>
 </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="setCacheColorHint"
  return="void"
  abstract="false"
@@ -208887,6 +222548,19 @@
 <parameter name="color" type="int">
 </parameter>
 </method>
+<method name="setChoiceMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="choiceMode" type="int">
+</parameter>
+</method>
 <method name="setDrawSelectorOnTop"
  return="void"
  abstract="false"
@@ -208926,6 +222600,34 @@
 <parameter name="filterText" type="java.lang.String">
 </parameter>
 </method>
+<method name="setItemChecked"
+ 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="value" 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.AbsListView.MultiChoiceModeListener">
+</parameter>
+</method>
 <method name="setOnScrollListener"
  return="void"
  abstract="false"
@@ -208952,6 +222654,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"
@@ -209101,6 +222816,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"
@@ -209114,6 +222844,50 @@
 <parameter name="dr" type="android.graphics.drawable.Drawable">
 </parameter>
 </method>
+<field name="CHOICE_MODE_MULTIPLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ 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"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CHOICE_MODE_SINGLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="TRANSCRIPT_MODE_ALWAYS_SCROLL"
  type="int"
  transient="false"
@@ -209205,6 +222979,35 @@
 </parameter>
 </constructor>
 </class>
+<interface name="AbsListView.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>
 <interface name="AbsListView.OnScrollListener"
  abstract="true"
  static="true"
@@ -210381,6 +224184,582 @@
 </parameter>
 </method>
 </interface>
+<class name="AdapterViewAnimator"
+ extends="android.widget.AdapterView"
+ abstract="true"
+ 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="getDisplayedChild"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getInAnimation"
+ return="android.animation.ObjectAnimator&lt;?&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getOutAnimation"
+ return="android.animation.ObjectAnimator&lt;?&gt;"
+ 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="onRestoreInstanceState"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="state" type="android.os.Parcelable">
+</parameter>
+</method>
+<method name="onSaveInstanceState"
+ return="android.os.Parcelable"
+ 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.animation.ObjectAnimator&lt;?&gt;">
+</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.animation.ObjectAnimator&lt;?&gt;">
+</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="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"
@@ -212368,6 +226747,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"
@@ -212515,6 +226908,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"
@@ -212586,6 +226996,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"
@@ -215025,7 +229457,7 @@
  visibility="public"
 >
 </method>
-<method name="getStretchMode"
+<method name="getNumColumns"
  return="int"
  abstract="false"
  native="false"
@@ -215036,8 +229468,8 @@
  visibility="public"
 >
 </method>
-<method name="setAdapter"
- return="void"
+<method name="getStretchMode"
+ return="int"
  abstract="false"
  native="false"
  synchronized="false"
@@ -215046,8 +229478,6 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="adapter" type="android.widget.ListAdapter">
-</parameter>
 </method>
 <method name="setColumnWidth"
  return="void"
@@ -215140,6 +229570,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"
@@ -216211,6 +230654,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"
@@ -216255,6 +230712,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"
@@ -216328,6 +230796,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"
@@ -216524,6 +231005,691 @@
 </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="setOnDismissListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.widget.PopupWindow.OnDismissListener">
+</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"
@@ -216628,17 +231794,6 @@
 <parameter name="v" type="android.view.View">
 </parameter>
 </method>
-<method name="clearChoices"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="findViewTraversal"
  return="android.view.View"
  abstract="false"
@@ -216683,51 +231838,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getCheckedItemIds"
- return="long[]"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getCheckedItemPosition"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getCheckedItemPositions"
- return="android.util.SparseBooleanArray"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getChoiceMode"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -216797,41 +231908,6 @@
  visibility="public"
 >
 </method>
-<method name="getOverscrollFooter"
- return="android.graphics.drawable.Drawable"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getOverscrollHeader"
- return="android.graphics.drawable.Drawable"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="isItemChecked"
- 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="removeFooterView"
  return="boolean"
  abstract="false"
@@ -216858,32 +231934,6 @@
 <parameter name="v" type="android.view.View">
 </parameter>
 </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="setChoiceMode"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="choiceMode" type="int">
-</parameter>
-</method>
 <method name="setDivider"
  return="void"
  abstract="false"
@@ -216936,21 +231986,6 @@
 <parameter name="headerDividersEnabled" type="boolean">
 </parameter>
 </method>
-<method name="setItemChecked"
- 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="value" type="boolean">
-</parameter>
-</method>
 <method name="setItemsCanFocus"
  return="void"
  abstract="false"
@@ -216964,32 +231999,6 @@
 <parameter name="itemsCanFocus" type="boolean">
 </parameter>
 </method>
-<method name="setOverscrollFooter"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="footer" type="android.graphics.drawable.Drawable">
-</parameter>
-</method>
-<method name="setOverscrollHeader"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="header" type="android.graphics.drawable.Drawable">
-</parameter>
-</method>
 <method name="setSelection"
  return="void"
  abstract="false"
@@ -217029,39 +232038,19 @@
 <parameter name="y" type="int">
 </parameter>
 </method>
-<field name="CHOICE_MODE_MULTIPLE"
- type="int"
- transient="false"
- volatile="false"
- value="2"
- static="true"
- final="true"
+<method name="smoothScrollByOffset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
  deprecated="not deprecated"
  visibility="public"
 >
-</field>
-<field name="CHOICE_MODE_NONE"
- type="int"
- transient="false"
- volatile="false"
- value="0"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="CHOICE_MODE_SINGLE"
- type="int"
- transient="false"
- volatile="false"
- value="1"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
+<parameter name="offset" type="int">
+</parameter>
+</method>
 </class>
 <class name="ListView.FixedViewInfo"
  extends="java.lang.Object"
@@ -217561,7 +232550,7 @@
 </parameter>
 </method>
 </interface>
-<class name="OverScroller"
+<class name="PopupMenu"
  extends="java.lang.Object"
  abstract="false"
  static="false"
@@ -217569,8 +232558,8 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<constructor name="OverScroller"
- type="android.widget.OverScroller"
+<constructor name="PopupMenu"
+ type="android.widget.PopupMenu"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -217578,36 +232567,10 @@
 >
 <parameter name="context" type="android.content.Context">
 </parameter>
-</constructor>
-<constructor name="OverScroller"
- type="android.widget.OverScroller"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-<parameter name="interpolator" type="android.view.animation.Interpolator">
+<parameter name="anchor" type="android.view.View">
 </parameter>
 </constructor>
-<constructor name="OverScroller"
- type="android.widget.OverScroller"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-<parameter name="interpolator" type="android.view.animation.Interpolator">
-</parameter>
-<parameter name="bounceCoefficientX" type="float">
-</parameter>
-<parameter name="bounceCoefficientY" type="float">
-</parameter>
-</constructor>
-<method name="abortAnimation"
+<method name="dismiss"
  return="void"
  abstract="false"
  native="false"
@@ -217618,8 +232581,8 @@
  visibility="public"
 >
 </method>
-<method name="computeScrollOffset"
- return="boolean"
+<method name="getMenu"
+ return="android.view.Menu"
  abstract="false"
  native="false"
  synchronized="false"
@@ -217629,7 +232592,18 @@
  visibility="public"
 >
 </method>
-<method name="fling"
+<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"
@@ -217639,24 +232613,10 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="startX" type="int">
-</parameter>
-<parameter name="startY" type="int">
-</parameter>
-<parameter name="velocityX" type="int">
-</parameter>
-<parameter name="velocityY" type="int">
-</parameter>
-<parameter name="minX" type="int">
-</parameter>
-<parameter name="maxX" type="int">
-</parameter>
-<parameter name="minY" type="int">
-</parameter>
-<parameter name="maxY" type="int">
+<parameter name="listener" type="android.widget.PopupMenu.OnMenuItemClickListener">
 </parameter>
 </method>
-<method name="fling"
+<method name="show"
  return="void"
  abstract="false"
  native="false"
@@ -217666,226 +232626,29 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="startX" type="int">
-</parameter>
-<parameter name="startY" type="int">
-</parameter>
-<parameter name="velocityX" type="int">
-</parameter>
-<parameter name="velocityY" type="int">
-</parameter>
-<parameter name="minX" type="int">
-</parameter>
-<parameter name="maxX" type="int">
-</parameter>
-<parameter name="minY" type="int">
-</parameter>
-<parameter name="maxY" type="int">
-</parameter>
-<parameter name="overX" type="int">
-</parameter>
-<parameter name="overY" type="int">
-</parameter>
-</method>
-<method name="forceFinished"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="finished" type="boolean">
-</parameter>
-</method>
-<method name="getCurrX"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getCurrY"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getFinalX"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getFinalY"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getStartX"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getStartY"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="isFinished"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="isOverscrolled"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="notifyHorizontalEdgeReached"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="startX" type="int">
-</parameter>
-<parameter name="finalX" type="int">
-</parameter>
-<parameter name="overX" type="int">
-</parameter>
-</method>
-<method name="notifyVerticalEdgeReached"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="startY" type="int">
-</parameter>
-<parameter name="finalY" type="int">
-</parameter>
-<parameter name="overY" type="int">
-</parameter>
-</method>
-<method name="springback"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="startX" type="int">
-</parameter>
-<parameter name="startY" type="int">
-</parameter>
-<parameter name="minX" type="int">
-</parameter>
-<parameter name="maxX" type="int">
-</parameter>
-<parameter name="minY" type="int">
-</parameter>
-<parameter name="maxY" type="int">
-</parameter>
-</method>
-<method name="startScroll"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="startX" type="int">
-</parameter>
-<parameter name="startY" type="int">
-</parameter>
-<parameter name="dx" type="int">
-</parameter>
-<parameter name="dy" type="int">
-</parameter>
-</method>
-<method name="startScroll"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="startX" type="int">
-</parameter>
-<parameter name="startY" type="int">
-</parameter>
-<parameter name="dx" type="int">
-</parameter>
-<parameter name="dy" type="int">
-</parameter>
-<parameter name="duration" type="int">
-</parameter>
 </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"
@@ -217937,6 +232700,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"
@@ -218966,6 +233745,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"
@@ -220102,6 +234892,21 @@
 <parameter name="value" type="double">
 </parameter>
 </method>
+<method name="setEmptyView"
+ 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="emptyViewId" type="int">
+</parameter>
+</method>
 <method name="setFloat"
  return="void"
  abstract="false"
@@ -220181,6 +234986,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"
@@ -220198,6 +235020,36 @@
 <parameter name="value" type="long">
 </parameter>
 </method>
+<method name="setOnClickExtras"
+ 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="extras" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="setOnClickFillInIntent"
+ 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="fillInIntent" type="android.content.Intent">
+</parameter>
+</method>
 <method name="setOnClickPendingIntent"
  return="void"
  abstract="false"
@@ -220213,6 +235065,21 @@
 <parameter name="pendingIntent" type="android.app.PendingIntent">
 </parameter>
 </method>
+<method name="setPendingIntentTemplate"
+ 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="pendingIntentTemplate" type="android.app.PendingIntent">
+</parameter>
+</method>
 <method name="setProgressBar"
  return="void"
  abstract="false"
@@ -220232,6 +235099,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"
@@ -220328,6 +235240,32 @@
 <parameter name="visibility" type="int">
 </parameter>
 </method>
+<method name="showNext"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+</method>
+<method name="showPrevious"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+</method>
 <method name="writeToParcel"
  return="void"
  abstract="false"
@@ -220394,6 +235332,160 @@
 <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="onDataSetChanged"
+ 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"
@@ -220432,6 +235524,22 @@
 <parameter name="autoRequery" type="boolean">
 </parameter>
 </constructor>
+<constructor name="ResourceCursorAdapter"
+ type="android.widget.ResourceCursorAdapter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="layout" type="int">
+</parameter>
+<parameter name="c" type="android.database.Cursor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</constructor>
 <method name="newView"
  return="android.view.View"
  abstract="false"
@@ -221060,6 +236168,277 @@
 >
 </method>
 </class>
+<class name="SearchView"
+ extends="android.widget.LinearLayout"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SearchView"
+ type="android.widget.SearchView"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="SearchView"
+ type="android.widget.SearchView"
+ 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="getSuggestionsAdapter"
+ return="android.widget.CursorAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isIconfiedByDefault"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isIconified"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isQueryRefinementEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isSubmitButtonEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setIconified"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="iconify" type="boolean">
+</parameter>
+</method>
+<method name="setIconifiedByDefault"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="iconified" type="boolean">
+</parameter>
+</method>
+<method name="setOnCloseListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.widget.SearchView.OnCloseListener">
+</parameter>
+</method>
+<method name="setOnQueryChangeListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.widget.SearchView.OnQueryChangeListener">
+</parameter>
+</method>
+<method name="setQuery"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="query" type="java.lang.CharSequence">
+</parameter>
+<parameter name="submit" type="boolean">
+</parameter>
+</method>
+<method name="setQueryHint"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="hint" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="setQueryRefinementEnabled"
+ 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="setSearchableInfo"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="searchable" type="android.app.SearchableInfo">
+</parameter>
+</method>
+<method name="setSubmitButtonEnabled"
+ 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="setSuggestionsAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="adapter" type="android.widget.CursorAdapter">
+</parameter>
+</method>
+</class>
+<interface name="SearchView.OnCloseListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onClose"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</interface>
+<interface name="SearchView.OnQueryChangeListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onQueryTextChanged"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="newText" type="java.lang.String">
+</parameter>
+</method>
+<method name="onSubmitQuery"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="query" type="java.lang.String">
+</parameter>
+</method>
+</interface>
 <interface name="SectionIndexer"
  abstract="true"
  static="false"
@@ -222443,6 +237822,18 @@
 >
 <parameter name="context" type="android.content.Context">
 </parameter>
+<parameter name="mode" type="int">
+</parameter>
+</constructor>
+<constructor name="Spinner"
+ type="android.widget.Spinner"
+ 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>
@@ -222460,6 +237851,22 @@
 <parameter name="defStyle" type="int">
 </parameter>
 </constructor>
+<constructor name="Spinner"
+ type="android.widget.Spinner"
+ 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>
+<parameter name="mode" type="int">
+</parameter>
+</constructor>
 <method name="getPrompt"
  return="java.lang.CharSequence"
  abstract="false"
@@ -222512,6 +237919,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"
@@ -222540,6 +237969,37 @@
 </parameter>
 </method>
 </interface>
+<class name="StackView"
+ extends="android.widget.AdapterViewAnimator"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StackView"
+ type="android.widget.StackView"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="StackView"
+ type="android.widget.StackView"
+ 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>
+</class>
 <class name="TabHost"
  extends="android.widget.FrameLayout"
  abstract="false"
@@ -234178,7 +249638,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="fileName" type="java.lang.String">
+<parameter name="path" type="java.lang.String">
 </parameter>
 <exception name="FileNotFoundException" type="java.io.FileNotFoundException">
 </exception>
@@ -234301,7 +249761,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="filename" type="java.lang.String">
+<parameter name="path" type="java.lang.String">
 </parameter>
 <exception name="FileNotFoundException" type="java.io.FileNotFoundException">
 </exception>
@@ -234313,7 +249773,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="filename" type="java.lang.String">
+<parameter name="path" type="java.lang.String">
 </parameter>
 <parameter name="append" type="boolean">
 </parameter>
@@ -234984,7 +250444,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="b" type="byte[]">
+<parameter name="buffer" type="byte[]">
 </parameter>
 <parameter name="offset" type="int">
 </parameter>
@@ -270061,9 +285521,9 @@
 >
 <parameter name="dst" type="byte[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="dstOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="byteCount" type="int">
 </parameter>
 </method>
 <method name="get"
@@ -270307,9 +285767,9 @@
 >
 <parameter name="src" type="byte[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="srcOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="byteCount" type="int">
 </parameter>
 </method>
 <method name="put"
@@ -270546,7 +286006,7 @@
 </parameter>
 <parameter name="start" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="byteCount" type="int">
 </parameter>
 </method>
 </class>
@@ -270779,9 +286239,9 @@
 >
 <parameter name="dst" type="char[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="dstOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="charCount" type="int">
 </parameter>
 </method>
 <method name="get"
@@ -270879,9 +286339,9 @@
 >
 <parameter name="src" type="char[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="srcOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="charCount" type="int">
 </parameter>
 </method>
 <method name="put"
@@ -271010,7 +286470,7 @@
 </parameter>
 <parameter name="start" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="charCount" type="int">
 </parameter>
 </method>
 <method name="wrap"
@@ -271171,9 +286631,9 @@
 >
 <parameter name="dst" type="double[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="dstOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="doubleCount" type="int">
 </parameter>
 </method>
 <method name="get"
@@ -271260,9 +286720,9 @@
 >
 <parameter name="src" type="double[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="srcOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="doubleCount" type="int">
 </parameter>
 </method>
 <method name="put"
@@ -271331,7 +286791,7 @@
 </parameter>
 <parameter name="start" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="doubleCount" type="int">
 </parameter>
 </method>
 </class>
@@ -271462,9 +286922,9 @@
 >
 <parameter name="dst" type="float[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="dstOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="floatCount" type="int">
 </parameter>
 </method>
 <method name="get"
@@ -271551,9 +287011,9 @@
 >
 <parameter name="src" type="float[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="srcOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="floatCount" type="int">
 </parameter>
 </method>
 <method name="put"
@@ -271622,7 +287082,7 @@
 </parameter>
 <parameter name="start" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="floatCount" type="int">
 </parameter>
 </method>
 </class>
@@ -271753,9 +287213,9 @@
 >
 <parameter name="dst" type="int[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="dstOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="intCount" type="int">
 </parameter>
 </method>
 <method name="get"
@@ -271842,9 +287302,9 @@
 >
 <parameter name="src" type="int[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="srcOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="intCount" type="int">
 </parameter>
 </method>
 <method name="put"
@@ -271913,7 +287373,7 @@
 </parameter>
 <parameter name="start" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="intCount" type="int">
 </parameter>
 </method>
 </class>
@@ -272061,9 +287521,9 @@
 >
 <parameter name="dst" type="long[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="dstOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="longCount" type="int">
 </parameter>
 </method>
 <method name="get"
@@ -272150,9 +287610,9 @@
 >
 <parameter name="src" type="long[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="srcOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="longCount" type="int">
 </parameter>
 </method>
 <method name="put"
@@ -272221,7 +287681,7 @@
 </parameter>
 <parameter name="start" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="longCount" type="int">
 </parameter>
 </method>
 </class>
@@ -272411,9 +287871,9 @@
 >
 <parameter name="dst" type="short[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="dstOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="shortCount" type="int">
 </parameter>
 </method>
 <method name="get"
@@ -272500,9 +287960,9 @@
 >
 <parameter name="src" type="short[]">
 </parameter>
-<parameter name="off" type="int">
+<parameter name="srcOffset" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="shortCount" type="int">
 </parameter>
 </method>
 <method name="put"
@@ -272571,7 +288031,7 @@
 </parameter>
 <parameter name="start" type="int">
 </parameter>
-<parameter name="len" type="int">
+<parameter name="shortCount" type="int">
 </parameter>
 </method>
 </class>
@@ -414388,7 +429848,7 @@
  abstract="true"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <method name="getLength"
@@ -414877,7 +430337,7 @@
  abstract="true"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <method name="characters"
@@ -415092,7 +430552,7 @@
  abstract="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <implements name="org.xml.sax.DTDHandler">
@@ -415559,7 +431019,7 @@
  abstract="true"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <method name="parse"
@@ -416991,7 +432451,7 @@
  abstract="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <implements name="org.xml.sax.AttributeList">
@@ -418480,7 +433940,7 @@
  abstract="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <method name="makeParser"
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index f2aa91f..b073004 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -102,6 +102,8 @@
             sendBroadcast();
         } else if (op.equals("profile")) {
             runProfile();
+        } else if (op.equals("dumpheap")) {
+            runDumpHeap();
         } else if (op.equals("monitor")) {
             runMonitor();
         } else {
@@ -146,6 +148,31 @@
                 String value = nextArgRequired();
                 intent.putExtra(key, Integer.valueOf(value));
                 hasIntentInfo = true;
+            } else if (opt.equals("--eia")) {
+                String key = nextArgRequired();
+                String value = nextArgRequired();
+                String[] strings = value.split(",");
+                int[] list = new int[strings.length];
+                for (int i = 0; i < strings.length; i++) {
+                    list[i] = Integer.valueOf(strings[i]);
+                }
+                intent.putExtra(key, list);
+                hasIntentInfo = true;
+            } else if (opt.equals("--el")) {
+                String key = nextArgRequired();
+                String value = nextArgRequired();
+                intent.putExtra(key, Long.valueOf(value));
+                hasIntentInfo = true;
+            } else if (opt.equals("--ela")) {
+                String key = nextArgRequired();
+                String value = nextArgRequired();
+                String[] strings = value.split(",");
+                long[] list = new long[strings.length];
+                for (int i = 0; i < strings.length; i++) {
+                    list[i] = Long.valueOf(strings[i]);
+                }
+                intent.putExtra(key, list);
+                hasIntentInfo = true;
             } else if (opt.equals("--ez")) {
                 String key = nextArgRequired();
                 String value = nextArgRequired();
@@ -430,6 +457,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);
+        }
+    }
+
     class MyActivityController extends IActivityController.Stub {
         final String mGdbPort;
 
@@ -894,8 +943,15 @@
                 "        -p <FILE>: write profiling data to <FILE>\n" +
                 "        -w: wait for instrumentation to finish before returning\n" +
                 "\n" +
+                "    run a test package against an application: am instrument [flags] <TEST_PACKAGE>/<RUNNER_CLASS>\n" +
+                "        -e <testrunner_flag> <testrunner_value> [,<testrunner_value>]\n" +
+                "        -w wait for the test to finish (required)\n" +
+                "        -r use with -e perf true to generate raw output for performance measurements\n" +
+                "\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" +
                 "    start monitoring: am monitor [--gdb <port>]\n" +
                 "        --gdb: start gdbserv on the given port at crash/ANR\n" +
@@ -906,7 +962,10 @@
                 "        [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
                 "        [--esn <EXTRA_KEY> ...]\n" +
                 "        [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
-                "        [-e|--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
+                "        [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
+                "        [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
+                "        [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
+                "        [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
                 "        [-n <COMPONENT>] [-f <FLAGS>]\n" +
                 "        [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
                 "        [--debug-log-resolution]\n" +
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 822f62d..67bd9f7 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -39,6 +39,8 @@
 static char cmdline_buf[16384] = "(unknown)";
 static const char *dump_traces_path = NULL;
 
+static char screenshot_path[PATH_MAX] = "";
+
 /* dumps the current system state to stdout */
 static void dumpstate() {
     time_t now = time(NULL);
@@ -76,6 +78,12 @@
     dump_file("SLAB INFO", "/proc/slabinfo");
     dump_file("ZONEINFO", "/proc/zoneinfo");
 
+    if (screenshot_path[0]) {
+        LOGI("taking screenshot\n");
+        run_command(NULL, 5, "su", "root", "screenshot", screenshot_path, NULL);
+        LOGI("wrote screenshot: %s\n", screenshot_path);
+    }
+
     run_command("SYSTEM LOG", 20, "logcat", "-v", "time", "-d", "*:v", NULL);
 
     /* show the traces we collected in main(), if that was done */
@@ -103,7 +111,12 @@
     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 STATUS", 20,
+            "su", "root", "dhdutil", "-i", "eth0", "dump", NULL);
     run_command("DUMP WIFI FIRMWARE LOG", 60,
             "su", "root", "dhdutil", "-i", "eth0", "upload", "/data/local/tmp/wlan_crash.dump", NULL);
 #endif
@@ -164,18 +177,25 @@
 }
 
 static void usage() {
-    fprintf(stderr, "usage: dumpstate [-d] [-o file] [-s] [-z]\n"
-            "  -d: append date to filename (requires -o)\n"
+    fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s]\n"
             "  -o: write to file (instead of stdout)\n"
+            "  -d: append date to filename (requires -o)\n"
+            "  -z: gzip output (requires -o)\n"
+            "  -p: capture screenshot to filename.png (requires -o)\n"
             "  -s: write output to control socket (for init)\n"
-            "  -z: gzip output (requires -o)\n");
+            "  -b: play sound file instead of vibrate, at beginning of job\n"
+            "  -e: play sound file instead of vibrate, at end of job\n"
+		);
 }
 
 int main(int argc, char *argv[]) {
     int do_add_date = 0;
     int do_compress = 0;
     char* use_outfile = 0;
+    char* begin_sound = 0;
+    char* end_sound = 0;
     int use_socket = 0;
+    int do_fb = 0;
 
     LOGI("begin\n");
 
@@ -191,13 +211,16 @@
     dump_traces_path = dump_vm_traces();
 
     int c;
-    while ((c = getopt(argc, argv, "dho:svz")) != -1) {
+    while ((c = getopt(argc, argv, "b:de:ho:svzp")) != -1) {
         switch (c) {
+            case 'b': begin_sound = optarg;  break;
             case 'd': do_add_date = 1;       break;
+            case 'e': end_sound = optarg;    break;
             case 'o': use_outfile = optarg;  break;
             case 's': use_socket = 1;        break;
             case 'v': break;  // compatibility no-op
             case 'z': do_compress = 6;       break;
+            case 'p': do_fb = 1;             break;
             case '?': printf("\n");
             case 'h':
                 usage();
@@ -244,6 +267,10 @@
             strftime(date, sizeof(date), "-%Y-%m-%d-%H-%M-%S", localtime(&now));
             strlcat(path, date, sizeof(path));
         }
+        if (do_fb) {
+            strlcpy(screenshot_path, path, sizeof(screenshot_path));
+            strlcat(screenshot_path, ".png", sizeof(screenshot_path));
+        }
         strlcat(path, ".txt", sizeof(path));
         if (do_compress) strlcat(path, ".gz", sizeof(path));
         strlcpy(tmp_path, path, sizeof(tmp_path));
@@ -251,16 +278,18 @@
         gzip_pid = redirect_to_file(stdout, tmp_path, do_compress);
     }
 
-    /* bzzzzzz */
-    if (vibrator) {
+    if (begin_sound) {
+        play_sound(begin_sound);
+    } else if (vibrator) {
         fputs("150", vibrator);
         fflush(vibrator);
     }
 
     dumpstate();
 
-    /* bzzz bzzz bzzz */
-    if (vibrator) {
+    if (end_sound) {
+        play_sound(end_sound);
+    } else if (vibrator) {
         int i;
         for (i = 0; i < 3; i++) {
             fputs("75\n", vibrator);
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 682eafd..83b1d11 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -44,4 +44,7 @@
 /* Displays a blocked processes in-kernel wait channel */
 void show_wchan(int pid, const char *name);
 
+/* Play a sound via Stagefright */
+void play_sound(const char* path);
+
 #endif /* _DUMPSTATE_H_ */
diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c
index c7a78cc..f92acbbb 100644
--- a/cmds/dumpstate/utils.c
+++ b/cmds/dumpstate/utils.c
@@ -429,3 +429,7 @@
     rename(anr_traces_path, traces_path);
     return dump_traces_path;
 }
+
+void play_sound(const char* path) {
+    run_command(NULL, 5, "/system/bin/stagefright", "-o", "-a", path, NULL);
+}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 9b8b0ac..040421a 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -233,7 +233,7 @@
             for (int i=0; i<rawList.length; i++) {
                 list.add(rawList[i]);
             }
-                    
+
 
             // Sort by name
             Collections.sort(list, new Comparator<FeatureInfo>() {
@@ -909,6 +909,9 @@
         System.err.println("The list instrumentation command prints all instrumentations,");
         System.err.println("or only those that target a specified package.  Options:");
         System.err.println("  -f: see their associated file.");
+        System.err.println("(Use this command to list all test packages, or use <TARGET-PACKAGE> ");
+        System.err.println(" to list the test packages for a particular application. The -f ");
+        System.err.println(" option lists the .apk file for the test package.)");
         System.err.println("");
         System.err.println("The list features command prints all features of the system.");
         System.err.println("");
diff --git a/cmds/screenshot/Android.mk b/cmds/screenshot/Android.mk
new file mode 100644
index 0000000..99c7aeb
--- /dev/null
+++ b/cmds/screenshot/Android.mk
@@ -0,0 +1,16 @@
+ifneq ($(TARGET_SIMULATOR),true)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := screenshot.c
+
+LOCAL_MODULE := screenshot
+
+LOCAL_SHARED_LIBRARIES := libcutils libz
+LOCAL_STATIC_LIBRARIES := libpng
+LOCAL_C_INCLUDES += external/zlib
+
+include $(BUILD_EXECUTABLE)
+
+endif
diff --git a/cmds/screenshot/screenshot.c b/cmds/screenshot/screenshot.c
new file mode 100644
index 0000000..048636c
--- /dev/null
+++ b/cmds/screenshot/screenshot.c
@@ -0,0 +1,171 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <linux/fb.h>
+
+#include <zlib.h>
+#include <libpng/png.h>
+
+#include "private/android_filesystem_config.h"
+
+#define LOG_TAG "screenshot"
+#include <utils/Log.h>
+
+void take_screenshot(FILE *fb_in, FILE *fb_out) {
+    int fb;
+    char imgbuf[0x10000];
+    struct fb_var_screeninfo vinfo;
+    png_structp png;
+    png_infop info;
+    unsigned int r,c,rowlen;
+    unsigned int bytespp,offset;
+
+    fb = fileno(fb_in);
+    if(fb < 0) {
+        LOGE("failed to open framebuffer\n");
+        return;
+    }
+    fb_in = fdopen(fb, "r");
+
+    if(ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) < 0) {
+        LOGE("failed to get framebuffer info\n");
+        return;
+    }
+    fcntl(fb, F_SETFD, FD_CLOEXEC);
+
+    png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (png == NULL) {
+        LOGE("failed png_create_write_struct\n");
+        fclose(fb_in);
+        return;
+    }
+
+    png_init_io(png, fb_out);
+    info = png_create_info_struct(png);
+    if (info == NULL) {
+        LOGE("failed png_create_info_struct\n");
+        png_destroy_write_struct(&png, NULL);
+        fclose(fb_in);
+        return;
+    }
+    if (setjmp(png_jmpbuf(png))) {
+        LOGE("failed png setjmp\n");
+        png_destroy_write_struct(&png, NULL);
+        fclose(fb_in);
+        return;
+    }
+
+    bytespp = vinfo.bits_per_pixel / 8;
+    png_set_IHDR(png, info,
+        vinfo.xres, vinfo.yres, vinfo.bits_per_pixel / 4, 
+        PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
+        PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+    png_write_info(png, info);
+
+    rowlen=vinfo.xres * bytespp;
+    if (rowlen > sizeof(imgbuf)) {
+        LOGE("crazy rowlen: %d\n", rowlen);
+        png_destroy_write_struct(&png, NULL);
+        fclose(fb_in);
+        return;
+    }
+
+    offset = vinfo.xoffset * bytespp + vinfo.xres * vinfo.yoffset * bytespp;
+    fseek(fb_in, offset, SEEK_SET);
+
+    for(r=0; r<vinfo.yres; r++) {
+        int len = fread(imgbuf, 1, rowlen, fb_in);
+        if (len <= 0) break;
+        png_write_row(png, (png_bytep)imgbuf);
+    }
+
+    png_write_end(png, info);
+    fclose(fb_in);
+    png_destroy_write_struct(&png, NULL);
+}
+
+void fork_sound(const char* path) {
+    pid_t pid = fork();
+    if (pid == 0) {
+        execl("/system/bin/stagefright", "stagefright", "-o", "-a", path, NULL);
+    }
+}
+
+void usage() {
+    fprintf(stderr,
+            "usage: screenshot [-s soundfile] filename.png\n"
+            "   -s: play a sound effect to signal success\n"
+            "   -i: autoincrement to avoid overwriting filename.png\n"
+    );
+}
+
+int main(int argc, char**argv) {
+    FILE *png = NULL;
+    FILE *fb_in = NULL;
+    char outfile[PATH_MAX] = "";
+
+    char * soundfile = NULL;
+    int do_increment = 0;
+
+    int c;
+    while ((c = getopt(argc, argv, "s:i")) != -1) {
+        switch (c) {
+            case 's': soundfile = optarg; break;
+            case 'i': do_increment = 1; break;
+            case '?':
+            case 'h':
+                usage(); exit(1);
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc < 1) {
+        usage(); exit(1);
+    }
+
+    strlcpy(outfile, argv[0], PATH_MAX);
+    if (do_increment) {
+        struct stat st;
+        char base[PATH_MAX] = "";
+        int i = 0;
+        while (stat(outfile, &st) == 0) {
+            if (!base[0]) {
+                char *p = strrchr(outfile, '.');
+                if (p) *p = '\0';
+                strcpy(base, outfile);
+            }
+            snprintf(outfile, PATH_MAX, "%s-%d.png", base, ++i);
+        }
+    }
+
+    fb_in = fopen("/dev/graphics/fb0", "r");
+    if (!fb_in) {
+        fprintf(stderr, "error: could not read framebuffer\n");
+        exit(1);
+    }
+
+    /* switch to non-root user and group */
+    gid_t groups[] = { AID_LOG, AID_SDCARD_RW };
+    setgroups(sizeof(groups)/sizeof(groups[0]), groups);
+    setuid(AID_SHELL);
+
+    png = fopen(outfile, "w");
+    if (!png) {
+        fprintf(stderr, "error: writing file %s: %s\n",
+                outfile, strerror(errno));
+        exit(1);
+    }
+
+    take_screenshot(fb_in, png);
+
+    if (soundfile) {
+        fork_sound(soundfile);
+    }
+
+    exit(0);
+}
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk
index 9a97284..cbdf119 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -53,6 +53,31 @@
 
 LOCAL_SRC_FILES:=         \
         SineSource.cpp    \
+        recordvideo.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libstagefright liblog libutils libbinder
+
+LOCAL_C_INCLUDES:= \
+	$(JNI_H_INCLUDE) \
+	frameworks/base/media/libstagefright \
+	$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
+
+LOCAL_CFLAGS += -Wno-multichar
+
+LOCAL_MODULE_TAGS := debug
+
+LOCAL_MODULE:= recordvideo
+
+include $(BUILD_EXECUTABLE)
+
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=         \
+        SineSource.cpp    \
         audioloop.cpp
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/cmds/stagefright/recordvideo.cpp b/cmds/stagefright/recordvideo.cpp
new file mode 100644
index 0000000..330fbc2
--- /dev/null
+++ b/cmds/stagefright/recordvideo.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SineSource.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/AudioPlayer.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MPEG4Writer.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/MediaPlayerInterface.h>
+
+using namespace android;
+
+// print usage showing how to use this utility to record videos
+static void usage(const char *me) {
+    fprintf(stderr, "usage: %s\n", me);
+    fprintf(stderr, "       -h(elp)\n");
+    fprintf(stderr, "       -b bit rate in bits per second (default 300000)\n");
+    fprintf(stderr, "       -c YUV420 color format: [0] semi planar or [1] planar (default 1)\n");
+    fprintf(stderr, "       -f frame rate in frames per second (default 30)\n");
+    fprintf(stderr, "       -i I frame interval in seconds (default 1)\n");
+    fprintf(stderr, "       -n number of frames to be recorded (default 300)\n");
+    fprintf(stderr, "       -w width in pixels (default 176)\n");
+    fprintf(stderr, "       -t height in pixels (default 144)\n");
+    fprintf(stderr, "       -v video codec: [0] AVC [1] M4V [2] H263 (default 0)\n");
+    exit(1);
+}
+
+class DummySource : public MediaSource {
+
+public:
+    DummySource(int width, int height, int nFrames, int fps, int colorFormat)
+        : mWidth(width),
+          mHeight(height),
+          mMaxNumFrames(nFrames),
+          mFrameRate(fps),
+          mColorFormat(colorFormat),
+          mSize((width * height * 3) / 2) {
+        mGroup.add_buffer(new MediaBuffer(mSize));
+
+        // Check the color format to make sure
+        // that the buffer size mSize it set correctly above.
+        CHECK(colorFormat == OMX_COLOR_FormatYUV420SemiPlanar ||
+              colorFormat == OMX_COLOR_FormatYUV420Planar);
+    }
+
+    virtual sp<MetaData> getFormat() {
+        sp<MetaData> meta = new MetaData;
+        meta->setInt32(kKeyWidth, mWidth);
+        meta->setInt32(kKeyHeight, mHeight);
+        meta->setInt32(kKeyColorFormat, mColorFormat);
+        meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
+
+        return meta;
+    }
+
+    virtual status_t start(MetaData *params) {
+        mNumFramesOutput = 0;
+        return OK;
+    }
+
+    virtual status_t stop() {
+        return OK;
+    }
+
+    virtual status_t read(
+            MediaBuffer **buffer, const MediaSource::ReadOptions *options) {
+
+        if (mNumFramesOutput % 10 == 0) {
+            fprintf(stderr, ".");
+        }
+        if (mNumFramesOutput == mMaxNumFrames) {
+            return ERROR_END_OF_STREAM;
+        }
+
+        status_t err = mGroup.acquire_buffer(buffer);
+        if (err != OK) {
+            return err;
+        }
+
+        char x = (char)((double)rand() / RAND_MAX * 255);
+        memset((*buffer)->data(), x, mSize);
+        (*buffer)->set_range(0, mSize);
+        (*buffer)->meta_data()->clear();
+        (*buffer)->meta_data()->setInt64(
+                kKeyTime, (mNumFramesOutput * 1000000) / mFrameRate);
+        ++mNumFramesOutput;
+
+        return OK;
+    }
+
+protected:
+    virtual ~DummySource() {}
+
+private:
+    MediaBufferGroup mGroup;
+    int mWidth, mHeight;
+    int mMaxNumFrames;
+    int mFrameRate;
+    int mColorFormat;
+    size_t mSize;
+    int64_t mNumFramesOutput;;
+
+    DummySource(const DummySource &);
+    DummySource &operator=(const DummySource &);
+};
+
+sp<MediaSource> createSource(const char *filename) {
+    sp<MediaSource> source;
+
+    sp<MediaExtractor> extractor =
+        MediaExtractor::Create(new FileSource(filename));
+    if (extractor == NULL) {
+        return NULL;
+    }
+
+    size_t num_tracks = extractor->countTracks();
+
+    sp<MetaData> meta;
+    for (size_t i = 0; i < num_tracks; ++i) {
+        meta = extractor->getTrackMetaData(i);
+        CHECK(meta.get() != NULL);
+
+        const char *mime;
+        if (!meta->findCString(kKeyMIMEType, &mime)) {
+            continue;
+        }
+
+        if (strncasecmp(mime, "video/", 6)) {
+            continue;
+        }
+
+        source = extractor->getTrack(i);
+        break;
+    }
+
+    return source;
+}
+
+enum {
+    kYUV420SP = 0,
+    kYUV420P  = 1,
+};
+
+// returns -1 if mapping of the given color is unsuccessful
+// returns an omx color enum value otherwise
+static int translateColorToOmxEnumValue(int color) {
+    switch (color) {
+        case kYUV420SP:
+            return OMX_COLOR_FormatYUV420SemiPlanar;
+        case kYUV420P:
+            return OMX_COLOR_FormatYUV420Planar;
+        default:
+            fprintf(stderr, "Unsupported color: %d\n", color);
+            return -1;
+    }
+}
+
+int main(int argc, char **argv) {
+
+    // Default values for the program if not overwritten
+    int frameRateFps = 30;
+    int width = 176;
+    int height = 144;
+    int bitRateBps = 300000;
+    int iFramesIntervalSeconds = 1;
+    int colorFormat = OMX_COLOR_FormatYUV420Planar;
+    int nFrames = 300;
+    int codec = 0;
+    const char *fileName = "/sdcard/output.mp4";
+
+    android::ProcessState::self()->startThreadPool();
+    int res;
+    while ((res = getopt(argc, argv, "b:c:f:i:n:w:t:v:o:h")) >= 0) {
+        switch (res) {
+            case 'b':
+            {
+                bitRateBps = atoi(optarg);
+                break;
+            }
+
+            case 'c':
+            {
+                colorFormat = translateColorToOmxEnumValue(atoi(optarg));
+                if (colorFormat == -1) {
+                    usage(argv[0]);
+                }
+                break;
+            }
+
+            case 'f':
+            {
+                frameRateFps = atoi(optarg);
+                break;
+            }
+
+            case 'i':
+            {
+                iFramesIntervalSeconds = atoi(optarg);
+                break;
+            }
+
+            case 'n':
+            {
+                nFrames = atoi(optarg);
+                break;
+            }
+
+            case 'w':
+            {
+                width = atoi(optarg);
+                break;
+            }
+
+            case 't':
+            {
+                height = atoi(optarg);
+                break;
+            }
+
+            case 'v':
+            {
+                codec = atoi(optarg);
+                if (codec < 0 || codec > 2) {
+                    usage(argv[0]);
+                }
+                break;
+            }
+
+            case 'h':
+            default:
+            {
+                usage(argv[0]);
+                break;
+            }
+        }
+    }
+
+    OMXClient client;
+    CHECK_EQ(client.connect(), OK);
+
+    status_t err = OK;
+    sp<MediaSource> decoder = new DummySource(width, height, nFrames, frameRateFps, colorFormat);
+
+    sp<MetaData> enc_meta = new MetaData;
+    switch (codec) {
+        case 1:
+            enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
+            break;
+        case 2:
+            enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
+            break;
+        default:
+            enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+            break;
+    }
+    enc_meta->setInt32(kKeyWidth, width);
+    enc_meta->setInt32(kKeyHeight, height);
+    enc_meta->setInt32(kKeySampleRate, frameRateFps);
+    enc_meta->setInt32(kKeyBitRate, bitRateBps);
+    enc_meta->setInt32(kKeyStride, width);
+    enc_meta->setInt32(kKeySliceHeight, height);
+    enc_meta->setInt32(kKeyIFramesInterval, iFramesIntervalSeconds);
+    enc_meta->setInt32(kKeyColorFormat, colorFormat);
+
+    sp<MediaSource> encoder =
+        OMXCodec::Create(
+                client.interface(), enc_meta, true /* createEncoder */, decoder);
+
+    sp<MPEG4Writer> writer = new MPEG4Writer(fileName);
+    writer->addSource(encoder);
+    int64_t start = systemTime();
+    CHECK_EQ(OK, writer->start());
+    while (!writer->reachedEOS()) {
+    }
+    err = writer->stop();
+    int64_t end = systemTime();
+
+    printf("$\n");
+    client.disconnect();
+
+    if (err != OK && err != ERROR_END_OF_STREAM) {
+        fprintf(stderr, "record failed: %d\n", err);
+        return 1;
+    }
+    fprintf(stderr, "encoding %d frames in %lld us\n", nFrames, (end-start)/1000);
+    fprintf(stderr, "encoding speed is: %.2f fps\n", (nFrames * 1E9) / (end-start));
+    return 0;
+}
diff --git a/core/java/android/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/Animator.java b/core/java/android/animation/Animator.java
new file mode 100644
index 0000000..2ada6d6
--- /dev/null
+++ b/core/java/android/animation/Animator.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+import java.util.ArrayList;
+
+/**
+ * This is the superclass for classes which provide basic support for animations which can be
+ * started, ended, and have <code>AnimatorListeners</code> added to them.
+ */
+public abstract class Animator implements Cloneable {
+
+
+    /**
+     * The set of listeners to be sent events through the life of an animation.
+     */
+    ArrayList<AnimatorListener> 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 android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
+     * its listeners, followed by an {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} 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 android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
+     * its listeners.
+     */
+    public void end() {
+    }
+
+    /**
+     * 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 abstract long getStartDelay();
+
+    /**
+     * 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 abstract void setStartDelay(long startDelay);
+
+
+    /**
+     * Sets the length of the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     */
+    public abstract void setDuration(long duration);
+
+    /**
+     * Gets the length of the animation.
+     *
+     * @return The length of the animation, in milliseconds.
+     */
+    public abstract long getDuration();
+
+    /**
+     * 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 abstract void setInterpolator(Interpolator value);
+
+    /**
+     * Returns whether this Animator is currently running (having been started and not yet ended).
+     * @return Whether the Animator is running.
+     */
+    public abstract boolean isRunning();
+
+    /**
+     * 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(AnimatorListener listener) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<AnimatorListener>();
+        }
+        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(AnimatorListener listener) {
+        if (mListeners == null) {
+            return;
+        }
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            mListeners = null;
+        }
+    }
+
+    /**
+     * Gets the set of {@link android.animation.Animator.AnimatorListener} objects that are currently
+     * listening for events on this <code>Animator</code> object.
+     *
+     * @return ArrayList<AnimatorListener> The set of listeners.
+     */
+    public ArrayList<AnimatorListener> 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;
+        }
+    }
+
+    @Override
+    public Animator clone() {
+        try {
+            final Animator anim = (Animator) super.clone();
+            if (mListeners != null) {
+                ArrayList<AnimatorListener> oldListeners = mListeners;
+                anim.mListeners = new ArrayList<AnimatorListener>();
+                int numListeners = oldListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    anim.mListeners.add(oldListeners.get(i));
+                }
+            }
+            return anim;
+        } catch (CloneNotSupportedException e) {
+           throw new AssertionError();
+        }
+    }
+
+    /**
+     * This method tells the object to use appropriate information to extract
+     * starting values for the animation. For example, a AnimatorSet object will pass
+     * this call to its child objects to tell them to set up the values. A
+     * ObjectAnimator object will use the information it has about its target object
+     * and PropertyValuesHolder objects to get the start values for its properties.
+     * An ValueAnimator object will ignore the request since it does not have enough
+     * information (such as a target object) to gather these values.
+     */
+    public void setupStartValues() {
+    }
+
+    /**
+     * This method tells the object to use appropriate information to extract
+     * ending values for the animation. For example, a AnimatorSet object will pass
+     * this call to its child objects to tell them to set up the values. A
+     * ObjectAnimator object will use the information it has about its target object
+     * and PropertyValuesHolder objects to get the start values for its properties.
+     * An ValueAnimator object will ignore the request since it does not have enough
+     * information (such as a target object) to gather these values.
+     */
+    public void setupEndValues() {
+    }
+
+    /**
+     * Sets the target object whose property will be animated by this animation. Not all subclasses
+     * operate on target objects (for example, {@link ValueAnimator}, but this method
+     * is on the superclass for the convenience of dealing generically with those subclasses
+     * that do handle targets.
+     *
+     * @param target The object being animated
+     */
+    public void setTarget(Object target) {
+    }
+
+    /**
+     * <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 AnimatorListener {
+        /**
+         * <p>Notifies the start of the animation.</p>
+         *
+         * @param animation The started animation.
+         */
+        void onAnimationStart(Animator 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(Animator 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(Animator animation);
+
+        /**
+         * <p>Notifies the repetition of the animation.</p>
+         *
+         * @param animation The animation which was repeated.
+         */
+        void onAnimationRepeat(Animator animation);
+    }
+}
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
new file mode 100644
index 0000000..0016459
--- /dev/null
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.content.res.Resources.NotFoundException;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.animation.AnimationUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This class is used to instantiate menu XML files into Animator objects.
+ * <p>
+ * For performance reasons, menu inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use MenuInflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource (R.
+ * <em>something</em> file.)
+ */
+public class AnimatorInflater {
+
+    /**
+     * These flags are used when parsing AnimatorSet objects
+     */
+    private static final int TOGETHER = 0;
+    private static final int SEQUENTIALLY = 1;
+
+    /**
+     * 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;
+
+    /**
+     * Loads an {@link Animator} object from a resource
+     *
+     * @param context Application context used to access resources
+     * @param id The resource id of the animation to load
+     * @return The animator object reference by the specified id
+     * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
+     */
+    public static Animator loadAnimator(Context context, int id)
+            throws NotFoundException {
+
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createAnimatorFromXml(context, parser);
+        } catch (XmlPullParserException ex) {
+            Resources.NotFoundException rnf =
+                    new Resources.NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            Resources.NotFoundException rnf =
+                    new Resources.NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+
+        return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
+    }
+
+    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser,
+            AttributeSet attrs, AnimatorSet parent, int sequenceOrdering)
+            throws XmlPullParserException, IOException {
+
+        Animator anim = null;
+        ArrayList<Animator> 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("objectAnimator")) {
+                anim = loadObjectAnimator(c, attrs);
+            } else if (name.equals("animator")) {
+                anim = loadAnimator(c, attrs, null);
+            } else if (name.equals("set")) {
+                anim = new AnimatorSet();
+                TypedArray a = c.obtainStyledAttributes(attrs,
+                        com.android.internal.R.styleable.AnimatorSet);
+                int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering,
+                        TOGETHER);
+                createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim,  ordering);
+                a.recycle();
+            } else {
+                throw new RuntimeException("Unknown animator name: " + parser.getName());
+            }
+
+            if (parent != null) {
+                if (childAnims == null) {
+                    childAnims = new ArrayList<Animator>();
+                }
+                childAnims.add(anim);
+            }
+        }
+        if (parent != null && childAnims != null) {
+            Animator[] animsArray = new Animator[childAnims.size()];
+            int index = 0;
+            for (Animator a : childAnims) {
+                animsArray[index++] = a;
+            }
+            if (sequenceOrdering == TOGETHER) {
+                parent.playTogether(animsArray);
+            } else {
+                parent.playSequentially(animsArray);
+            }
+        }
+
+        return anim;
+
+    }
+
+    private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs)
+            throws NotFoundException {
+
+        ObjectAnimator anim = new ObjectAnimator();
+
+        loadAnimator(context, attrs, anim);
+
+        TypedArray a =
+                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.PropertyAnimator);
+
+        String propertyName = a.getString(com.android.internal.R.styleable.PropertyAnimator_propertyName);
+
+        anim.setPropertyName(propertyName);
+
+        a.recycle();
+
+        return anim;
+    }
+
+    /**
+     * 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
+     */
+    private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim)
+            throws NotFoundException {
+
+        TypedArray a =
+                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator);
+
+        long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 0);
+
+        long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
+
+        int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType,
+                VALUE_TYPE_FLOAT);
+
+        Object valueFrom = null;
+        Object valueTo = null;
+        TypeEvaluator evaluator = null;
+
+        switch (valueType) {
+            case VALUE_TYPE_FLOAT:
+                if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) {
+                    valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
+                }
+                if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) {
+                    valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
+                }
+                break;
+            case VALUE_TYPE_COLOR:
+                evaluator = new RGBEvaluator();
+                // fall through to pick up values
+            case VALUE_TYPE_INT:
+                if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) {
+                    valueFrom = a.getInteger(com.android.internal.R.styleable.Animator_valueFrom, 0);
+                }
+                if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) {
+                    valueTo = a.getInteger(com.android.internal.R.styleable.Animator_valueTo, 0);
+                }
+                break;
+            case VALUE_TYPE_DOUBLE:
+                if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) {
+                    valueFrom = (Double)((Float)(a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f))).doubleValue();
+                }
+                if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) {
+                    valueTo = (Double)((Float)a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f)).doubleValue();
+                }
+                break;
+            case VALUE_TYPE_CUSTOM:
+                // TODO: How to get an 'Object' value?
+                if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) {
+                    valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
+                }
+                if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) {
+                    valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
+                }
+                break;
+        }
+
+        if (anim == null) {
+            anim = new ValueAnimator(duration, valueFrom, valueTo);
+        } else {
+            anim.setDuration(duration);
+            anim.setValues(valueFrom, valueTo);
+        }
+
+        anim.setStartDelay(startDelay);
+
+        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) {
+            anim.setRepeatCount(
+                    a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0));
+        }
+        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) {
+            anim.setRepeatMode(
+                    a.getInt(com.android.internal.R.styleable.Animator_repeatMode,
+                            ValueAnimator.RESTART));
+        }
+        if (evaluator != null) {
+            anim.setEvaluator(evaluator);
+        }
+
+        final int resID =
+                a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
+        if (resID > 0) {
+            anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+        }
+        a.recycle();
+
+        return anim;
+    }
+}
diff --git a/core/java/android/animation/AnimatorListenerAdapter.java b/core/java/android/animation/AnimatorListenerAdapter.java
new file mode 100644
index 0000000..e5d70a4
--- /dev/null
+++ b/core/java/android/animation/AnimatorListenerAdapter.java
@@ -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.
+ */
+
+package android.animation;
+
+/**
+ * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}.
+ * 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 AnimatorListenerAdapter implements Animator.AnimatorListener {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationCancel(Animator animation) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationEnd(Animator animation) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationStart(Animator animation) {
+    }
+
+}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
new file mode 100644
index 0000000..a8385e4
--- /dev/null
+++ b/core/java/android/animation/AnimatorSet.java
@@ -0,0 +1,939 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This class plays a set of {@link Animator} 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>AnimatorSet</code>:
+ * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or
+ * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add
+ * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be
+ * used in conjunction with methods in the {@link AnimatorSet.Builder Builder}
+ * class to add animations
+ * one by one.</p>
+ *
+ * <p>It is possible to set up a <code>AnimatorSet</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 AnimatorSet extends Animator {
+
+    /**
+     * Internal variables
+     * NOTE: This object implements the clone() method, making a deep copy of any referenced
+     * objects. As other non-trivial fields are added to this class, make sure to add logic
+     * to clone() to make deep copies of them.
+     */
+
+    /**
+     * Tracks animations currently being played, so that we know what to
+     * cancel or end when cancel() or end() is called on this AnimatorSet
+     */
+    private ArrayList<Animator> mPlayingSet = new ArrayList<Animator>();
+
+    /**
+     * Contains all nodes, mapped to their respective Animators. When new
+     * dependency information is added for an Animator, we want to add it
+     * to a single node representing that Animator, not create a new Node
+     * if one already exists.
+     */
+    private HashMap<Animator, Node> mNodeMap = new HashMap<Animator, Node>();
+
+    /**
+     * Set of all nodes created for this AnimatorSet. This list is used upon
+     * starting the set, and the nodes are placed in sorted order into the
+     * sortedNodes collection.
+     */
+    private 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 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 AnimatorSetListener mSetListener = null;
+
+    /**
+     * Flag indicating that the AnimatorSet has been canceled (by calling cancel() or end()).
+     * This flag is used to avoid starting other animations when currently-playing
+     * child animations of this AnimatorSet end.
+     */
+    boolean mCanceled = false;
+
+    // The amount of time in ms to delay starting the animation after start() is called
+    private long mStartDelay = 0;
+
+
+    // How long the child animations should last in ms. The default value is negative, which
+    // simply means that there is no duration set on the AnimatorSet. When a real duration is
+    // set, it is passed along to the child animations.
+    private long mDuration = -1;
+
+
+    /**
+     * Sets up this AnimatorSet to play all of the supplied animations at the same time.
+     *
+     * @param items The animations that will be started simultaneously.
+     */
+    public void playTogether(Animator... items) {
+        if (items != null) {
+            mNeedsSort = true;
+            Builder builder = play(items[0]);
+            for (int i = 1; i < items.length; ++i) {
+                builder.with(items[i]);
+            }
+        }
+    }
+
+    /**
+     * Sets up this AnimatorSet to play each of the supplied animations when the
+     * previous animation ends.
+     *
+     * @param items The aniamtions that will be started one after another.
+     */
+    public void playSequentially(Animator... items) {
+        if (items != null) {
+            mNeedsSort = true;
+            if (items.length == 1) {
+                play(items[0]);
+            } else {
+                for (int i = 0; i < items.length - 1; ++i) {
+                    play(items[i]).before(items[i+1]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the current list of child Animator objects controlled by this
+     * AnimatorSet. This is a copy of the internal list; modifications to the returned list
+     * will not affect the AnimatorSet, although changes to the underlying Animator objects
+     * will affect those objects being managed by the AnimatorSet.
+     *
+     * @return ArrayList<Animator> The list of child animations of this AnimatorSet.
+     */
+    public ArrayList<Animator> getChildAnimations() {
+        ArrayList<Animator> childList = new ArrayList<Animator>();
+        for (Node node : mNodes) {
+            childList.add(node.animation);
+        }
+        return childList;
+    }
+
+    /**
+     * Sets the target object for all current {@link #getChildAnimations() child animations}
+     * of this AnimatorSet that take targets ({@link ObjectAnimator} and
+     * AnimatorSet).
+     *
+     * @param target The object being animated
+     */
+    @Override
+    public void setTarget(Object target) {
+        for (Node node : mNodes) {
+            Animator animation = node.animation;
+            if (animation instanceof AnimatorSet) {
+                ((AnimatorSet)animation).setTarget(target);
+            } else if (animation instanceof ObjectAnimator) {
+                ((ObjectAnimator)animation).setTarget(target);
+            }
+        }
+    }
+
+    /**
+     * Sets the Interpolator for all current {@link #getChildAnimations() child animations}
+     * of this AnimatorSet.
+     *
+     * @param interpolator the interpolator to be used by each child animation of this AnimatorSet
+     */
+    @Override
+    public void setInterpolator(Interpolator interpolator) {
+        for (Node node : mNodes) {
+            node.animation.setInterpolator(interpolator);
+        }
+    }
+
+    /**
+     * 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 AnimatorSet to play
+     * <code>a1</code> and <code>a2</code> at the same time,
+     * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play
+     * <code>a1</code> first, followed by <code>a2</code>, and
+     * <code>play(a1).after(a2)</code> sets up the AnimatorSet 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 AnimatorSet 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(Animator anim) {
+        if (anim != null) {
+            mNeedsSort = true;
+            return new Builder(anim);
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Note that canceling a <code>AnimatorSet</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<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            for (AnimatorListener listener : tmpListeners) {
+                listener.onAnimationCancel(this);
+            }
+        }
+        if (mSortedNodes.size() > 0) {
+            for (Node node : mSortedNodes) {
+                node.animation.cancel();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is
+     * responsible for.</p>
+     */
+    @Override
+    public void end() {
+        mCanceled = true;
+        if (mSortedNodes.size() != mNodes.size()) {
+            // hasn't been started yet - sort the nodes now, then end them
+            sortNodes();
+            for (Node node : mSortedNodes) {
+                if (mSetListener == null) {
+                    mSetListener = new AnimatorSetListener(this);
+                }
+                node.animation.addListener(mSetListener);
+            }
+        }
+        if (mSortedNodes.size() > 0) {
+            for (Node node : mSortedNodes) {
+                node.animation.end();
+            }
+        }
+    }
+
+    /**
+     * Returns true if any of the child animations of this AnimatorSet have been started and have not
+     * yet ended.
+     * @return Whether this AnimatorSet has been started and has not yet ended.
+     */
+    @Override
+    public boolean isRunning() {
+        for (Node node : mNodes) {
+            if (node.animation.isRunning()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 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
+     */
+    @Override
+    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
+     */
+    @Override
+    public void setStartDelay(long startDelay) {
+        mStartDelay = startDelay;
+    }
+
+    /**
+     * Gets the length of each of the child animations of this AnimatorSet. This value may
+     * be less than 0, which indicates that no duration has been set on this AnimatorSet
+     * and each of the child animations will use their own duration.
+     *
+     * @return The length of the animation, in milliseconds, of each of the child
+     * animations of this AnimatorSet.
+     */
+    @Override
+    public long getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * Sets the length of each of the current child animations of this AnimatorSet. By default,
+     * each child animation will use its own duration. If the duration is set on the AnimatorSet,
+     * then each child animation inherits this duration.
+     *
+     * @param duration The length of the animation, in milliseconds, of each of the child
+     * animations of this AnimatorSet.
+     */
+    @Override
+    public void setDuration(long duration) {
+        if (duration < 0) {
+            throw new IllegalArgumentException("duration must be a value of zero or greater");
+        }
+        for (Node node : mNodes) {
+            // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
+            // insert "play-after" delays
+            node.animation.setDuration(duration);
+        }
+        mDuration = duration;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Starting this <code>AnimatorSet</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.
+        final ArrayList<Node> nodesToStart = new ArrayList<Node>();
+        for (Node node : mSortedNodes) {
+            if (mSetListener == null) {
+                mSetListener = new AnimatorSetListener(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(mSetListener);
+        }
+        // Now that all dependencies are set up, start the animations that should be started.
+        if (mStartDelay <= 0) {
+            for (Node node : nodesToStart) {
+                node.animation.start();
+                mPlayingSet.add(node.animation);
+            }
+        } else {
+            // TODO: Need to cancel out of the delay appropriately
+            ValueAnimator delayAnim = new ValueAnimator(mStartDelay, 0f, 1f);
+            delayAnim.addListener(new AnimatorListenerAdapter() {
+                public void onAnimationEnd(Animator anim) {
+                    for (Node node : nodesToStart) {
+                        node.animation.start();
+                        mPlayingSet.add(node.animation);
+                    }
+                }
+            });
+        }
+        if (mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            for (AnimatorListener listener : tmpListeners) {
+                listener.onAnimationStart(this);
+            }
+        }
+    }
+
+    @Override
+    public AnimatorSet clone() {
+        final AnimatorSet anim = (AnimatorSet) super.clone();
+        /*
+         * The basic clone() operation copies all items. This doesn't work very well for
+         * AnimatorSet, because it will copy references that need to be recreated and state
+         * that may not apply. What we need to do now is put the clone in an uninitialized
+         * state, with fresh, empty data structures. Then we will build up the nodes list
+         * manually, as we clone each Node (and its animation). The clone will then be sorted,
+         * and will populate any appropriate lists, when it is started.
+         */
+        anim.mNeedsSort = true;
+        anim.mCanceled = false;
+        anim.mPlayingSet = new ArrayList<Animator>();
+        anim.mNodeMap = new HashMap<Animator, Node>();
+        anim.mNodes = new ArrayList<Node>();
+        anim.mSortedNodes = new ArrayList<Node>();
+
+        // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
+        // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
+        // We need to track the old/new nodes in order to reconstruct the dependencies in the clone.
+        HashMap<Node, Node> nodeCloneMap = new HashMap<Node, Node>(); // <old, new>
+        for (Node node : mNodes) {
+            Node nodeClone = node.clone();
+            nodeCloneMap.put(node, nodeClone);
+            anim.mNodes.add(nodeClone);
+            anim.mNodeMap.put(nodeClone.animation, nodeClone);
+            // Clear out the dependencies in the clone; we'll set these up manually later
+            nodeClone.dependencies = null;
+            nodeClone.tmpDependencies = null;
+            nodeClone.nodeDependents = null;
+            nodeClone.nodeDependencies = null;
+            // clear out any listeners that were set up by the AnimatorSet; these will
+            // be set up when the clone's nodes are sorted
+            ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
+            if (cloneListeners != null) {
+                ArrayList<AnimatorListener> listenersToRemove = null;
+                for (AnimatorListener listener : cloneListeners) {
+                    if (listener instanceof AnimatorSetListener) {
+                        if (listenersToRemove == null) {
+                            listenersToRemove = new ArrayList<AnimatorListener>();
+                        }
+                        listenersToRemove.add(listener);
+                    }
+                }
+                if (listenersToRemove != null) {
+                    for (AnimatorListener listener : listenersToRemove) {
+                        cloneListeners.remove(listener);
+                    }
+                }
+            }
+        }
+        // Now that we've cloned all of the nodes, we're ready to walk through their
+        // dependencies, mapping the old dependencies to the new nodes
+        for (Node node : mNodes) {
+            Node nodeClone = nodeCloneMap.get(node);
+            if (node.dependencies != null) {
+                for (Dependency dependency : node.dependencies) {
+                    Node clonedDependencyNode = nodeCloneMap.get(dependency.node);
+                    Dependency cloneDependency = new Dependency(clonedDependencyNode,
+                            dependency.rule);
+                    nodeClone.addDependency(cloneDependency);
+                }
+            }
+        }
+
+        return anim;
+    }
+
+    /**
+     * 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 AnimatorListener {
+
+        private AnimatorSet mAnimatorSet;
+
+        // 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(AnimatorSet animatorSet, Node node, int rule) {
+            this.mAnimatorSet = animatorSet;
+            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(Animator animation) {
+        }
+
+        /**
+         * An end event is received - see if this is an event we are listening for
+         */
+        public void onAnimationEnd(Animator animation) {
+            if (mRule == Dependency.AFTER) {
+                startIfReady(animation);
+            }
+        }
+
+        /**
+         * Ignore repeat events for now
+         */
+        public void onAnimationRepeat(Animator animation) {
+        }
+
+        /**
+         * A start event is received - see if this is an event we are listening for
+         */
+        public void onAnimationStart(Animator 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(Animator dependencyAnimation) {
+            if (mAnimatorSet.mCanceled) {
+                // if the parent AnimatorSet 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();
+                mAnimatorSet.mPlayingSet.add(mNode.animation);
+            }
+        }
+
+    }
+
+    private class AnimatorSetListener implements AnimatorListener {
+
+        private AnimatorSet mAnimatorSet;
+
+        AnimatorSetListener(AnimatorSet animatorSet) {
+            mAnimatorSet = animatorSet;
+        }
+
+        public void onAnimationCancel(Animator animation) {
+            if (mPlayingSet.size() == 0) {
+                if (mListeners != null) {
+                    for (AnimatorListener listener : mListeners) {
+                        listener.onAnimationCancel(mAnimatorSet);
+                    }
+                }
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        public void onAnimationEnd(Animator animation) {
+            animation.removeListener(this);
+            mPlayingSet.remove(animation);
+            Node animNode = mAnimatorSet.mNodeMap.get(animation);
+            animNode.done = true;
+            ArrayList<Node> sortedNodes = mAnimatorSet.mSortedNodes;
+            boolean allDone = true;
+            for (Node node : sortedNodes) {
+                if (!node.done) {
+                    allDone = false;
+                    break;
+                }
+            }
+            if (allDone) {
+                // If this was the last child animation to end, then notify listeners that this
+                // AnimatorSet has ended
+                if (mListeners != null) {
+                    ArrayList<AnimatorListener> tmpListeners =
+                            (ArrayList<AnimatorListener>) mListeners.clone();
+                    for (AnimatorListener listener : tmpListeners) {
+                        listener.onAnimationEnd(mAnimatorSet);
+                    }
+                }
+            }
+        }
+
+        // Nothing to do
+        public void onAnimationRepeat(Animator animation) {
+        }
+
+        // Nothing to do
+        public void onAnimationStart(Animator 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 AnimatorSet");
+            }
+        } 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;
+            }
+        }
+    }
+
+    /**
+     * 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 Animator 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 implements Cloneable {
+        public Animator 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 AnimatorSet is launched in the future. So we create a copy of the dependency
+         * list when the AnimatorSet 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 AnimatorSet 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 AnimatorSet.
+         */
+        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(Animator 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);
+        }
+
+        @Override
+        public Node clone() {
+            try {
+                Node node = (Node) super.clone();
+                node.animation = (Animator) animation.clone();
+                return node;
+            } catch (CloneNotSupportedException e) {
+               throw new AssertionError();
+            }
+        }
+    }
+
+    /**
+     * The <code>Builder</code> object is a utility class to facilitate adding animations to a
+     * <code>AnimatorSet</code> along with the relationships between the various animations. The
+     * intention of the <code>Builder</code> methods, along with the {@link
+     * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible to
+     * express the dependency relationships of animations in a natural way. Developers can also use
+     * the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link
+     * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need,
+     * but it might be easier in some situations to express the AnimatorSet 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 AnimatorSet#play(Animator)}.</p>
+     * <p/>
+     * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to
+     * play when anim2 finishes, and anim4 to play when anim3 finishes:</p>
+     * <pre>
+     *     AnimatorSet s = new AnimatorSet();
+     *     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(Animator)} and {@link
+     * Builder#after(Animator)} 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 AnimatorSet#play(Animator)} 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>
+     *   AnimatorSet s = new AnimatorSet();
+     *   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>
+     *   AnimatorSet s = new AnimatorSet();
+     *   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 AnimatorSets
+     * 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 AnimatorSet and passed into the constructor of Builder.
+         */
+        private Node mCurrentNode;
+
+        /**
+         * package-private constructor. Builders are only constructed by AnimatorSet, 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(Animator 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 AnimatorSet#play(Animator)} call that created this <code>Builder</code> object.
+         *
+         * @param anim The animation that will play when the animation supplied to the
+         * {@link AnimatorSet#play(Animator)} method starts.
+         */
+        public void with(Animator 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 AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+         * ends.
+         *
+         * @param anim The animation that will play when the animation supplied to the
+         * {@link AnimatorSet#play(Animator)} method ends.
+         */
+        public void before(Animator 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 AnimatorSet#play(Animator)} 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 AnimatorSet#play(Animator)} method to play.
+         */
+        public void after(Animator 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 AnimatorSet#play(Animator)} 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 ValueAnimator just to run the clock
+            after(new ValueAnimator(delay, 0f, 1f));
+        }
+
+    }
+
+}
diff --git a/core/java/android/animation/DoubleEvaluator.java b/core/java/android/animation/DoubleEvaluator.java
new file mode 100644
index 0000000..e46eb37
--- /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 = ((Number) startValue).doubleValue();
+        return startDouble + fraction * (((Number) endValue).doubleValue() - 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..9e2054d
--- /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 = ((Number) startValue).floatValue();
+        return startFloat + fraction * (((Number) endValue).floatValue() - 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..7288927
--- /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 = ((Number) startValue).intValue();
+        return (int) (startInt + fraction * (((Number) endValue).intValue() - 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..192ba5c
--- /dev/null
+++ b/core/java/android/animation/Keyframe.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 ValueAnimator} 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 implements Cloneable {
+    /**
+     * 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 ValueAnimator} 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, (value != null) ? value.getClass() : Object.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 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, Integer value) {
+        this(fraction, value, Integer.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);
+    }
+
+    /**
+     * 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 ValueAnimator 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;
+    }
+
+    @Override
+    public Keyframe clone() {
+        Keyframe kfClone = new Keyframe(mFraction, mValue, mValueType);
+        kfClone.setInterpolator(mInterpolator);
+        return kfClone;
+    }
+}
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
new file mode 100644
index 0000000..af47a15
--- /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 ValueAnimator 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/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
new file mode 100644
index 0000000..52f0f16
--- /dev/null
+++ b/core/java/android/animation/LayoutTransition.java
@@ -0,0 +1,835 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class enables automatic animations on layout changes in ViewGroup objects. To enable
+ * transitions for a layout container, create a LayoutTransition object and set it on any
+ * ViewGroup by calling {@link ViewGroup#setLayoutTransition(LayoutTransition)}. This will cause
+ * default animations to run whenever items are added to or removed from that container. To specify
+ * custom animations, use the {@link LayoutTransition#setAnimator(int, Animator)
+ * setAnimator()} method.
+ *
+ * <p>One of the core concepts of these transition animations is that there are two core
+ * changes that cause the transition and four different animations that run because of
+ * those changes. The changes that trigger the transition are items being added to a container
+ * (referred to as an "appearing" transition) or removed from a container (also known as
+ * "disappearing"). The animations that run due to those events are one that animates
+ * items being added, one that animates items being removed, and two that animate the other
+ * items in the container that change due to the add/remove occurrence. Users of
+ * the transition may want different animations for the changing items depending on whether
+ * they are changing due to anappearing or disappearing event, so there is one animation for
+ * each of these variations of the changing event. Most of the API of this class is concerned
+ * with setting up the basic properties of the animations used in these four situations,
+ * or with setting up custom animations for any or all of the four.</p>
+ *
+ * <p>The animations specified for the transition, both the defaults and any custom animations
+ * set on the transition object, are templates only. That is, these animations exist to hold the
+ * basic animation properties, such as the duration, start delay, and properties being animated.
+ * But the actual target object, as well as the start and end values for those properties, are
+ * set automatically in the process of setting up the transition each time it runs. Each of the
+ * animations is cloned from the original copy and the clone is then populated with the dynamic
+ * values of the target being animated (such as one of the items in a layout container that is
+ * moving as a result of the layout event) as well as the values that are changing (such as the
+ * position and size of that object). The actual values that are pushed to each animation
+ * depends on what properties are specified for the animation. For example, the default
+ * CHANGE_APPEARING animation animates <code>left</code>, <code>top</code>, <code>right</code>,
+ * and <code>bottom</code>. Values for these properties are updated with the pre- and post-layout
+ * values when the transition begins. Custom animations will be similarly populated with
+ * the target and values being animated, assuming they use ObjectAnimator objects with
+ * property names that are known on the target object.</p>
+ */
+public class LayoutTransition {
+
+    /**
+     * A flag indicating the animation that runs on those items that are changing
+     * due to a new item appearing in the container.
+     */
+    public static final int CHANGE_APPEARING = 0;
+
+    /**
+     * A flag indicating the animation that runs on those items that are changing
+     * due to a new item disappearing from the container.
+     */
+    public static final int CHANGE_DISAPPEARING = 1;
+
+    /**
+     * A flag indicating the animation that runs on those items that are changing
+     * due to a new item appearing in the container.
+     */
+    public static final int APPEARING = 2;
+
+    /**
+     * A flag indicating the animation that runs on those items that are changing
+     * due to a new item appearing in the container.
+     */
+    public static final int DISAPPEARING = 3;
+
+    /**
+     * These variables hold the animations that are currently used to run the transition effects.
+     * These animations are set to defaults, but can be changed to custom animations by
+     * calls to setAnimator().
+     */
+    private Animator mDisappearingAnim = null;
+    private Animator mAppearingAnim = null;
+    private Animator mChangingAppearingAnim = null;
+    private Animator mChangingDisappearingAnim = null;
+
+    /**
+     * These are the default animations, defined in the constructor, that will be used
+     * unless the user specifies custom animations.
+     */
+    private static ObjectAnimator defaultChangeIn;
+    private static ObjectAnimator defaultChangeOut;
+    private static ObjectAnimator defaultFadeIn;
+    private static ObjectAnimator defaultFadeOut;
+
+    /**
+     * The default duration used by all animations.
+     */
+    private static long DEFAULT_DURATION = 300;
+
+    /**
+     * The durations of the four different animations
+     */
+    private long mChangingAppearingDuration = DEFAULT_DURATION;
+    private long mChangingDisappearingDuration = DEFAULT_DURATION;
+    private long mAppearingDuration = DEFAULT_DURATION;
+    private long mDisappearingDuration = DEFAULT_DURATION;
+
+    /**
+     * The start delays of the four different animations. Note that the default behavior of
+     * the appearing item is the default duration, since it should wait for the items to move
+     * before fading it. Same for the changing animation when disappearing; it waits for the item
+     * to fade out before moving the other items.
+     */
+    private long mAppearingDelay = DEFAULT_DURATION;
+    private long mDisappearingDelay = 0;
+    private long mChangingAppearingDelay = 0;
+    private long mChangingDisappearingDelay = DEFAULT_DURATION;
+
+    /**
+     * The inter-animation delays used on the two changing animations
+     */
+    private long mChangingAppearingStagger = 0;
+    private long mChangingDisappearingStagger = 0;
+
+    /**
+     * The default interpolators used for the animations
+     */
+    private Interpolator mAppearingInterpolator = new AccelerateDecelerateInterpolator();
+    private Interpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator();
+    private Interpolator mChangingAppearingInterpolator = new DecelerateInterpolator();
+    private Interpolator mChangingDisappearingInterpolator = new DecelerateInterpolator();
+
+    /**
+     * This hashmap is used to store the animations that are currently running as part of
+     * the transition. The reason for this is that a further layout event should cause
+     * existing animations to stop where they are prior to starting new animations. So
+     * we cache all of the current animations in this map for possible cancellation on
+     * another layout event.
+     */
+    private HashMap<View, Animator> currentAnimations = new HashMap<View, Animator>();
+
+    /**
+     * This hashmap is used to track the listeners that have been added to the children of
+     * a container. When a layout change occurs, an animation is created for each View, so that
+     * the pre-layout values can be cached in that animation. Then a listener is added to the
+     * view to see whether the layout changes the bounds of that view. If so, the animation
+     * is set with the final values and then run. If not, the animation is not started. When
+     * the process of setting up and running all appropriate animations is done, we need to
+     * remove these listeners and clear out the map.
+     */
+    private HashMap<View, View.OnLayoutChangeListener> layoutChangeListenerMap =
+            new HashMap<View, View.OnLayoutChangeListener>();
+
+    /**
+     * Used to track the current delay being assigned to successive animations as they are
+     * started. This value is incremented for each new animation, then zeroed before the next
+     * transition begins.
+     */
+    private long staggerDelay;
+
+    /**
+     * The set of listeners that should be notified when APPEARING/DISAPPEARING transitions
+     * start and end.
+     */
+    private ArrayList<TransitionListener> mListeners;
+
+
+    /**
+     * Constructs a LayoutTransition object. By default, the object will listen to layout
+     * events on any ViewGroup that it is set on and will run default animations for each
+     * type of layout event.
+     */
+    public LayoutTransition() {
+        if (defaultChangeIn == null) {
+            // "left" is just a placeholder; we'll put real properties/values in when needed
+            PropertyValuesHolder<Integer> pvhLeft = new PropertyValuesHolder<Integer>("left", 0, 1);
+            PropertyValuesHolder<Integer> pvhTop = new PropertyValuesHolder<Integer>("top", 0, 1);
+            PropertyValuesHolder<Integer> pvhRight = new PropertyValuesHolder<Integer>("right", 0, 1);
+            PropertyValuesHolder<Integer> pvhBottom = new PropertyValuesHolder<Integer>("bottom", 0, 1);
+            defaultChangeIn = new ObjectAnimator<PropertyValuesHolder>(DEFAULT_DURATION, this,
+                    pvhLeft, pvhTop, pvhRight, pvhBottom);
+            defaultChangeIn.setStartDelay(mChangingAppearingDelay);
+            defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
+            defaultChangeOut = defaultChangeIn.clone();
+            defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
+            defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
+            defaultFadeIn =
+                    new ObjectAnimator<Float>(DEFAULT_DURATION, this, "alpha", 0f, 1f);
+            defaultFadeIn.setStartDelay(mAppearingDelay);
+            defaultFadeIn.setInterpolator(mAppearingInterpolator);
+            defaultFadeOut =
+                    new ObjectAnimator<Float>(DEFAULT_DURATION, this, "alpha", 1f, 0f);
+            defaultFadeOut.setStartDelay(mDisappearingDelay);
+            defaultFadeOut.setInterpolator(mDisappearingInterpolator);
+        }
+        mChangingAppearingAnim = defaultChangeIn;
+        mChangingDisappearingAnim = defaultChangeOut;
+        mAppearingAnim = defaultFadeIn;
+        mDisappearingAnim = defaultFadeOut;
+    }
+
+    /**
+     * Sets the duration to be used by all animations of this transition object. If you want to
+     * set the duration of just one of the animations in particular, use the
+     * {@link #setDuration(int, long)} method.
+     *
+     * @param duration The length of time, in milliseconds, that the transition animations
+     * should last.
+     */
+    public void setDuration(long duration) {
+        mChangingAppearingDuration = duration;
+        mChangingDisappearingDuration = duration;
+        mAppearingDuration = duration;
+        mDisappearingDuration = duration;
+    }
+
+    /**
+     * Sets the start delay on one of the animation objects used by this transition. The
+     * <code>transitionType</code> parameter determines the animation whose start delay
+     * is being set.
+     *
+     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+     * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start
+     * delay is being set.
+     * @param delay The length of time, in milliseconds, to delay before starting the animation.
+     * @see Animator#setStartDelay(long)
+     */
+    public void setStartDelay(int transitionType, long delay) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                mChangingAppearingDelay = delay;
+                break;
+            case CHANGE_DISAPPEARING:
+                mChangingDisappearingDelay = delay;
+                break;
+            case APPEARING:
+                mAppearingDelay = delay;
+                break;
+            case DISAPPEARING:
+                mDisappearingDelay = delay;
+                break;
+        }
+    }
+
+    /**
+     * Gets the start delay on one of the animation objects used by this transition. The
+     * <code>transitionType</code> parameter determines the animation whose start delay
+     * is returned.
+     *
+     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+     * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start
+     * delay is returned.
+     * @return long The start delay of the specified animation.
+     * @see Animator#getStartDelay()
+     */
+    public long getStartDelay(int transitionType) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                return mChangingAppearingDuration;
+            case CHANGE_DISAPPEARING:
+                return mChangingDisappearingDuration;
+            case APPEARING:
+                return mAppearingDuration;
+            case DISAPPEARING:
+                return mDisappearingDuration;
+        }
+        // shouldn't reach here
+        return 0;
+    }
+
+    /**
+     * Sets the duration on one of the animation objects used by this transition. The
+     * <code>transitionType</code> parameter determines the animation whose duration
+     * is being set.
+     *
+     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+     * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
+     * duration is being set.
+     * @param duration The length of time, in milliseconds, that the specified animation should run.
+     * @see Animator#setDuration(long)
+     */
+    public void setDuration(int transitionType, long duration) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                mChangingAppearingDuration = duration;
+                break;
+            case CHANGE_DISAPPEARING:
+                mChangingDisappearingDuration = duration;
+                break;
+            case APPEARING:
+                mAppearingDuration = duration;
+                break;
+            case DISAPPEARING:
+                mDisappearingDuration = duration;
+                break;
+        }
+    }
+
+    /**
+     * Gets the duration on one of the animation objects used by this transition. The
+     * <code>transitionType</code> parameter determines the animation whose duration
+     * is returned.
+     *
+     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+     * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
+     * duration is returned.
+     * @return long The duration of the specified animation.
+     * @see Animator#getDuration()
+     */
+    public long getDuration(int transitionType) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                return mChangingAppearingDuration;
+            case CHANGE_DISAPPEARING:
+                return mChangingDisappearingDuration;
+            case APPEARING:
+                return mAppearingDuration;
+            case DISAPPEARING:
+                return mDisappearingDuration;
+        }
+        // shouldn't reach here
+        return 0;
+    }
+
+    /**
+     * Sets the length of time to delay between starting each animation during one of the
+     * CHANGE animations.
+     *
+     * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}.
+     * @param duration The length of time, in milliseconds, to delay before launching the next
+     * animation in the sequence.
+     */
+    public void setStagger(int transitionType, long duration) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                mChangingAppearingStagger = duration;
+                break;
+            case CHANGE_DISAPPEARING:
+                mChangingDisappearingStagger = duration;
+                break;
+            // noop other cases
+        }
+    }
+
+    /**
+     * Tets the length of time to delay between starting each animation during one of the
+     * CHANGE animations.
+     *
+     * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}.
+     * @return long The length of time, in milliseconds, to delay before launching the next
+     * animation in the sequence.
+     */
+    public long getStagger(int transitionType) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                return mChangingAppearingStagger;
+            case CHANGE_DISAPPEARING:
+                return mChangingDisappearingStagger;
+        }
+        // shouldn't reach here
+        return 0;
+    }
+
+    /**
+     * Sets the interpolator on one of the animation objects used by this transition. The
+     * <code>transitionType</code> parameter determines the animation whose interpolator
+     * is being set.
+     *
+     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+     * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
+     * duration is being set.
+     * @param interpolator The interpolator that the specified animation should use.
+     * @see Animator#setInterpolator(android.view.animation.Interpolator)
+     */
+    public void setInterpolator(int transitionType, Interpolator interpolator) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                mChangingAppearingInterpolator = interpolator;
+                break;
+            case CHANGE_DISAPPEARING:
+                mChangingDisappearingInterpolator = interpolator;
+                break;
+            case APPEARING:
+                mAppearingInterpolator = interpolator;
+                break;
+            case DISAPPEARING:
+                mDisappearingInterpolator = interpolator;
+                break;
+        }
+    }
+
+    /**
+     * Gets the interpolator on one of the animation objects used by this transition. The
+     * <code>transitionType</code> parameter determines the animation whose interpolator
+     * is returned.
+     *
+     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+     * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
+     * duration is being set.
+     * @return Interpolator The interpolator that the specified animation uses.
+     * @see Animator#setInterpolator(android.view.animation.Interpolator)
+     */
+    public Interpolator getInterpolator(int transitionType) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                return mChangingAppearingInterpolator;
+            case CHANGE_DISAPPEARING:
+                return mChangingDisappearingInterpolator;
+            case APPEARING:
+                return mAppearingInterpolator;
+            case DISAPPEARING:
+                return mDisappearingInterpolator;
+        }
+        // shouldn't reach here
+        return null;
+    }
+
+    /**
+     * Sets the animation used during one of the transition types that may run. Any
+     * Animator object can be used, but to be most useful in the context of layout
+     * transitions, the animation should either be a ObjectAnimator or a AnimatorSet
+     * of animations including PropertyAnimators. Also, these ObjectAnimator objects
+     * should be able to get and set values on their target objects automatically. For
+     * example, a ObjectAnimator that animates the property "left" is able to set and get the
+     * <code>left</code> property from the View objects being animated by the layout
+     * transition. The transition works by setting target objects and properties
+     * dynamically, according to the pre- and post-layoout values of those objects, so
+     * having animations that can handle those properties appropriately will work best
+     * for custom animation. The dynamic setting of values is only the case for the
+     * CHANGE animations; the APPEARING and DISAPPEARING animations are simply run with
+     * the values they have.
+     *
+     * <p>It is also worth noting that any and all animations (and their underlying
+     * PropertyValuesHolder objects) will have their start and end values set according
+     * to the pre- and post-layout values. So, for example, a custom animation on "alpha"
+     * as the CHANGE_APPEARING animation will inherit the real value of alpha on the target
+     * object (presumably 1) as its starting and ending value when the animation begins.
+     * Animations which need to use values at the beginning and end that may not match the
+     * values queried when the transition begins may need to use a different mechanism
+     * than a standard ObjectAnimator object.</p>
+     *
+     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+     * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
+     * duration is being set.
+     * @param animator The animation being assigned. A value of <code>null</code> means that no
+     * animation will be run for the specified transitionType.
+     */
+    public void setAnimator(int transitionType, Animator animator) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                mChangingAppearingAnim = animator;
+                break;
+            case CHANGE_DISAPPEARING:
+                mChangingDisappearingAnim = animator;
+                break;
+            case APPEARING:
+                mAppearingAnim = animator;
+                break;
+            case DISAPPEARING:
+                mDisappearingAnim = animator;
+                break;
+        }
+    }
+
+    /**
+     * Gets the animation used during one of the transition types that may run.
+     *
+     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+     * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
+     * duration is being set.
+     * @return Animator The animation being used for the given transition type.
+     * @see #setAnimator(int, Animator)
+     */
+    public Animator getAnimator(int transitionType) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+                return mChangingAppearingAnim;
+            case CHANGE_DISAPPEARING:
+                return mChangingDisappearingAnim;
+            case APPEARING:
+                return mAppearingAnim;
+            case DISAPPEARING:
+                return mDisappearingAnim;
+        }
+        // shouldn't reach here
+        return null;
+    }
+
+    /**
+     * This function sets up runs animations on all of the views that change during layout.
+     * For every child in the parent, we create a change animation of the appropriate
+     * type (appearing or disappearing) and ask it to populate its start values from its
+     * target view. We add layout listeners to all child views and listen for changes. For
+     * those views that change, we populate the end values for those animations and start them.
+     * Animations are not run on unchanging views.
+     *
+     * @param parent The container which is undergoing an appearing or disappearing change.
+     * @param newView The view being added to or removed from the parent.
+     * @param changeReason A value of APPEARING or DISAPPEARING, indicating whether the
+     * transition is occuring because an item is being added to or removed from the parent.
+     */
+    private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
+
+        Animator baseAnimator = (changeReason == APPEARING) ?
+                mChangingAppearingAnim : mChangingDisappearingAnim;
+        // If the animation is null, there's nothing to do
+        if (baseAnimator == null) {
+            return;
+        }
+
+        // reset the inter-animation delay, in case we use it later
+        staggerDelay = 0;
+
+        final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup
+        if (!observer.isAlive()) {
+            // If the observer's not in a good state, skip the transition
+            return;
+        }
+        int numChildren = parent.getChildCount();
+
+        for (int i = 0; i < numChildren; ++i) {
+            final View child = parent.getChildAt(i);
+
+            // only animate the views not being added or removed
+            if (child != newView) {
+
+                // If there's an animation running on this view already, cancel it
+                Animator currentAnimation = currentAnimations.get(child);
+                if (currentAnimation != null) {
+                    currentAnimation.cancel();
+                    currentAnimations.remove(child);
+                }
+
+                // Make a copy of the appropriate animation
+                final Animator anim = baseAnimator.clone();
+
+                // Cache the animation in case we need to cancel it later
+                currentAnimations.put(child, anim);
+
+                // Set the target object for the animation
+                anim.setTarget(child);
+
+                // A ObjectAnimator (or AnimatorSet of them) can extract start values from
+                // its target object
+                anim.setupStartValues();
+
+                // Add a listener to track layout changes on this view. If we don't get a callback,
+                // then there's nothing to animate.
+                final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
+                    public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                            int oldLeft, int oldTop, int oldRight, int oldBottom) {
+
+                        // Tell the animation to extract end values from the changed object
+                        anim.setupEndValues();
+
+                        long startDelay;
+                        long duration;
+                        if (changeReason == APPEARING) {
+                            startDelay = mChangingAppearingDelay + staggerDelay;
+                            staggerDelay += mChangingAppearingStagger;
+                            duration = mChangingAppearingDuration;
+                        } else {
+                            startDelay = mChangingDisappearingDelay + staggerDelay;
+                            staggerDelay += mChangingDisappearingStagger;
+                            duration = mChangingDisappearingDuration;
+                        }
+                        anim.setStartDelay(startDelay);
+                        anim.setDuration(duration);
+
+                        if (anim instanceof ObjectAnimator) {
+                            ((ObjectAnimator) anim).setCurrentPlayTime(0);
+                        }
+                        anim.start();
+
+                        // this only removes listeners whose views changed - must clear the
+                        // other listeners later
+                        child.removeOnLayoutChangeListener(this);
+                        layoutChangeListenerMap.remove(child);
+                    }
+                };
+                // Remove the animation from the cache when it ends
+                anim.addListener(new AnimatorListenerAdapter() {
+                    private boolean canceled = false;
+                    public void onAnimationCancel(Animator animator) {
+                        // we remove canceled animations immediately, not here
+                        canceled = true;
+                        child.removeOnLayoutChangeListener(listener);
+                        layoutChangeListenerMap.remove(child);
+                    }
+                    public void onAnimationEnd(Animator animator) {
+                        if (!canceled) {
+                            currentAnimations.remove(child);
+                        }
+                    }
+                });
+
+                child.addOnLayoutChangeListener(listener);
+                // cache the listener for later removal
+                layoutChangeListenerMap.put(child, listener);
+            }
+        }
+        // This is the cleanup step. When we get this rendering event, we know that all of
+        // the appropriate animations have been set up and run. Now we can clear out the
+        // layout listeners.
+        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            public boolean onPreDraw() {
+                parent.getViewTreeObserver().removeOnPreDrawListener(this);
+                int numChildren = parent.getChildCount();
+                for (int i = 0; i < numChildren; ++i) {
+                    final View child = parent.getChildAt(i);
+                    child.removeOnLayoutChangeListener(layoutChangeListenerMap.get(child));
+                }
+                layoutChangeListenerMap.clear();
+                return true;
+            }
+        });
+    }
+
+    /**
+     * This method runs the animation that makes an added item appear.
+     *
+     * @param parent The ViewGroup to which the View is being added.
+     * @param child The View being added to the ViewGroup.
+     */
+    private void runAppearingTransition(final ViewGroup parent, final View child) {
+        if (mAppearingAnim == null) {
+            if (mListeners != null) {
+                for (TransitionListener listener : mListeners) {
+                    listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
+                }
+            }
+            return;
+        }
+        Animator anim = mAppearingAnim.clone();
+        anim.setTarget(child);
+        anim.setStartDelay(mAppearingDelay);
+        anim.setDuration(mAppearingDuration);
+        if (anim instanceof ObjectAnimator) {
+            ((ObjectAnimator) anim).setCurrentPlayTime(0);
+        }
+        if (mListeners != null) {
+            anim.addListener(new AnimatorListenerAdapter() {
+                public void onAnimationEnd() {
+                    for (TransitionListener listener : mListeners) {
+                        listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
+                    }
+                }
+            });
+        }
+        anim.start();
+    }
+
+    /**
+     * This method runs the animation that makes a removed item disappear.
+     *
+     * @param parent The ViewGroup from which the View is being removed.
+     * @param child The View being removed from the ViewGroup.
+     */
+    private void runDisappearingTransition(final ViewGroup parent, final View child) {
+        if (mDisappearingAnim == null) {
+            if (mListeners != null) {
+                for (TransitionListener listener : mListeners) {
+                    listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
+                }
+            }
+            return;
+        }
+        Animator anim = mDisappearingAnim.clone();
+        anim.setStartDelay(mDisappearingDelay);
+        anim.setDuration(mDisappearingDuration);
+        anim.setTarget(child);
+        if (mListeners != null) {
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator anim) {
+                    for (TransitionListener listener : mListeners) {
+                        listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
+                    }
+                }
+            });
+        }
+        if (anim instanceof ObjectAnimator) {
+            ((ObjectAnimator) anim).setCurrentPlayTime(0);
+        }
+        anim.start();
+    }
+
+    /**
+     * This method is called by ViewGroup when a child view is about to be added to the
+     * container. This callback starts the process of a transition; we grab the starting
+     * values, listen for changes to all of the children of the container, and start appropriate
+     * animations.
+     *
+     * @param parent The ViewGroup to which the View is being added.
+     * @param child The View being added to the ViewGroup.
+     */
+    public void addChild(ViewGroup parent, View child) {
+        if (mListeners != null) {
+            for (TransitionListener listener : mListeners) {
+                listener.startTransition(this, parent, child, APPEARING);
+            }
+        }
+        runChangeTransition(parent, child, APPEARING);
+        runAppearingTransition(parent, child);
+    }
+
+    /**
+     * This method is called by ViewGroup when a child view is about to be added to the
+     * container. This callback starts the process of a transition; we grab the starting
+     * values, listen for changes to all of the children of the container, and start appropriate
+     * animations.
+     *
+     * @param parent The ViewGroup to which the View is being added.
+     * @param child The View being added to the ViewGroup.
+     */
+    public void showChild(ViewGroup parent, View child) {
+        addChild(parent, child);
+    }
+
+    /**
+     * This method is called by ViewGroup when a child view is about to be removed from the
+     * container. This callback starts the process of a transition; we grab the starting
+     * values, listen for changes to all of the children of the container, and start appropriate
+     * animations.
+     *
+     * @param parent The ViewGroup from which the View is being removed.
+     * @param child The View being removed from the ViewGroup.
+     */
+    public void removeChild(ViewGroup parent, View child) {
+        if (mListeners != null) {
+            for (TransitionListener listener : mListeners) {
+                listener.startTransition(this, parent, child, DISAPPEARING);
+            }
+        }
+        runChangeTransition(parent, child, DISAPPEARING);
+        runDisappearingTransition(parent, child);
+    }
+
+    /**
+     * This method is called by ViewGroup when a child view is about to be removed from the
+     * container. This callback starts the process of a transition; we grab the starting
+     * values, listen for changes to all of the children of the container, and start appropriate
+     * animations.
+     *
+     * @param parent The ViewGroup from which the View is being removed.
+     * @param child The View being removed from the ViewGroup.
+     */
+    public void hideChild(ViewGroup parent, View child) {
+        removeChild(parent, child);
+    }
+
+    /**
+     * Add a listener that will be called when the bounds of the view change due to
+     * layout processing.
+     *
+     * @param listener The listener that will be called when layout bounds change.
+     */
+    public void addTransitionListener(TransitionListener listener) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<TransitionListener>();
+        }
+        mListeners.add(listener);
+    }
+
+    /**
+     * Remove a listener for layout changes.
+     *
+     * @param listener The listener for layout bounds change.
+     */
+    public void removeTransitionListener(TransitionListener listener) {
+        if (mListeners == null) {
+            return;
+        }
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Gets the current list of listeners for layout changes.
+     * @return
+     */
+    public List<TransitionListener> getTransitionListeners() {
+        return mListeners;
+    }
+
+    /**
+     * This interface is used for listening to starting and ending events for transitions.
+     */
+    public interface TransitionListener {
+
+        /**
+         * This event is sent to listeners when an APPEARING or DISAPPEARING transition
+         * begins.
+         *
+         * @param transition The LayoutTransition sending out the event.
+         * @param container The ViewGroup on which the transition is playing.
+         * @param view The View object being added or removed from its parent.
+         * @param transitionType The type of transition that is beginning, either
+         * {@link android.animation.LayoutTransition#APPEARING} or
+         * {@link android.animation.LayoutTransition#DISAPPEARING}.
+         */
+        public void startTransition(LayoutTransition transition, ViewGroup container,
+                View view, int transitionType);
+
+        /**
+         * This event is sent to listeners when an APPEARING or DISAPPEARING transition ends.
+         *
+         * @param transition The LayoutTransition sending out the event.
+         * @param container The ViewGroup on which the transition is playing.
+         * @param view The View object being added or removed from its parent.
+         * @param transitionType The type of transition that is ending, either
+         * {@link android.animation.LayoutTransition#APPEARING} or
+         * {@link android.animation.LayoutTransition#DISAPPEARING}.
+         */
+        public void endTransition(LayoutTransition transition, ViewGroup container,
+                View view, int transitionType);
+    }
+
+}
\ No newline at end of file
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
new file mode 100644
index 0000000..31ddb0b
--- /dev/null
+++ b/core/java/android/animation/ObjectAnimator.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 android.animation;
+
+import android.util.Log;
+
+import java.lang.reflect.Method;
+
+/**
+ * This subclass of {@link ValueAnimator} 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 ObjectAnimator<T> extends ValueAnimator<T> {
+
+    // The target object on which the property exists, set in the constructor
+    private Object mTarget;
+
+    private String mPropertyName;
+
+    /**
+     * 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>
+     *
+     * <p>If this ObjectAnimator has been set up to animate several properties together,
+     * using more than one PropertyValuesHolder objects, then setting the propertyName simply
+     * sets the propertyName in the first of those PropertyValuesHolder objects.</p>
+     *
+     * @param propertyName The name of the property being animated.
+     */
+    public void setPropertyName(String propertyName) {
+        // mValues could be null if this is being constructed piecemeal. Just record the
+        // propertyName to be used later when setValues() is called if so.
+        if (mValues != null) {
+            PropertyValuesHolder valuesHolder = mValues[0];
+            String oldName = valuesHolder.getPropertyName();
+            valuesHolder.setPropertyName(propertyName);
+            mValuesMap.remove(oldName);
+            mValuesMap.put(propertyName, valuesHolder);
+        }
+        mPropertyName = propertyName;
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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("ObjectAnimator",
+                    "Couldn't find setter/getter for property " + mPropertyName + ": " + e);
+        }
+        return returnVal;
+    }
+
+    /**
+     * Creates a new ObjectAnimator object. This default constructor is primarily for
+     * use internally; the other constructors which take parameters are more generally
+     * useful.
+     */
+    public ObjectAnimator() {
+    }
+
+    /**
+     * A constructor that takes a single property name and set of values. This constructor is
+     * used in the simple case of animating a single property.
+     *
+     * @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 method on it called <code>setName()</code>, where <code>name</code> is
+     * the value of the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property being animated.
+     * @param values The set of values to animate between. If there is only one value, it
+     * is assumed to be the final value being animated to, and the initial value will be
+     * derived on the fly.
+     */
+    public ObjectAnimator(long duration, Object target, String propertyName, T...values) {
+        super(duration, (T[]) values);
+        mTarget = target;
+        setPropertyName(propertyName);
+    }
+
+    /**
+     * A constructor that takes <code>PropertyValueHolder</code> values. This constructor should
+     * be used when animating several properties at once with the same ObjectAnimator, since
+     * PropertyValuesHolder allows you to associate a set of animation values with a property
+     * name.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have public methods on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter for
+     * each of the PropertyValuesHolder objects.
+     * @param values The PropertyValuesHolder objects which hold each the property name and values
+     * to animate that property between.
+     */
+    public ObjectAnimator(long duration, Object target, PropertyValuesHolder...values) {
+        super(duration);
+        setValues(values);
+        mTarget = target;
+    }
+
+    @Override
+    public void setValues(T... values) {
+        if (mValues == null || mValues.length == 0) {
+            // No values yet - this animator is being constructed piecemeal. Init the values with
+            // whatever the current propertyName is
+            setValues(new PropertyValuesHolder[]{
+                    new PropertyValuesHolder(mPropertyName, (Object[])values)});
+        } else {
+            super.setValues((T[]) values);
+        }
+    }
+
+    /**
+     * 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() {
+        if (!mInitialized) {
+            // mValueType may change due to setter/getter setup; do this before calling super.init(),
+            // which uses mValueType to set up the default type evaluator.
+            int numValues = mValues.length;
+            for (int i = 0; i < numValues; ++i) {
+                mValues[i].setupSetterAndGetter(mTarget);
+            }
+            super.initAnimation();
+        }
+    }
+
+
+    /**
+     * 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
+     */
+    @Override
+    public void setTarget(Object target) {
+        mTarget = target;
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    @Override
+    public void setupStartValues() {
+        initAnimation();
+        int numValues = mValues.length;
+        for (int i = 0; i < numValues; ++i) {
+            mValues[i].setupStartValue(mTarget);
+        }
+    }
+
+    @Override
+    public void setupEndValues() {
+        initAnimation();
+        int numValues = mValues.length;
+        for (int i = 0; i < numValues; ++i) {
+            mValues[i].setupEndValue(mTarget);
+        }
+    }
+
+    /**
+     * 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);
+        int numValues = mValues.length;
+        for (int i = 0; i < numValues; ++i) {
+            mValues[i].setAnimatedValue(mTarget);
+        }
+    }
+
+    @Override
+    public ObjectAnimator clone() {
+        final ObjectAnimator anim = (ObjectAnimator) super.clone();
+        return anim;
+    }
+}
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
new file mode 100644
index 0000000..1d46123
--- /dev/null
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * This class holds information about a property and the values that that property
+ * should take on during an animation. PropertyValuesHolder objects can be used to create
+ * animations with ValueAnimator or ObjectAnimator that operate on several different properties
+ * in parallel.
+ */
+public class PropertyValuesHolder<T> implements Cloneable {
+
+    /**
+     * The name of the property associated with the values. This need not be a real property,
+     * unless this object is being used with ObjectAnimator. But this is the name by which
+     * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator.
+     */
+    private String mPropertyName;
+
+    /**
+     * The setter function, if needed. ObjectAnimator hands off this functionality to
+     * PropertyValuesHolder, since it holds all of the per-property information. This
+     * property can be manually set via setSetter(). Otherwise, it is automatically
+     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
+     */
+    private Method mSetter = null;
+
+    /**
+     * The getter function, if needed. ObjectAnimator hands off this functionality to
+     * PropertyValuesHolder, since it holds all of the per-property information. This
+     * property can be manually set via setSetter(). Otherwise, it is automatically
+     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
+     * The getter is only derived and used if one of the values is null.
+     */
+    private Method mGetter = null;
+
+    /**
+     * The type of values supplied. This information is used both in deriving the setter/getter
+     * functions and in deriving the type of TypeEvaluator.
+     */
+    private Class mValueType;
+
+    /**
+     * The set of keyframes (time/value pairs) that define this animation.
+     */
+    private KeyframeSet mKeyframeSet = null;
+
+
+    // 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();
+
+    // We try several different types when searching for appropriate setter/getter functions.
+    // The caller may have supplied values in a type that does not match the setter/getter
+    // functions (such as the integers 0 and 1 to represent floating point values for alpha).
+    // Also, the use of generics in constructors means that we end up with the Object versions
+    // of primitive types (Float vs. float). But most likely, the setter/getter functions
+    // will take primitive types instead.
+    // So we supply an ordered array of other types to try before giving up.
+    private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
+            Double.class, Integer.class};
+    private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
+            Float.class, Double.class};
+    private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class,
+            Float.class, Integer.class};
+
+    // 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<Class, HashMap<String, Method>> sSetterPropertyMap =
+            new HashMap<Class, HashMap<String, Method>>();
+    private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap =
+            new HashMap<Class, 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];
+
+    /**
+     * 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 value most recently calculated by calculateValue(). This is set during
+     * that function and might be retrieved later either by ValueAnimator.animatedValue() or
+     * by the property-setting logic in ObjectAnimator.animatedValue().
+     */
+    private Object mAnimatedValue;
+
+    /**
+     * Constructs a PropertyValuesHolder object with just a set of values. This constructor
+     * is typically not used when animating objects with ObjectAnimator, because that
+     * object needs distinct and meaningful property names. Simpler animations of one
+     * set of values using ValueAnimator may use this constructor, however, because no
+     * distinguishing name is needed.
+     * @param values The set of values to animate between. If there is only one value, it
+     * is assumed to be the final value being animated to, and the initial value will be
+     * derived on the fly.
+     */
+    public PropertyValuesHolder(T...values) {
+        this(null, values);
+    }
+
+    /**
+     * Constructs a PropertyValuesHolder object with the specified property name and set of
+     * values. These values can be of any type, but the type should be consistent so that
+     * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
+     * the common type.
+     * <p>If there is only one value, it is assumed to be the end value of an animation,
+     * and an initial value will be derived, if possible, by calling a getter function
+     * on the object. Also, if any value is null, the value will be filled in when the animation
+     * starts in the same way. This mechanism of automatically getting null values only works
+     * if the PropertyValuesHolder object is used in conjunction
+     * {@link ObjectAnimator}, and with a getter function either
+     * derived automatically from <code>propertyName</code> or set explicitly via
+     * {@link #setGetter(java.lang.reflect.Method)}, since otherwise PropertyValuesHolder has
+     * no way of determining what the value should be.
+     * @param propertyName The name of the property associated with this set of values. This
+     * can be the actual property name to be used when using a ObjectAnimator object, or
+     * just a name used to get animated values, such as if this object is used with an
+     * ValueAnimator object.
+     * @param values The set of values to animate between.
+     */
+    public PropertyValuesHolder(String propertyName, T... values) {
+        mPropertyName = propertyName;
+        setValues(values);
+    }
+
+    /**
+     * Sets the values being animated between.
+     * If there is only one value, it is assumed to be the end value of an animation,
+     * and an initial value will be derived, if possible, by calling a getter function
+     * on the object. Also, if any value is null, the value will be filled in when the animation
+     * starts in the same way. This mechanism of automatically getting null values only works
+     * if the PropertyValuesHolder object is used in conjunction
+     * {@link ObjectAnimator}, and with a getter function either
+     * derived automatically from <code>propertyName</code> or set explicitly via
+     * {@link #setGetter(java.lang.reflect.Method)}, since otherwise PropertyValuesHolder has
+     * no way of determining what the value should be.
+     * @param values The set of values to animate between.
+     */
+    public void setValues(T... values) {
+        int numKeyframes = values.length;
+        for (int i = 0; i < numKeyframes; ++i) {
+            if (values[i] != null) {
+                Class thisValueType = values[i].getClass();
+                if (mValueType == null) {
+                    mValueType = thisValueType;
+                } else {
+                    if (thisValueType != mValueType) {
+                        if (mValueType == Integer.class &&
+                                (thisValueType == Float.class || thisValueType == Double.class)) {
+                            mValueType = thisValueType;
+                        } else if (mValueType == Float.class && thisValueType == Double.class) {
+                            mValueType = thisValueType;
+                        }
+                    }
+                }
+            }
+        }
+        Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)];
+        if (mValueType.equals(Keyframe.class)) {
+            mValueType = ((Keyframe)values[0]).getType();
+            for (int i = 0; i < numKeyframes; ++i) {
+                keyframes[i] = (Keyframe)values[i];
+            }
+        } else {
+            if (numKeyframes == 1) {
+                keyframes[0] = new Keyframe(0f, (Object) null);
+                keyframes[1] = new Keyframe(1f, values[0]);
+            } else {
+                keyframes[0] = new Keyframe(0f, values[0]);
+                for (int i = 1; i < numKeyframes; ++i) {
+                    if (values[i] != null && (values[i].getClass() != mValueType)) {
+
+                    }
+                    keyframes[i] = new Keyframe((float) i / (numKeyframes - 1), values[i]);
+                }
+            }
+        }
+        mKeyframeSet = new KeyframeSet(keyframes);
+    }
+
+
+
+    /**
+     * 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 targetClass The class to search for the method
+     * @param prefix "set" or "get", depending on whether we need a setter or getter.
+     * @param valueType The type of the parameter (in the case of a setter). This type
+     * is derived from the values set on this PropertyValuesHolder. This type is used as
+     * a first guess at the parameter type, but we check for methods with several different
+     * types to avoid problems with slight mis-matches between supplied values and actual
+     * value types used on the setter.
+     * @return Method the method associated with mPropertyName.
+     */
+    private Method getPropertyFunction(Class targetClass, 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 methodName = prefix + firstLetter + theRest;
+        Class args[] = null;
+        if (valueType == null) {
+            try {
+                returnVal = targetClass.getMethod(methodName, args);
+            } catch (NoSuchMethodException e) {
+                Log.e("PropertyValuesHolder",
+                        "Couldn't find no-arg method for property " + mPropertyName + ": " + e);
+            }
+        } else {
+            args = new Class[1];
+            Class typeVariants[];
+            if (mValueType.equals(Float.class)) {
+                typeVariants = FLOAT_VARIANTS;
+            } else if (mValueType.equals(Integer.class)) {
+                typeVariants = INTEGER_VARIANTS;
+            } else if (mValueType.equals(Double.class)) {
+                typeVariants = DOUBLE_VARIANTS;
+            } else {
+                typeVariants = new Class[1];
+                typeVariants[0] = mValueType;
+            }
+            for (Class typeVariant : typeVariants) {
+                args[0] = typeVariant;
+                try {
+                    returnVal = targetClass.getMethod(methodName, args);
+                    // change the value type to suit
+                    mValueType = typeVariant;
+                    return returnVal;
+                } catch (NoSuchMethodException e) {
+                    // Swallow the error and keep trying other variants
+                }
+            }
+            // If we got here, then no appropriate function was found
+            Log.e("PropertyValuesHolder",
+                    "Couldn't find setter/getter for property " + mPropertyName +
+                            "with value type "+ mValueType);
+        }
+
+        return returnVal;
+    }
+
+
+    /**
+     * Returns the setter or getter requested. This utility function checks whether the
+     * requested method exists in the propertyMapMap cache. If not, it calls another
+     * utility function to request the Method from the targetClass directly.
+     * @param targetClass The Class on which the requested method should exist.
+     * @param propertyMapMap The cache of setters/getters derived so far.
+     * @param prefix "set" or "get", for the setter or getter.
+     * @param valueType The type of parameter passed into the method (null for getter).
+     * @return Method the method associated with mPropertyName.
+     */
+    private Method setupSetterOrGetter(Class targetClass,
+            HashMap<Class, HashMap<String, Method>> propertyMapMap,
+            String prefix, Class valueType) {
+        Method setterOrGetter = 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
+            // TODO: can we store the setter/getter per Class instead of per Object?
+            propertyMapLock.writeLock().lock();
+            HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
+            if (propertyMap != null) {
+                setterOrGetter = propertyMap.get(mPropertyName);
+            }
+            if (setterOrGetter == null) {
+                setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
+                if (propertyMap == null) {
+                    propertyMap = new HashMap<String, Method>();
+                    propertyMapMap.put(targetClass, propertyMap);
+                }
+                propertyMap.put(mPropertyName, setterOrGetter);
+            }
+        } finally {
+            propertyMapLock.writeLock().unlock();
+        }
+        return setterOrGetter;
+    }
+
+    /**
+     * Utility function to get the setter from targetClass
+     * @param targetClass The Class on which the requested method should exist.
+     */
+    private void setupSetter(Class targetClass) {
+        mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
+    }
+
+    /**
+     * Utility function to get the getter from targetClass
+     */
+    private void setupGetter(Class targetClass) {
+        mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
+    }
+
+    /**
+     * Internal function (called from ObjectAnimator) to set up the setter and getter
+     * prior to running the animation. If the setter has not been manually set for this
+     * object, it will be derived automatically given the property name, target object, and
+     * types of values supplied. If no getter has been set, it will be supplied iff any of the
+     * supplied values was null. If there is a null value, then the getter (supplied or derived)
+     * will be called to set those null values to the current value of the property
+     * on the target object.
+     * @param target The object on which the setter (and possibly getter) exist.
+     */
+    void setupSetterAndGetter(Object target) {
+        Class targetClass = target.getClass();
+        if (mSetter == null) {
+            setupSetter(targetClass);
+        }
+        for (Keyframe kf : mKeyframeSet.mKeyframes) {
+            if (kf.getValue() == null) {
+                if (mGetter == null) {
+                    setupGetter(targetClass);
+                }
+                try {
+                    kf.setValue((T) mGetter.invoke(target));
+                } catch (InvocationTargetException e) {
+                    Log.e("PropertyValuesHolder", e.toString());
+                } catch (IllegalAccessException e) {
+                    Log.e("PropertyValuesHolder", e.toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * Utility function to set the value stored in a particular Keyframe. The value used is
+     * whatever the value is for the property name specified in the keyframe on the target object.
+     *
+     * @param target The target object from which the current value should be extracted.
+     * @param kf The keyframe which holds the property name and value.
+     */
+    private void setupValue(Object target, Keyframe kf) {
+        try {
+            if (mGetter == null) {
+                Class targetClass = target.getClass();
+                setupGetter(targetClass);
+            }
+            kf.setValue((T) mGetter.invoke(target));
+        } catch (InvocationTargetException e) {
+            Log.e("PropertyValuesHolder", e.toString());
+        } catch (IllegalAccessException e) {
+            Log.e("PropertyValuesHolder", e.toString());
+        }
+    }
+
+    /**
+     * This function is called by ObjectAnimator when setting the start values for an animation.
+     * The start values are set according to the current values in the target object. The
+     * property whose value is extracted is whatever is specified by the propertyName of this
+     * PropertyValuesHolder object.
+     *
+     * @param target The object which holds the start values that should be set.
+     */
+    void setupStartValue(Object target) {
+        setupValue(target, mKeyframeSet.mKeyframes.get(0));
+    }
+
+    /**
+     * This function is called by ObjectAnimator when setting the end values for an animation.
+     * The end values are set according to the current values in the target object. The
+     * property whose value is extracted is whatever is specified by the propertyName of this
+     * PropertyValuesHolder object.
+     *
+     * @param target The object which holds the start values that should be set.
+     */
+    void setupEndValue(Object target) {
+        setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1));
+    }
+
+    @Override
+    public PropertyValuesHolder clone() {
+        ArrayList<Keyframe> keyframes = mKeyframeSet.mKeyframes;
+        int numKeyframes = mKeyframeSet.mKeyframes.size();
+        Keyframe[] newKeyframes = new Keyframe[numKeyframes];
+        for (int i = 0; i < numKeyframes; ++i) {
+            newKeyframes[i] = keyframes.get(i).clone();
+        }
+        PropertyValuesHolder pvhClone = new PropertyValuesHolder(mPropertyName,
+                (Object[]) newKeyframes);
+        return pvhClone;
+    }
+    /**
+     * Internal function to set the value on the target object, using the setter set up
+     * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+     * to handle turning the value calculated by ValueAnimator into a value set on the object
+     * according to the name of the property.
+     * @param target The target object on which the value is set
+     */
+    void setAnimatedValue(Object target) {
+        if (mSetter != null) {
+            try {
+                mTmpValueArray[0] = mAnimatedValue;
+                mSetter.invoke(target, mTmpValueArray);
+            } catch (InvocationTargetException e) {
+                Log.e("PropertyValuesHolder", e.toString());
+            } catch (IllegalAccessException e) {
+                Log.e("PropertyValuesHolder", e.toString());
+            }
+        }
+    }
+
+    /**
+     * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
+     * to calculate animated values.
+     */
+    void init() {
+        if (mEvaluator == null) {
+            mEvaluator = (mValueType == int.class || mValueType == Integer.class) ? sIntEvaluator :
+                (mValueType == double.class || mValueType == Double.class) ? sDoubleEvaluator :
+                        sFloatEvaluator;
+        }
+    }
+
+    /**
+     * The TypeEvaluator will the automatically determined based on the type of values
+     * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so
+     * desired. This may be important in cases where either the type of the values supplied
+     * do not match the way that they should be interpolated between, or if the values
+     * are of a custom type or one not currently understood by the animation system. Currently,
+     * only values of type float, double, and int (and their Object equivalents, Float, Double,
+     * and Integer) are  correctly interpolated; all other types require setting a TypeEvaluator.
+     * @param evaluator
+     */
+	public void setEvaluator(TypeEvaluator evaluator) {
+        mEvaluator = evaluator;
+    }
+
+    /**
+     * Function used to calculate the value according to the evaluator set up for
+     * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue().
+     *
+     * @param fraction The elapsed, interpolated fraction of the animation.
+     * @return The calculated value at this point in the animation.
+     */
+    Object calculateValue(float fraction) {
+        mAnimatedValue = mKeyframeSet.getValue(fraction, mEvaluator);
+        return mAnimatedValue;
+    }
+
+    /**
+     * 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>ObjectAnimator</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>ObjectAnimator</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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value
+     * most recently calculated in calculateValue().
+     * @return
+     */
+    Object getAnimatedValue() {
+        return mAnimatedValue;
+    }
+}
\ No newline at end of file
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/TypeEvaluator.java b/core/java/android/animation/TypeEvaluator.java
new file mode 100644
index 0000000..fa49175
--- /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 ValueAnimator#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 ValueAnimator#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/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
new file mode 100755
index 0000000..f81b1ea
--- /dev/null
+++ b/core/java/android/animation/ValueAnimator.java
@@ -0,0 +1,1004 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * 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, ValueAnimator 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 ValueAnimator#setInterpolator(Interpolator)}.</p>
+ */
+public class ValueAnimator<T> extends Animator {
+
+    /**
+     * 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
+
+    /**
+     * Internal variables
+     * NOTE: This object implements the clone() method, making a deep copy of any referenced
+     * objects. As other non-trivial fields are added to this class, make sure to add logic
+     * to clone() to make deep copies of them.
+     */
+
+    // 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<ValueAnimator> sAnimations = new ArrayList<ValueAnimator>();
+
+    // The set of animations to be started on the next animation frame
+    private static final ArrayList<ValueAnimator> sPendingAnimations = new ArrayList<ValueAnimator>();
+
+    // 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<ValueAnimator> sEndingAnims = new ArrayList<ValueAnimator>();
+    private static final ArrayList<ValueAnimator> sDelayedAnims = new ArrayList<ValueAnimator>();
+    private static final ArrayList<ValueAnimator> sReadyAnims = new ArrayList<ValueAnimator>();
+
+    /**
+     * Flag that denotes whether the animation is set up and ready to go. Used to
+     * set up animation that has not yet been started.
+     */
+    boolean mInitialized = false;
+
+    //
+    // Backing variables
+    //
+
+    // How long the animation should last in ms
+    private long mDuration;
+
+    // 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 set of listeners to be sent events through the life of an animation.
+     */
+    private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
+
+    /**
+     * The property/value sets being animated.
+     */
+    PropertyValuesHolder[] mValues;
+
+    /**
+     * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values
+     * by property name during calls to getAnimatedValue(String).
+     */
+    HashMap<String, PropertyValuesHolder> mValuesMap;
+
+    /**
+     * 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 ValueAnimator object. This default constructor is primarily for
+     * use internally; the other constructors which take parameters are more generally
+     * useful.
+     */
+    public ValueAnimator() {
+    }
+
+    /**
+     * Constructs an ValueAnimator object with the specified duration and set of
+     * values. If the values are a set of PropertyValuesHolder objects, then these objects
+     * define the potentially multiple properties being animated and the values the properties are
+     * animated between. Otherwise, the values define a single set of values animated between.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param values The set of values to animate between. If these values are not
+     * PropertyValuesHolder objects, then there should be more than one value, since the values
+     * determine the interval to animate between.
+     */
+    public ValueAnimator(long duration, T...values) {
+        mDuration = duration;
+        if (values.length > 0) {
+            setValues(values);
+        }
+    }
+
+    /**
+     * Sets the values, per property, being animated between. This function is called internally
+     * by the constructors of ValueAnimator that take a list of values. But an ValueAnimator can
+     * be constructed without values and this method can be called to set the values manually
+     * instead.
+     *
+     * @param values The set of values, per property, being animated between.
+     */
+    public void setValues(PropertyValuesHolder... values) {
+        int numValues = values.length;
+        mValues = values;
+        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+        for (int i = 0; i < numValues; ++i) {
+            PropertyValuesHolder valuesHolder = (PropertyValuesHolder) values[i];
+            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
+        }
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    /**
+     * Returns the values that this ValueAnimator animates between. These values are stored in
+     * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list
+     * of value objects instead.
+     *
+     * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the
+     * values, per property, that define the animation.
+     */
+    public PropertyValuesHolder[] getValues() {
+        return mValues;
+    }
+
+    /**
+     * Sets the values to animate between for this animation. If <code>values</code> is
+     * a set of PropertyValuesHolder objects, these objects will become the set of properties
+     * animated and the values that those properties are animated between. Otherwise, this method
+     * will set only one set of values for the ValueAnimator. Also, if the values are not
+     * PropertyValuesHolder objects and if there are already multiple sets of
+     * values defined for this ValueAnimator via
+     * more than one PropertyValuesHolder objects, this method will set the values for
+     * the first of those objects.
+     *
+     * @param values The set of values to animate between.
+     */
+    public void setValues(T... values) {
+        if (mValues == null || mValues.length == 0) {
+            setValues(new PropertyValuesHolder[]{
+                    new PropertyValuesHolder("", (Object[])values)});
+        } else {
+            PropertyValuesHolder valuesHolder = mValues[0];
+            valuesHolder.setValues(values);
+        }
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    /**
+     * 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 (!mInitialized) {
+            int numValues = mValues.length;
+            for (int i = 0; i < numValues; ++i) {
+                mValues[i].init();
+            }
+            mCurrentIteration = 0;
+            mInitialized = true;
+        }
+    }
+
+
+    /**
+     * Sets the length of the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     */
+    public void setDuration(long duration) {
+        mDuration = duration;
+    }
+
+    /**
+     * Gets the length of the animation.
+     *
+     * @return The length of the animation, in milliseconds.
+     */
+    public long getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * 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 setCurrentPlayTime()
+     * 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) {
+        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 || mPlayingState == STOPPED) {
+            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<ValueAnimator> pendingCopy =
+                                (ArrayList<ValueAnimator>) sPendingAnimations.clone();
+                        sPendingAnimations.clear();
+                        int count = pendingCopy.size();
+                        for (int i = 0; i < count; ++i) {
+                            ValueAnimator anim = pendingCopy.get(i);
+                            // If the animation has a startDelay, place it on the delayed list
+                            if (anim.mStartDelay == 0 || anim.mPlayingState == ENDED ||
+                                    anim.mPlayingState == CANCELED) {
+                                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) {
+                        ValueAnimator 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) {
+                            ValueAnimator 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) {
+                        ValueAnimator 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;
+    }
+
+    /**
+     * 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>ValueAnimator</code> when there is just one
+     * 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>ValueAnimator</code>
+     * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
+     * is called during each animation frame, immediately after the value is calculated.
+     *
+     * @return animatedValue The value most recently calculated by this <code>ValueAnimator</code> for
+     * the single property being animated. If there are several properties being animated
+     * (specified by several PropertyValuesHolder objects in the constructor), this function
+     * returns the animated value for the first of those objects.
+     */
+    public Object getAnimatedValue() {
+        if (mValues != null && mValues.length > 0) {
+            return mValues[0].getAnimatedValue();
+        }
+        // Shouldn't get here; should always have values unless ValueAnimator was set up wrong
+        return null;
+    }
+
+    /**
+     * The most recent value calculated by this <code>ValueAnimator</code> for <code>propertyName</code>.
+     * The main purpose for this read-only property is to retrieve the value from the
+     * <code>ValueAnimator</code> during a call to
+     * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
+     * is called during each animation frame, immediately after the value is calculated.
+     *
+     * @return animatedValue The value most recently calculated for the named property
+     * by this <code>ValueAnimator</code>.
+     */
+    public Object getAnimatedValue(String propertyName) {
+        PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName);
+        if (valuesHolder != null) {
+            return valuesHolder.getAnimatedValue();
+        } else {
+            // At least avoid crashing if called with bogus propertyName
+            return null;
+        }
+    }
+
+    /**
+     * 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 all listeners from the set listening to frame updates for this animation.
+     */
+    public void removeAllUpdateListeners() {
+        if (mUpdateListeners == null) {
+            return;
+        }
+        mUpdateListeners.clear();
+        mUpdateListeners = null;
+    }
+
+    /**
+     * 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
+     */
+    @Override
+    public void setInterpolator(Interpolator value) {
+        if (value != null) {
+            mInterpolator = value;
+        }
+    }
+
+    /**
+     * Returns the timing interpolator that this ValueAnimator uses.
+     *
+     * @return The timing interpolator for this ValueAnimator.
+     */
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * 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.
+     *
+     * <p>If this ValueAnimator has only one set of values being animated between, this evaluator
+     * will be used for that set. If there are several sets of values being animated, which is
+     * the case if PropertyValuesHOlder objects were set on the ValueAnimator, then the evaluator
+     * is assigned just to the first PropertyValuesHolder object.</p>
+     *
+     * @param value the evaluator to be used this animation
+     */
+    public void setEvaluator(TypeEvaluator value) {
+        if (value != null && mValues != null && mValues.length > 0) {
+            mValues[0].setEvaluator(value);
+        }
+    }
+
+    /**
+     * Start the animation playing. This version of start() takes a boolean flag that indicates
+     * whether the animation should play in reverse. The flag is usually false, but may be set
+     * to true if called from the reverse() method/
+     *
+     * @param playBackwards Whether the ValueAnimator should start playing in reverse.
+     */
+    private void start(boolean playBackwards) {
+        mPlayingBackwards = playBackwards;
+        Looper looper = Looper.getMainLooper();
+        final boolean isUiThread;
+        if (looper != null) {
+            isUiThread = Thread.currentThread() == looper.getThread();
+        } else {
+            // ignore check if we don't have a Looper (this isn't an Activity)
+            isUiThread = true;
+        }
+        if ((mStartDelay == 0) && isUiThread) {
+            if (mListeners != null) {
+                ArrayList<AnimatorListener> tmpListeners =
+                        (ArrayList<AnimatorListener>) mListeners.clone();
+                for (AnimatorListener listener : tmpListeners) {
+                    listener.onAnimationStart(this);
+                }
+            }
+            // This sets the initial value of the animation, prior to actually starting it running
+            setCurrentPlayTime(getCurrentPlayTime());
+        }
+        mPlayingState = STOPPED;
+        mStartedDelay = false;
+        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);
+    }
+
+    @Override
+    public void start() {
+        start(false);
+    }
+
+    @Override
+    public void cancel() {
+        if (mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            for (AnimatorListener 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;
+    }
+
+    @Override
+    public void end() {
+        if (!sAnimations.contains(this) && !sPendingAnimations.contains(this)) {
+            // Special case if the animation has not yet started; get it ready for ending
+            mStartedDelay = false;
+            sPendingAnimations.add(this);
+            if (sAnimationHandler == null) {
+                sAnimationHandler = new AnimationHandler();
+            }
+            sAnimationHandler.sendEmptyMessage(ANIMATION_START);
+        }
+        // Just set the ENDED flag - this causes the animation to end the next time a frame
+        // is processed.
+        mPlayingState = ENDED;
+    }
+
+    @Override
+    public boolean isRunning() {
+        // ENDED or CANCELED indicate that it has been ended or canceled, but not processed yet
+        return (mPlayingState == RUNNING || mPlayingState == ENDED || mPlayingState == CANCELED);
+    }
+
+    /**
+     * Plays the ValueAnimator in reverse. If the animation is already running,
+     * it will stop itself and play backwards from the point reached when reverse was called.
+     * If the animation is not currently running, then it will start from the end and
+     * play backwards. This behavior is only set for the current animation; future playing
+     * of the animation will use the default behavior of playing forward.
+     */
+    public void reverse() {
+        mPlayingBackwards = !mPlayingBackwards;
+        if (mPlayingState == RUNNING) {
+            long currentTime = AnimationUtils.currentAnimationTimeMillis();
+            long currentPlayTime = currentTime - mStartTime;
+            long timeLeft = mDuration - currentPlayTime;
+            mStartTime = currentTime - timeLeft;
+        } else {
+            start(true);
+        }
+    }
+
+    /**
+     * 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);
+        mPlayingState = STOPPED;
+        if (mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            for (AnimatorListener listener : tmpListeners) {
+                listener.onAnimationEnd(this);
+            }
+        }
+    }
+
+    /**
+     * 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 (mStartDelay > 0 && mListeners != null) {
+            // Listeners were already notified in start() if startDelay is 0; this is
+            // just for delayed animations
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            for (AnimatorListener 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 (mPlayingState == CANCELED || mPlayingState == ENDED) {
+            // end the delay, process an animation frame to actually cancel it
+            return true;
+        }
+        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 (AnimatorListener 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;
+            mPlayingState = STOPPED;
+            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);
+        int numValues = mValues.length;
+        for (int i = 0; i < numValues; ++i) {
+            mValues[i].calculateValue(fraction);
+        }
+        if (mUpdateListeners != null) {
+            int numListeners = mUpdateListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                mUpdateListeners.get(i).onAnimationUpdate(this);
+            }
+        }
+    }
+
+    @Override
+    public ValueAnimator clone() {
+        final ValueAnimator anim = (ValueAnimator) super.clone();
+        if (mUpdateListeners != null) {
+            ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners;
+            anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
+            int numListeners = oldListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                anim.mUpdateListeners.add(oldListeners.get(i));
+            }
+        }
+        anim.mSeekTime = -1;
+        anim.mPlayingBackwards = false;
+        anim.mCurrentIteration = 0;
+        anim.mInitialized = false;
+        anim.mPlayingState = STOPPED;
+        anim.mStartedDelay = false;
+        PropertyValuesHolder[] oldValues = mValues;
+        if (oldValues != null) {
+            int numValues = oldValues.length;
+            anim.mValues = new PropertyValuesHolder[numValues];
+            for (int i = 0; i < numValues; ++i) {
+                anim.mValues[i] = oldValues[i].clone();
+            }
+            anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+            for (int i = 0; i < numValues; ++i) {
+                PropertyValuesHolder valuesHolder = mValues[i];
+                anim.mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
+            }
+        }
+        return anim;
+    }
+
+    /**
+     * Implementors of this interface can add themselves as update listeners
+     * to an <code>ValueAnimator</code> instance to receive callbacks on every animation
+     * frame, after the current frame's values have been calculated for that
+     * <code>ValueAnimator</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(ValueAnimator animation);
+
+    }
+}
\ 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..29f2e30
--- /dev/null
+++ b/core/java/android/app/ActionBar.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.view.Window;
+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, 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();
+
+    /**
+     * 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);
+
+    /**
+     * Add 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 addTab(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);
+
+    /**
+     * Returns the currently selected tab if in tabbed navigation mode and there is at least
+     * one tab present.
+     *
+     * @return The currently selected tab or null
+     */
+    public abstract Tab getSelectedTab();
+
+    /**
+     * Retrieve the current height of the ActionBar.
+     *
+     * @return The ActionBar's height
+     */
+    public abstract int getHeight();
+
+    /**
+     * Show the ActionBar if it is not currently showing.
+     * If the window hosting the ActionBar does not have the feature
+     * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application
+     * content to fit the new space available.
+     */
+    public abstract void show();
+
+    /**
+     * Hide the ActionBar if it is not currently showing.
+     * If the window hosting the ActionBar does not have the feature
+     * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application
+     * content to fit the new space available.
+     */
+    public abstract void hide();
+
+    /**
+     * @return <code>true</code> if the ActionBar is showing, <code>false</code> otherwise.
+     */
+    public abstract boolean isShowing();
+
+    /**
+     * 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);
+
+        /**
+         * Set a custom view to be used for this tab. This overrides values set by
+         * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+         *
+         * @param view Custom view to be used as a tab.
+         */
+        public abstract void setCustomView(View view);
+
+        /**
+         * Retrieve a previously set custom view for this tab.
+         *
+         * @return The custom view set by {@link #setCustomView(View)}.
+         */
+        public abstract View getCustomView();
+
+        /**
+         * Give this Tab an arbitrary object to hold for later use.
+         *
+         * @param obj Object to store
+         */
+        public abstract void setTag(Object obj);
+
+        /**
+         * @return This Tab's tag object.
+         */
+        public abstract Object getTag();
+
+        /**
+         * Set the {@link TabListener} that will handle switching to and from this tab.
+         * All tabs must have a TabListener set before being added to the ActionBar.
+         *
+         * @param listener Listener to handle tab selection events
+         */
+        public abstract void setTabListener(TabListener listener);
+
+        /**
+         * Select this tab. Only valid if the tab has been added to the action bar.
+         */
+        public abstract void select();
+    }
+
+    /**
+     * Callback interface invoked when a tab is focused, unfocused, added, or removed.
+     */
+    public interface TabListener {
+        /**
+         * Called when a tab enters the selected state.
+         *
+         * @param tab The tab that was selected
+         * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+         *        during a tab switch. The previous tab's unselect and this tab's select will be
+         *        executed in a single transaction.
+         */
+        public void onTabSelected(Tab tab, FragmentTransaction ft);
+
+        /**
+         * Called when a tab exits the selected state.
+         *
+         * @param tab The tab that was unselected
+         * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+         *        during a tab switch. This tab's unselect and the newly selected tab's select
+         *        will be executed in a single transaction.
+         */
+        public void onTabUnselected(Tab tab, FragmentTransaction ft);
+    }
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 72bf825..ee49d97 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,19 +16,22 @@
 
 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.CursorLoader;
 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 +42,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,7 +53,9 @@
 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.KeyEvent;
 import android.view.LayoutInflater;
@@ -58,18 +64,18 @@
 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.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -110,6 +116,7 @@
  * 
  * <p>Topics covered here:
  * <ol>
+ * <li><a href="#Fragments">Fragments</a>
  * <li><a href="#ActivityLifecycle">Activity Lifecycle</a>
  * <li><a href="#ConfigurationChanges">Configuration Changes</a>
  * <li><a href="#StartingActivities">Starting Activities and Getting Results</a>
@@ -118,6 +125,14 @@
  * <li><a href="#ProcessLifecycle">Process Lifecycle</a>
  * </ol>
  * 
+ * <a name="Fragments"></a>
+ * <h3>Fragments</h3>
+ *
+ * <p>Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, Activity
+ * implementations can make use of the {@link Fragment} class to better
+ * modularize their code, build more sophisticated user interfaces for larger
+ * screens, and help scale their application between small and large screens.
+ *
  * <a name="ActivityLifecycle"></a>
  * <h3>Activity Lifecycle</h3>
  *
@@ -592,7 +607,7 @@
  * or finished.
  */
 public class Activity extends ContextThemeWrapper
-        implements LayoutInflater.Factory,
+        implements LayoutInflater.Factory2,
         Window.Callback, KeyEvent.Callback,
         OnCreateContextMenuListener, ComponentCallbacks {
     private static final String TAG = "Activity";
@@ -604,9 +619,8 @@
     /** Start of user-defined activity results. */
     public static final int RESULT_FIRST_USER   = 1;
 
-    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 +642,28 @@
     private ComponentName mComponent;
     /*package*/ ActivityInfo mActivityInfo;
     /*package*/ ActivityThread mMainThread;
-    /*package*/ Object mLastNonConfigurationInstance;
-    /*package*/ HashMap<String,Object> mLastNonConfigurationChildInstances;
     Activity mParent;
     boolean mCalled;
+    boolean mCheckedForLoaderManager;
+    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 +671,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 FragmentManagerImpl mFragments = new FragmentManagerImpl();
+    
+    SparseArray<LoaderManagerImpl> mAllLoaderManagers;
+    LoaderManagerImpl mLoaderManager;
+    
     private static final class ManagedCursor {
         ManagedCursor(Cursor cursor) {
             mCursor = cursor;
@@ -677,24 +707,7 @@
     protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused};
 
     private Thread mUiThread;
-    private final Handler mHandler = new Handler();
-
-    // Used for debug only
-    /*
-    public Activity() {
-        ++sInstanceCount;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        super.finalize();
-        --sInstanceCount;
-    }
-    */
-
-    public static long getInstanceCount() {
-        return sInstanceCount;
-    }
+    final Handler mHandler = new Handler();
 
     /** Return the intent that started this activity. */
     public Intent getIntent() {
@@ -748,6 +761,30 @@
     }
 
     /**
+     * Return the LoaderManager for this fragment, creating it if needed.
+     */
+    public LoaderManager getLoaderManager() {
+        if (mLoaderManager != null) {
+            return mLoaderManager;
+        }
+        mCheckedForLoaderManager = true;
+        mLoaderManager = getLoaderManager(-1, mStarted, true);
+        return mLoaderManager;
+    }
+    
+    LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create) {
+        if (mAllLoaderManagers == null) {
+            mAllLoaderManagers = new SparseArray<LoaderManagerImpl>();
+        }
+        LoaderManagerImpl lm = mAllLoaderManagers.get(index);
+        if (lm == null && create) {
+            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 +838,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;
     }
 
@@ -933,6 +979,13 @@
      */
     protected void onStart() {
         mCalled = true;
+        mStarted = true;
+        if (mLoaderManager != null) {
+            mLoaderManager.doStart();
+        } else if (!mCheckedForLoaderManager) {
+            mLoaderManager = getLoaderManager(-1, mStarted, false);
+        }
+        mCheckedForLoaderManager = true;
     }
 
     /**
@@ -1085,6 +1138,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);
+        }
     }
 
     /**
@@ -1408,7 +1465,8 @@
      * {@link #onRetainNonConfigurationInstance()}.
      */
     public Object getLastNonConfigurationInstance() {
-        return mLastNonConfigurationInstance;
+        return mLastNonConfigurationInstances != null
+                ? mLastNonConfigurationInstances.activity : null;
     }
     
     /**
@@ -1420,6 +1478,11 @@
      * {@link #getLastNonConfigurationInstance()} in the new activity
      * instance.
      * 
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using a {@link Fragment} with
+     * {@link Fragment#setRetainInstance(boolean)
+     * Fragment.setRetainInstance(boolean}.</em>
+     *
      * <p>This function is called purely as an optimization, and you must
      * not rely on it being called.  When it is called, a number of guarantees
      * will be made to help optimize configuration switching:
@@ -1475,8 +1538,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;
     }
     
     /**
@@ -1490,17 +1554,88 @@
         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 and 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 {
+                    lm.doDestroy();
+                    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;
     }
     
     /**
+     * Return the FragmentManager for interacting with fragments associated
+     * with this activity.
+     */
+    public FragmentManager getFragmentManager() {
+        return mFragments;
+    }
+
+    /**
+     * Start a series of edit operations on the Fragments associated with
+     * this activity.
+     * @deprecated use {@link #getFragmentManager}.
+     */
+    @Deprecated
+    public FragmentTransaction openFragmentTransaction() {
+        return mFragments.openTransaction();
+    }
+    
+    void invalidateFragmentIndex(int index) {
+        //Log.v(TAG, "invalidateFragmentIndex: index=" + index);
+        if (mAllLoaderManagers != null) {
+            LoaderManagerImpl lm = mAllLoaderManagers.get(index);
+            if (lm != null) {
+                lm.doDestroy();
+            }
+            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
      * {@link #startManagingCursor} so that the activity will manage its
      * lifecycle for you.
      * 
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using {@link LoaderManager} instead, available
+     * via {@link #getLoaderManager()}.</em>
+     *
      * @param uri The URI of the content provider to query.
      * @param projection List of columns to return.
      * @param selection SQL WHERE clause.
@@ -1511,12 +1646,12 @@
      * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
      * @see #startManagingCursor
      * @hide
+     *
+     * @deprecated Use {@link CursorLoader} instead.
      */
-    public final Cursor managedQuery(Uri uri,
-                                     String[] projection,
-                                     String selection,
-                                     String sortOrder)
-    {
+    @Deprecated
+    public final Cursor managedQuery(Uri uri, String[] projection, String selection,
+            String sortOrder) {
         Cursor c = getContentResolver().query(uri, projection, selection, null, sortOrder);
         if (c != null) {
             startManagingCursor(c);
@@ -1531,6 +1666,10 @@
      * {@link #startManagingCursor} so that the activity will manage its
      * lifecycle for you.
      * 
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using {@link LoaderManager} instead, available
+     * via {@link #getLoaderManager()}.</em>
+     *
      * @param uri The URI of the content provider to query.
      * @param projection List of columns to return.
      * @param selection SQL WHERE clause.
@@ -1541,13 +1680,12 @@
      * 
      * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
      * @see #startManagingCursor
+     *
+     * @deprecated Use {@link CursorLoader} instead.
      */
-    public final Cursor managedQuery(Uri uri,
-                                     String[] projection,
-                                     String selection,
-                                     String[] selectionArgs,
-                                     String sortOrder)
-    {
+    @Deprecated
+    public final Cursor managedQuery(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
         Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
         if (c != null) {
             startManagingCursor(c);
@@ -1556,40 +1694,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
@@ -1597,11 +1701,18 @@
      * it will call {@link Cursor#requery} for you.  When the activity is
      * destroyed, all managed Cursors will be closed automatically.
      * 
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using {@link LoaderManager} instead, available
+     * via {@link #getLoaderManager()}.</em>
+     *
      * @param c The Cursor to be managed.
      * 
      * @see #managedQuery(android.net.Uri , String[], String, String[], String)
      * @see #stopManagingCursor
+     *
+     * @deprecated Use {@link CursorLoader} instead.
      */
+    @Deprecated
     public void startManagingCursor(Cursor c) {
         synchronized (mManagedCursors) {
             mManagedCursors.add(new ManagedCursor(c));
@@ -1616,7 +1727,10 @@
      * @param c The Cursor that was being managed.
      * 
      * @see #startManagingCursor
+     *
+     * @deprecated Use {@link CursorLoader} instead.
      */
+    @Deprecated
     public void stopManagingCursor(Cursor c) {
         synchronized (mManagedCursors) {
             final int N = mManagedCursors.size();
@@ -1671,7 +1785,54 @@
     public View findViewById(int id) {
         return getWindow().findViewById(id);
     }
-
+    
+    /**
+     * Retrieve a reference to this activity's ActionBar.
+     *
+     * @return The Activity's ActionBar, or null if it does not have one.
+     */
+    public ActionBar getActionBar() {
+        initActionBar();
+        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 (isChild() || !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.
+     * @deprecated use {@link #getFragmentManager}.
+     */
+    @Deprecated
+    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.
+     * @deprecated use {@link #getFragmentManager}.
+     */
+    @Deprecated
+    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.
@@ -1680,6 +1841,7 @@
      */
     public void setContentView(int layoutResID) {
         getWindow().setContentView(layoutResID);
+        initActionBar();
     }
 
     /**
@@ -1691,6 +1853,7 @@
      */
     public void setContentView(View view) {
         getWindow().setContentView(view);
+        initActionBar();
     }
 
     /**
@@ -1703,6 +1866,7 @@
      */
     public void setContentView(View view, ViewGroup.LayoutParams params) {
         getWindow().setContentView(view, params);
+        initActionBar();
     }
 
     /**
@@ -1714,6 +1878,7 @@
      */
     public void addContentView(View view, ViewGroup.LayoutParams params) {
         getWindow().addContentView(view, params);
+        initActionBar();
     }
 
     /**
@@ -1937,12 +2102,65 @@
     }
     
     /**
+     * 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.
+     * @deprecated use {@link #getFragmentManager}.
+     */
+    @Deprecated
+    public boolean popBackStack() {
+        return mFragments.popBackStack();
+    }
+
+    /**
+     * 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}.
+     * @deprecated use {@link #getFragmentManager}.
+     */
+    @Deprecated
+    public boolean popBackStack(String name, int flags) {
+        return mFragments.popBackStack(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}.
+     * @deprecated use {@link #getFragmentManager}.
+     */
+    @Deprecated
+    public boolean popBackStack(int id, int flags) {
+        return mFragments.popBackStack(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 (!mFragments.popBackStack()) {
+            finish();
+        }
     }
     
     /**
@@ -2180,7 +2398,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;
     }
@@ -2197,6 +2417,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;
@@ -2227,11 +2448,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;
@@ -2250,6 +2477,7 @@
     public void onPanelClosed(int featureId, Menu menu) {
         switch (featureId) {
             case Window.FEATURE_OPTIONS_PANEL:
+                mFragments.dispatchOptionsMenuClosed(menu);
                 onOptionsMenuClosed(menu);
                 break;
                 
@@ -2260,6 +2488,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>.
      * 
@@ -2482,6 +2719,9 @@
      * by the activity.  The default implementation calls through to
      * {@link #onCreateDialog(int)} for compatibility.
      *
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using a {@link DialogFragment} instead.</em>
+     *
      * <p>If you use {@link #showDialog(int)}, the activity will call through to
      * this method the first time, and hang onto it thereafter.  Any dialog
      * that is created by this method will automatically be saved and restored
@@ -2554,6 +2794,9 @@
      * will be made with the same id the first time this is called for a given
      * id.  From thereafter, the dialog will be automatically saved and restored.
      *
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using a {@link DialogFragment} instead.</em>
+     *
      * <p>Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog, Bundle)} will
      * be made to provide an opportunity to do any timely preparation.
      *
@@ -3101,6 +3344,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)}
@@ -3259,6 +3532,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().
@@ -3359,8 +3645,7 @@
      * @see #createPendingResult
      * @see #setResult(int)
      */
-    protected void onActivityResult(int requestCode, int resultCode,
-            Intent data) {
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     }
 
     /**
@@ -3744,9 +4029,12 @@
     }
 
     /**
-     * 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 does nothing and is for
+     * pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} apps.  Newer apps
+     * should use {@link #onCreateView(View, String, Context, AttributeSet)}.
      *
      * @see android.view.LayoutInflater#createView
      * @see android.view.Window#getLayoutInflater
@@ -3755,6 +4043,172 @@
         return null;
     }
 
+    /**
+     * Standard implementation of
+     * {@link android.view.LayoutInflater.Factory2#onCreateView(View, String, Context, AttributeSet)}
+     * 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(View parent, String name, Context context, AttributeSet attrs) {
+        if (!"fragment".equals(name)) {
+            return onCreateView(name, context, attrs);
+        }
+        
+        String fname = attrs.getAttributeValue(null, "class");
+        TypedArray a = 
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
+        if (fname == null) {
+            fname = a.getString(com.android.internal.R.styleable.Fragment_name);
+        }
+        int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID);
+        String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
+        a.recycle();
+        
+        int containerId = parent != null ? parent.getId() : 0;
+        if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
+            throw new IllegalArgumentException(attrs.getPositionDescription()
+                    + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
+        }
+
+        // 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 = id != View.NO_ID ? mFragments.findFragmentById(id) : null;
+        if (fragment == null && tag != null) {
+            fragment = mFragments.findFragmentByTag(tag);
+        }
+        if (fragment == null && containerId != View.NO_ID) {
+            fragment = mFragments.findFragmentById(containerId);
+        }
+
+        if (FragmentManagerImpl.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 != 0 ? id : containerId;
+            fragment.mContainerId = containerId;
+            fragment.mTag = tag;
+            fragment.mInLayout = true;
+            fragment.mImmediateActivity = this;
+            fragment.mFragmentManager = mFragments;
+            fragment.onInflate(attrs, fragment.mSavedFragmentState);
+            mFragments.addFragment(fragment, true);
+
+        } else if (fragment.mInLayout) {
+            // A fragment already exists and it is not one we restored from
+            // previous state.
+            throw new IllegalArgumentException(attrs.getPositionDescription()
+                    + ": Duplicate id 0x" + Integer.toHexString(id)
+                    + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
+                    + " with another fragment for " + fname);
+        } else {
+            // This fragment was retained from a previous instance; get it
+            // going now.
+            fragment.mInLayout = true;
+            fragment.mImmediateActivity = this;
+            // 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(attrs, fragment.mSavedFragmentState);
+            }
+            mFragments.moveToState(fragment);
+        }
+
+        if (fragment.mView == null) {
+            throw new IllegalStateException("Fragment " + fname
+                    + " did not create a view.");
+        }
+        if (id != 0) {
+            fragment.mView.setId(id);
+        }
+        if (fragment.mView.getTag() == null) {
+            fragment.mView.setTag(tag);
+        }
+        return fragment.mView;
+    }
+
+    /**
+     * Print the Activity's state into the given stream.  This gets invoked if
+     * you run "adb shell dumpsys activity <youractivityname>".
+     *
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param writer The PrintWriter to which you should dump your state.  This will be
+     * closed for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mFragments.dump("", fd, writer, args);
+    }
+
+    /**
+     * Bit indicating that this activity is "immersive" and should not be
+     * interrupted by notifications if possible.
+     *
+     * This value is initially set by the manifest property
+     * <code>android:immersive</code> but may be changed at runtime by
+     * {@link #setImmersive}.
+     *
+     * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE
+     * @hide
+     */
+    public boolean isImmersive() {
+        try {
+            return ActivityManagerNative.getDefault().isImmersive(mToken);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Adjust the current immersive mode setting.
+     * 
+     * Note that changing this value will have no effect on the activity's
+     * {@link android.content.pm.ActivityInfo} structure; that is, if
+     * <code>android:immersive</code> is set to <code>true</code>
+     * in the application's manifest entry for this activity, the {@link
+     * android.content.pm.ActivityInfo#flags ActivityInfo.flags} member will
+     * always have its {@link android.content.pm.ActivityInfo#FLAG_IMMERSIVE
+     * FLAG_IMMERSIVE} bit set.
+     *
+     * @see #isImmersive
+     * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE
+     * @hide
+     */
+    public void setImmersive(boolean i) {
+        try {
+            ActivityManagerNative.getDefault().setImmersive(mToken, i);
+        } catch (RemoteException e) {
+            // pass
+        }
+    }
+
+    /**
+     * 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) {
+        initActionBar();
+        if (mActionBar != null) {
+            return mActionBar.startActionMode(callback);
+        }
+        return null;
+    }
+
     // ------------------ Internal API ------------------
     
     final void setParent(Activity parent) {
@@ -3763,28 +4217,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().setFactory2(this);
         if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
             mWindow.setSoftInputMode(info.softInputMode);
         }
         mUiThread = Thread.currentThread();
-
+        
         mMainThread = aThread;
         mInstrumentation = instr;
         mToken = token;
@@ -3796,10 +4252,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());
         }
@@ -3811,23 +4267,41 @@
         return mParent != null ? mParent.getActivityToken() : mToken;
     }
 
+    final void performCreate(Bundle icicle) {
+        onCreate(icicle);
+        mFragments.dispatchActivityCreated();
+    }
+    
     final void performStart() {
+        mFragments.mStateSaved = false;
         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() {
+        mFragments.mStateSaved = false;
+
         synchronized (mManagedCursors) {
             final int N = mManagedCursors.size();
             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;
                 }
@@ -3850,7 +4324,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.
@@ -3865,6 +4341,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(
@@ -3874,6 +4354,7 @@
     }
 
     final void performPause() {
+        mFragments.dispatchPause();
         mCalled = false;
         onPause();
         if (!mCalled && getApplicationInfo().targetSdkVersion
@@ -3890,11 +4371,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) {
@@ -3919,6 +4413,15 @@
         mResumed = false;
     }
 
+    final void performDestroy() {
+        mWindow.destroy();
+        mFragments.dispatchDestroy();
+        onDestroy();
+        if (mLoaderManager != null) {
+            mLoaderManager.doDestroy();
+        }
+    }
+    
     final boolean isResumed() {
         return mResumed;
     }
@@ -3928,8 +4431,14 @@
         if (Config.LOGV) Log.v(
             TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
             + ", resCode=" + resultCode + ", data=" + data);
+        mFragments.mStateSaved = false;
         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 89812ab..b34c243 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1261,6 +1261,32 @@
             return true;
         }
 
+        case IS_IMMERSIVE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            boolean isit = isImmersive(token);
+            reply.writeNoException();
+            reply.writeInt(isit ? 1 : 0);
+            return true;
+        }
+
+        case SET_IMMERSIVE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            boolean imm = data.readInt() == 1;
+            setImmersive(token, imm);
+            reply.writeNoException();
+            return true;
+        }
+        
+        case IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            boolean isit = isTopActivityImmersive();
+            reply.writeNoException();
+            reply.writeInt(isit ? 1 : 0);
+            return true;
+        }
+
         case CRASH_APPLICATION_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int uid = data.readInt();
@@ -1315,6 +1341,31 @@
             return true;
         }
 
+        case CHECK_GRANT_URI_PERMISSION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int callingUid = data.readInt();
+            String targetPkg = data.readString();
+            Uri uri = Uri.CREATOR.createFromParcel(data);
+            int modeFlags = data.readInt();
+            int res = checkGrantUriPermission(callingUid, targetPkg, uri, modeFlags);
+            reply.writeNoException();
+            reply.writeInt(res);
+            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);
@@ -2841,6 +2892,46 @@
         reply.recycle();
     }
     
+    public void setImmersive(IBinder token, boolean immersive)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(immersive ? 1 : 0);
+        mRemote.transact(SET_IMMERSIVE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    public boolean isImmersive(IBinder token)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(IS_IMMERSIVE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() == 1;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+
+    public boolean isTopActivityImmersive()
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() == 1;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+
     public void crashApplication(int uid, int initialPid, String packageName,
             String message) throws RemoteException {
         Parcel data = Parcel.obtain();
@@ -2918,6 +3009,45 @@
         data.recycle();
         reply.recycle();
     }
+
+    public int checkGrantUriPermission(int callingUid, String targetPkg,
+            Uri uri, int modeFlags) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(callingUid);
+        data.writeString(targetPkg);
+        uri.writeToParcel(data, 0);
+        data.writeInt(modeFlags);
+        mRemote.transact(CHECK_GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+
+    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 63a5ff6..f3f7ee7 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;
@@ -63,6 +64,7 @@
 import android.util.LogPrinter;
 import android.util.Slog;
 import android.view.Display;
+import android.view.HardwareRenderer;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewManager;
@@ -199,8 +201,7 @@
         Window window;
         Activity parent;
         String embeddedID;
-        Object lastNonConfigurationInstance;
-        HashMap<String,Object> lastNonConfigurationChildInstances;
+        Activity.NonConfigurationInstances lastNonConfigurationInstances;
         boolean paused;
         boolean stopped;
         boolean hideForNow;
@@ -335,9 +336,9 @@
         }
     }
 
-    private static final class DumpServiceInfo {
+    private static final class DumpComponentInfo {
         FileDescriptor fd;
-        IBinder service;
+        IBinder token;
         String[] args;
         boolean dumped;
     }
@@ -361,12 +362,17 @@
         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 TWO_COUNT_COLUMNS_DB = "%20s %8d %20s %8d";
-        private static final String DB_INFO_FORMAT = "  %8d %8d %14d  %s";
+        private static final String DB_INFO_FORMAT = "  %8s %8s %14s %14s  %s";
 
         // Formatting for checkin service - update version if row format changes
         private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
@@ -587,9 +593,9 @@
         }
 
         public void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) {
-            DumpServiceInfo data = new DumpServiceInfo();
+            DumpComponentInfo data = new DumpComponentInfo();
             data.fd = fd;
-            data.service = servicetoken;
+            data.token = servicetoken;
             data.args = args;
             data.dumped = false;
             queueOrSendMessage(H.DUMP_SERVICE, data);
@@ -629,6 +635,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
@@ -652,6 +665,25 @@
         public void scheduleCrash(String msg) {
             queueOrSendMessage(H.SCHEDULE_CRASH, msg);
         }
+
+        public void dumpActivity(FileDescriptor fd, IBinder activitytoken, String[] args) {
+            DumpComponentInfo data = new DumpComponentInfo();
+            data.fd = fd;
+            data.token = activitytoken;
+            data.args = args;
+            data.dumped = false;
+            queueOrSendMessage(H.DUMP_ACTIVITY, data);
+            synchronized (data) {
+                while (!data.dumped) {
+                    try {
+                        data.wait();
+                    } catch (InterruptedException e) {
+                        // no need to do anything here, we will keep waiting until
+                        // dumped is set
+                    }
+                }
+            }
+        }
         
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -677,14 +709,14 @@
             long dalvikAllocated = dalvikMax - dalvikFree;
             long viewInstanceCount = ViewDebug.getViewInstanceCount();
             long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
-            long appContextInstanceCount = ContextImpl.getInstanceCount();
-            long activityInstanceCount = Activity.getInstanceCount();
+            long appContextInstanceCount = Debug.countInstancesOfClass(ContextImpl.class);
+            long activityInstanceCount = Debug.countInstancesOfClass(Activity.class);
             int globalAssetCount = AssetManager.getGlobalAssetCount();
             int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
             int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
             int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
             int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
-            int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount();
+            long openSslSocketCount = Debug.countInstancesOfClass(OpenSSLSocketImpl.class);
             long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
             SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
 
@@ -766,7 +798,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(',');
                 }
 
@@ -817,11 +849,15 @@
             int N = stats.dbStats.size();
             if (N > 0) {
                 pw.println(" DATABASES");
-                printRow(pw, "  %8s %8s %14s  %s", "pgsz", "dbsz", "Lookaside(b)", "Dbname");
+                printRow(pw, "  %8s %8s %14s %14s  %s", "pgsz", "dbsz", "Lookaside(b)", "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);
+                    printRow(pw, DB_INFO_FORMAT,
+                            (dbStats.pageSize > 0) ? String.valueOf(dbStats.pageSize) : " ",
+                            (dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ",
+                            (dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ",
+                            dbStats.cache, dbStats.dbName);
                 }
             }
 
@@ -875,6 +911,8 @@
         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;
+        public static final int DUMP_ACTIVITY           = 136;
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
                 switch (code) {
@@ -913,6 +951,8 @@
                     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";
+                    case DUMP_ACTIVITY: return "DUMP_ACTIVITY";
                 }
             }
             return "(unknown)";
@@ -1008,7 +1048,7 @@
                     scheduleGcIdler();
                     break;
                 case DUMP_SERVICE:
-                    handleDumpService((DumpServiceInfo)msg.obj);
+                    handleDumpService((DumpComponentInfo)msg.obj);
                     break;
                 case LOW_MEMORY:
                     handleLowMemory();
@@ -1039,14 +1079,39 @@
                     break;
                 case SCHEDULE_CRASH:
                     throw new RemoteServiceException((String)msg.obj);
+                case DUMP_HEAP:
+                    handleDumpHeap(msg.arg1 != 0, (DumpHeapData)msg.obj);
+                    break;
+                case DUMP_ACTIVITY:
+                    handleDumpActivity((DumpComponentInfo)msg.obj);
+                    break;
             }
             if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what);
         }
 
         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);
             }
         }
     }
@@ -1437,7 +1502,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;
@@ -1446,7 +1511,7 @@
             r.parent = parent;
             r.embeddedID = id;
             r.activityInfo = activityInfo;
-            r.lastNonConfigurationInstance = lastNonConfigurationInstance;
+            r.lastNonConfigurationInstances = lastNonConfigurationInstances;
         if (localLOGV) {
             ComponentName compname = intent.getComponent();
             String name;
@@ -1568,14 +1633,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) {
@@ -1988,9 +2051,9 @@
         }
     }
 
-    private void handleDumpService(DumpServiceInfo info) {
+    private void handleDumpService(DumpComponentInfo info) {
         try {
-            Service s = mServices.get(info.service);
+            Service s = mServices.get(info.token);
             if (s != null) {
                 PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd));
                 s.dump(info.fd, pw, info.args);
@@ -2004,6 +2067,22 @@
         }
     }
 
+    private void handleDumpActivity(DumpComponentInfo info) {
+        try {
+            ActivityClientRecord r = mActivities.get(info.token);
+            if (r != null && r.activity != null) {
+                PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd));
+                r.activity.dump(info.fd, pw, info.args);
+                pw.close();
+            }
+        } finally {
+            synchronized (info) {
+                info.dumped = true;
+                info.notifyAll();
+            }
+        }
+    }
+
     private final void handleServiceArgs(ServiceArgsData data) {
         Service s = mServices.get(data.token);
         if (s != null) {
@@ -2552,6 +2631,9 @@
             if (finishing) {
                 r.activity.mFinished = true;
             }
+            if (getNonConfigInstance) {
+                r.activity.mChangingConfigurations = true;
+            }
             if (!r.paused) {
                 try {
                     r.activity.mCalled = false;
@@ -2592,8 +2674,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(
@@ -2602,22 +2684,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) +
@@ -3029,6 +3099,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) {
@@ -3611,6 +3700,7 @@
     }
 
     public static final ActivityThread systemMain() {
+        HardwareRenderer.disable();
         ActivityThread thread = new ActivityThread();
         thread.attach(true);
         return thread;
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 61a8fc3..f0477e5 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -16,12 +16,16 @@
 
 package android.app;
 
+import com.android.internal.app.AlertController;
+
 import android.content.Context;
 import android.content.DialogInterface;
 import android.database.Cursor;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Message;
+import android.view.ContextThemeWrapper;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.WindowManager;
@@ -30,8 +34,6 @@
 import android.widget.ListAdapter;
 import android.widget.ListView;
 
-import com.android.internal.app.AlertController;
-
 /**
  * A subclass of Dialog that can display one, two or three buttons. If you only want to
  * display a String in this dialog box, use the setMessage() method.  If you
@@ -56,7 +58,10 @@
     private AlertController mAlert;
 
     protected AlertDialog(Context context) {
-        this(context, com.android.internal.R.style.Theme_Dialog_Alert);
+        this(context,
+                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB
+                        ? com.android.internal.R.style.Theme_Holo_Dialog_Alert
+                        : com.android.internal.R.style.Theme_Dialog_Alert);
     }
 
     protected AlertDialog(Context context, int theme) {
@@ -65,7 +70,10 @@
     }
 
     protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
-        super(context, com.android.internal.R.style.Theme_Dialog_Alert);
+        super(context,
+                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB
+                ? com.android.internal.R.style.Theme_Holo_Dialog_Alert
+                : com.android.internal.R.style.Theme_Dialog_Alert);
         setCancelable(cancelable);
         setOnCancelListener(cancelListener);
         mAlert = new AlertController(context, this, getWindow());
@@ -266,12 +274,16 @@
     public static class Builder {
         private final AlertController.AlertParams P;
         private int mTheme;
+        private Context mWrappedContext;
         
         /**
          * Constructor using a context for this builder and the {@link AlertDialog} it creates.
          */
         public Builder(Context context) {
-            this(context, com.android.internal.R.style.Theme_Dialog_Alert);
+            this(context,
+                    context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB
+                    ? com.android.internal.R.style.Theme_Holo_Dialog_Alert
+                    : com.android.internal.R.style.Theme_Dialog_Alert);
         }
 
         /**
@@ -284,6 +296,21 @@
         }
         
         /**
+         * Returns a {@link Context} with the appropriate theme for dialogs created by this Builder.
+         * Applications should use this Context for obtaining LayoutInflaters for inflating views
+         * that will be used in the resulting dialogs, as it will cause views to be inflated with
+         * the correct theme.
+         *
+         * @return A Context for built Dialogs.
+         */
+        public Context getContext() {
+            if (mWrappedContext == null) {
+                mWrappedContext = new ContextThemeWrapper(P.mContext, mTheme);
+            }
+            return mWrappedContext;
+        }
+
+        /**
          * Set the title using the given resource id.
          *
          * @return This Builder object to allow for chaining of calls to set methods
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..95689fc 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -403,6 +403,32 @@
             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;
+        }
+
+        case DUMP_ACTIVITY_TRANSACTION: {
+            data.enforceInterface(IApplicationThread.descriptor);
+            ParcelFileDescriptor fd = data.readFileDescriptor();
+            final IBinder activity = data.readStrongBinder();
+            final String[] args = data.readStringArray();
+            if (fd != null) {
+                dumpActivity(fd.getFileDescriptor(), activity, args);
+                try {
+                    fd.close();
+                } catch (IOException e) {
+                }
+            }
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -829,5 +855,33 @@
         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();
+    }
+
+    public void dumpActivity(FileDescriptor fd, IBinder token, String[] args)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeFileDescriptor(fd);
+        data.writeStrongBinder(token);
+        data.writeStringArray(args);
+        mRemote.transact(DUMP_ACTIVITY_TRANSACTION, data, null, 0);
+        data.recycle();
+    }
 }
 
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
new file mode 100644
index 0000000..e6cc0f9
--- /dev/null
+++ b/core/java/android/app/BackStackRecord.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.text.TextUtils;
+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;
+    final int mBreadCrumbTitleRes;
+    final CharSequence mBreadCrumbTitleText;
+    final int mBreadCrumbShortTitleRes;
+    final CharSequence mBreadCrumbShortTitleText;
+
+    public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
+        int numRemoved = 0;
+        BackStackRecord.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];
+
+        if (!bse.mAddToBackStack) {
+            throw new IllegalStateException("Not on back stack");
+        }
+
+        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;
+        mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
+        mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
+        mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
+        mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
+    }
+
+    public BackStackState(Parcel in) {
+        mOps = in.createIntArray();
+        mTransition = in.readInt();
+        mTransitionStyle = in.readInt();
+        mName = in.readString();
+        mIndex = in.readInt();
+        mBreadCrumbTitleRes = in.readInt();
+        mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mBreadCrumbShortTitleRes = in.readInt();
+        mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+    }
+
+    public BackStackRecord instantiate(FragmentManagerImpl fm) {
+        BackStackRecord bse = new BackStackRecord(fm);
+        int pos = 0;
+        while (pos < mOps.length) {
+            BackStackRecord.Op op = new BackStackRecord.Op();
+            op.cmd = mOps[pos++];
+            if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
+                    "BSE " + bse + " set base fragment #" + mOps[pos]);
+            Fragment f = fm.mActive.get(mOps[pos++]);
+            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++) {
+                    if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
+                            "BSE " + bse + " set remove fragment #" + mOps[pos]);
+                    Fragment r = fm.mActive.get(mOps[pos++]);
+                    op.removed.add(r);
+                }
+            }
+            bse.addOp(op);
+        }
+        bse.mTransition = mTransition;
+        bse.mTransitionStyle = mTransitionStyle;
+        bse.mName = mName;
+        bse.mIndex = mIndex;
+        bse.mAddToBackStack = true;
+        bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
+        bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
+        bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
+        bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
+        bse.bumpBackStackNesting(1);
+        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);
+        dest.writeInt(mBreadCrumbTitleRes);
+        TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
+        dest.writeInt(mBreadCrumbShortTitleRes);
+        TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
+    }
+
+    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 BackStackRecord implements FragmentTransaction,
+        FragmentManager.BackStackEntry, Runnable {
+    static final String TAG = "BackStackEntry";
+
+    final FragmentManagerImpl 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;
+
+    int mBreadCrumbTitleRes;
+    CharSequence mBreadCrumbTitleText;
+    int mBreadCrumbShortTitleRes;
+    CharSequence mBreadCrumbShortTitleText;
+
+    public BackStackRecord(FragmentManagerImpl manager) {
+        mManager = manager;
+    }
+
+    public int getId() {
+        return mIndex;
+    }
+
+    public CharSequence getBreadCrumbTitle() {
+        if (mBreadCrumbTitleRes != 0) {
+            return mManager.mActivity.getText(mBreadCrumbTitleRes);
+        }
+        return mBreadCrumbTitleText;
+    }
+
+    public CharSequence getBreadCrumbShortTitle() {
+        if (mBreadCrumbShortTitleRes != 0) {
+            return mManager.mActivity.getText(mBreadCrumbShortTitleRes);
+        }
+        return mBreadCrumbShortTitleText;
+    }
+
+    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;
+        fragment.mFragmentManager = mManager;
+
+        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 FragmentTransaction setBreadCrumbTitle(int res) {
+        mBreadCrumbTitleRes = res;
+        mBreadCrumbTitleText = null;
+        return this;
+    }
+
+    public FragmentTransaction setBreadCrumbTitle(CharSequence text) {
+        mBreadCrumbTitleRes = 0;
+        mBreadCrumbTitleText = text;
+        return this;
+    }
+
+    public FragmentTransaction setBreadCrumbShortTitle(int res) {
+        mBreadCrumbShortTitleRes = res;
+        mBreadCrumbShortTitleText = null;
+        return this;
+    }
+
+    public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) {
+        mBreadCrumbShortTitleRes = 0;
+        mBreadCrumbShortTitleText = text;
+        return this;
+    }
+
+    void bumpBackStackNesting(int amt) {
+        if (!mAddToBackStack) {
+            return;
+        }
+        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this
+                + " by " + amt);
+        Op op = mHead;
+        while (op != null) {
+            op.fragment.mBackStackNesting += amt;
+            if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+                    + op.fragment + " to " + op.fragment.mBackStackNesting);
+            if (op.removed != null) {
+                for (int i=op.removed.size()-1; i>=0; i--) {
+                    Fragment r = op.removed.get(i);
+                    r.mBackStackNesting += amt;
+                    if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+                            + r + " to " + r.mBackStackNesting);
+                }
+            }
+            op = op.next;
+        }
+    }
+
+    public int commit() {
+        if (mCommitted) throw new IllegalStateException("commit already called");
+        if (FragmentManagerImpl.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 (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this);
+
+        if (mAddToBackStack) {
+            if (mIndex < 0) {
+                throw new IllegalStateException("addToBackStack() called after commit()");
+            }
+        }
+
+        bumpBackStackNesting(1);
+
+        Op op = mHead;
+        while (op != null) {
+            switch (op.cmd) {
+                case OP_ADD: {
+                    Fragment f = op.fragment;
+                    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 (FragmentManagerImpl.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);
+                                old.mNextAnim = op.exitAnim;
+                                if (mAddToBackStack) {
+                                    old.mBackStackNesting += 1;
+                                    if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+                                            + old + " to " + old.mBackStackNesting);
+                                }
+                                mManager.removeFragment(old, mTransition, mTransitionStyle);
+                            }
+                        }
+                    }
+                    f.mNextAnim = op.enterAnim;
+                    mManager.addFragment(f, false);
+                } break;
+                case OP_REMOVE: {
+                    Fragment f = op.fragment;
+                    f.mNextAnim = op.exitAnim;
+                    mManager.removeFragment(f, mTransition, mTransitionStyle);
+                } break;
+                case OP_HIDE: {
+                    Fragment f = op.fragment;
+                    f.mNextAnim = op.exitAnim;
+                    mManager.hideFragment(f, mTransition, mTransitionStyle);
+                } break;
+                case OP_SHOW: {
+                    Fragment f = op.fragment;
+                    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 (mAddToBackStack) {
+            mManager.addBackStackState(this);
+        }
+    }
+
+    public void popFromBackStack(boolean doStateMove) {
+        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "popFromBackStack: " + this);
+
+        bumpBackStackNesting(-1);
+
+        Op op = mTail;
+        while (op != null) {
+            switch (op.cmd) {
+                case OP_ADD: {
+                    Fragment f = op.fragment;
+                    f.mImmediateActivity = null;
+                    mManager.removeFragment(f,
+                            FragmentManagerImpl.reverseTransit(mTransition),
+                            mTransitionStyle);
+                } break;
+                case OP_REPLACE: {
+                    Fragment f = op.fragment;
+                    f.mImmediateActivity = null;
+                    mManager.removeFragment(f,
+                            FragmentManagerImpl.reverseTransit(mTransition),
+                            mTransitionStyle);
+                    if (op.removed != null) {
+                        for (int i=0; i<op.removed.size(); i++) {
+                            Fragment old = op.removed.get(i);
+                            f.mImmediateActivity = mManager.mActivity;
+                            mManager.addFragment(old, false);
+                        }
+                    }
+                } break;
+                case OP_REMOVE: {
+                    Fragment f = op.fragment;
+                    f.mImmediateActivity = mManager.mActivity;
+                    mManager.addFragment(f, false);
+                } break;
+                case OP_HIDE: {
+                    Fragment f = op.fragment;
+                    mManager.showFragment(f,
+                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+                } break;
+                case OP_SHOW: {
+                    Fragment f = op.fragment;
+                    mManager.hideFragment(f,
+                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+                } break;
+                default: {
+                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
+                }
+            }
+
+            op = op.prev;
+        }
+
+        if (doStateMove) {
+            mManager.moveToState(mManager.mCurState,
+                    FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
+        }
+
+        if (mIndex >= 0) {
+            mManager.freeBackStackIndex(mIndex);
+            mIndex = -1;
+        }
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public int getTransition() {
+        return mTransition;
+    }
+
+    public int getTransitionStyle() {
+        return mTransitionStyle;
+    }
+
+    public boolean isEmpty() {
+        return mNumOp == 0;
+    }
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3c2a7b8..918ecf1 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;
@@ -72,6 +75,7 @@
 import android.net.wifi.IWifiManager;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.DropBoxManager;
 import android.os.Environment;
@@ -88,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;
@@ -170,6 +174,7 @@
     private static ThrottleManager sThrottleManager;
     private static WifiManager sWifiManager;
     private static LocationManager sLocationManager;
+    private static CountryDetector sCountryDetector;
     private static NfcManager sNfcManager;
     private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
             new HashMap<String, SharedPreferencesImpl>();
@@ -212,23 +217,8 @@
     private File mExternalFilesDir;
     private File mExternalCacheDir;
 
-    private static long sInstanceCount = 0;
-
     private static final String[] EMPTY_FILE_LIST = {};
 
-    // For debug only
-    /*
-    @Override
-    protected void finalize() throws Throwable {
-        super.finalize();
-        --sInstanceCount;
-    }
-    */
-
-    public static long getInstanceCount() {
-        return sInstanceCount;
-    }
-
     @Override
     public AssetManager getAssets() {
         return mResources.getAssets();
@@ -545,6 +535,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);
@@ -622,7 +621,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
@@ -946,6 +946,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)) {
@@ -1066,8 +1068,13 @@
     private NotificationManager getNotificationManager() {
         synchronized (mSync) {
             if (mNotificationManager == null) {
+                final Context outerContext = getOuterContext();
                 mNotificationManager = new NotificationManager(
-                        new ContextThemeWrapper(getOuterContext(), com.android.internal.R.style.Theme_Dialog),
+                        new ContextThemeWrapper(outerContext,
+                                outerContext.getApplicationInfo().targetSdkVersion >=
+                                    Build.VERSION_CODES.HONEYCOMB
+                                ? com.android.internal.R.style.Theme_Holo_Dialog
+                                : com.android.internal.R.style.Theme_Dialog),
                         mMainThread.getHandler());
             }
         }
@@ -1114,6 +1121,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) {
@@ -1502,8 +1520,6 @@
     }
 
     ContextImpl() {
-        // For debug only
-        //++sInstanceCount;
         mOuterContext = this;
     }
 
@@ -1514,7 +1530,6 @@
      * @param context Existing application context.
      */
     public ContextImpl(ContextImpl context) {
-        ++sInstanceCount;
         mPackageInfo = context.mPackageInfo;
         mResources = context.mResources;
         mMainThread = context.mMainThread;
@@ -2820,6 +2835,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) {
@@ -2881,6 +2903,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/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 8ba480d..37f8738 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
+import android.os.Build;
 import android.os.Bundle;
 import android.text.TextUtils.TruncateAt;
 import android.view.LayoutInflater;
@@ -82,7 +83,9 @@
             int year,
             int monthOfYear,
             int dayOfMonth) {
-        this(context, com.android.internal.R.style.Theme_Dialog_Alert,
+        this(context, context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB
+                        ? com.android.internal.R.style.Theme_Holo_Dialog_Alert
+                        : com.android.internal.R.style.Theme_Dialog_Alert,
                 callBack, year, monthOfYear, dayOfMonth);
     }
 
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index da8c9e5..a178c04 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,18 +16,22 @@
 
 package android.app;
 
+import com.android.internal.app.ActionBarImpl;
 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.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.view.ActionMode;
 import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.KeyEvent;
@@ -36,13 +40,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;
@@ -76,6 +79,7 @@
     final WindowManager mWindowManager;
     Window mWindow;
     View mDecor;
+    private ActionBarImpl mActionBar;
     /**
      * This field should be made private, so it is hidden from the SDK.
      * {@hide}
@@ -137,7 +141,10 @@
      */
     public Dialog(Context context, int theme) {
         mContext = new ContextThemeWrapper(
-            context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme);
+            context, theme == 0 ? 
+                    (context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB
+                            ? com.android.internal.R.style.Theme_Holo_Dialog
+                                    : com.android.internal.R.style.Theme_Dialog) : theme);
         mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
         Window w = PolicyManager.makeNewWindow(mContext);
         mWindow = w;
@@ -170,13 +177,22 @@
     /**
      * Retrieve the Context this Dialog is running in.
      * 
-     * @return Context The Context that was supplied to the constructor.
+     * @return Context The Context used by the Dialog.
      */
     public final Context getContext() {
         return mContext;
     }
 
     /**
+     * Retrieve the {@link ActionBar} attached to this dialog, if present.
+     *
+     * @return The ActionBar attached to the dialog or null if no ActionBar is present.
+     */
+    public ActionBar getActionBar() {
+        return mActionBar;
+    }
+
+    /**
      * Sets the Activity that owns this dialog. An example use: This Dialog will
      * use the suggested volume control stream of the Activity.
      * 
@@ -216,6 +232,9 @@
     public void show() {
         if (mShowing) {
             if (mDecor != null) {
+                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
+                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
+                }
                 mDecor.setVisibility(View.VISIBLE);
             }
             return;
@@ -227,6 +246,11 @@
 
         onStart();
         mDecor = mWindow.getDecorView();
+
+        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
+            mActionBar = new ActionBarImpl(this);
+        }
+
         WindowManager.LayoutParams l = mWindow.getAttributes();
         if ((l.softInputMode
                 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
@@ -310,7 +334,7 @@
     }
 
     /**
-     * Similar to {@link Activity#onCreate}, you should initialized your dialog
+     * Similar to {@link Activity#onCreate}, you should initialize your dialog
      * in this method, including calling {@link #setContentView}.
      * @param savedInstanceState If this dialog is being reinitalized after a
      *     the hosting activity was previously shut down, holds the result from
@@ -775,6 +799,13 @@
     }
 
     /**
+     * @see Activity#invalidateOptionsMenu()
+     */
+    public void invalidateOptionsMenu() {
+        mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+    }
+
+    /**
      * @see Activity#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)
      */
     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
@@ -832,8 +863,15 @@
         }
     }
 
+    public ActionMode onStartActionMode(ActionMode.Callback callback) {
+        if (mActionBar != null) {
+            return mActionBar.startActionMode(callback);
+        }
+        return null;
+    }
+
     /**
-     * @return The activity associated with this dialog, or null if there is no assocaited activity.
+     * @return The activity associated with this dialog, or null if there is no associated activity.
      */
     private ComponentName getAssociatedActivity() {
         Activity activity = mOwnerActivity;
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
new file mode 100644
index 0000000..8e2389b
--- /dev/null
+++ b/core/java/android/app/DialogFragment.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.
+ *
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#Lifecycle">Lifecycle</a>
+ * <li><a href="#BasicDialog">Basic Dialog</a>
+ * <li><a href="#AlertDialog">Alert Dialog</a>
+ * <li><a href="#DialogOrEmbed">Selecting Between Dialog or Embedding</a>
+ * </ol>
+ *
+ * <a name="Lifecycle"></a>
+ * <h3>Lifecycle</h3>
+ *
+ * <p>DialogFragment does various things to keep the fragment's lifecycle
+ * driving it, instead of the Dialog.  Note that dialogs are generally
+ * autonomous entities -- they are their own window, receiving their own
+ * input events, and often deciding on their own when to disappear (by
+ * receiving a back key event or the user clicking on a button).
+ *
+ * <p>DialogFragment needs to ensure that what is happening with the Fragment
+ * and Dialog states remains consistent.  To do this, it watches for dismiss
+ * events from the dialog and takes are of removing its own state when they
+ * happen.  This means you should use {@link #show(FragmentManager, String)}
+ * or {@link #show(FragmentTransaction, String)} to add an instance of
+ * DialogFragment to your UI, as these keep track of how DialogFragment should
+ * remove itself when the dialog is dismissed.
+ *
+ * <a name="BasicDialog"></a>
+ * <h3>Basic Dialog</h3>
+ *
+ * <p>The simplest use of DialogFragment is as a floating container for the
+ * fragment's view hierarchy.  A simple implementation may look like this:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialog.java
+ *      dialog}
+ *
+ * <p>An example showDialog() method on the Activity could be:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialog.java
+ *      add_dialog}
+ *
+ * <p>This removes any currently shown dialog, creates a new DialogFragment
+ * with an argument, and shows it as a new state on the back stack.  When the
+ * transaction is popped, the current DialogFragment and its Dialog will be
+ * destroyed, and the previous one (if any) re-shown.  Note that in this case
+ * DialogFragment will take care of popping the transaction of the Dialog
+ * is dismissed separately from it.
+ *
+ * <a name="AlertDialog"></a>
+ * <h3>Alert Dialog</h3>
+ *
+ * <p>Instead of (or in addition to) implementing {@link #onCreateView} to
+ * generate the view hierarchy inside of a dialog, you may implement
+ * {@link #onCreateDialog(Bundle)} to create your own custom Dialog object.
+ *
+ * <p>This is most useful for creating an {@link AlertDialog}, allowing you
+ * to display standard alerts to the user that are managed by a fragment.
+ * A simple example implementation of this is:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentAlertDialog.java
+ *      dialog}
+ *
+ * <p>The activity creating this fragment may have the following methods to
+ * show the dialog and receive results from it:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentAlertDialog.java
+ *      activity}
+ *
+ * <p>Note that in this case the fragment is not placed on the back stack, it
+ * is just added as an indefinitely running fragment.  Because dialogs normally
+ * are modal, this will still operate as a back stack, since the dialog will
+ * capture user input until it is dismissed.  When it is dismissed, DialogFragment
+ * will take care of removing itself from its fragment manager.
+ *
+ * <a name="DialogOrEmbed"></a>
+ * <h3>Selecting Between Dialog or Embedding</h3>
+ *
+ * <p>A DialogFragment can still optionally be used as a normal fragment, if
+ * desired.  This is useful if you have a fragment that in some cases should
+ * be shown as a dialog and others embedded in a larger UI.  This behavior
+ * will normally be automatically selected for you based on how you are using
+ * the fragment, but can be customized with {@link #setShowsDialog(boolean)}.
+ *
+ * <p>For example, here is a simple dialog fragment:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
+ *      dialog}
+ *
+ * <p>An instance of this fragment can be created and shown as a dialog:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
+ *      show_dialog}
+ *
+ * <p>It can also be added as content in a view hierarchy:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
+ *      embed}
+ */
+public class DialogFragment extends Fragment
+        implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
+
+    /**
+     * Style for {@link #setStyle(int, int)}: a basic,
+     * normal dialog.
+     */
+    public static final int STYLE_NORMAL = 0;
+
+    /**
+     * Style for {@link #setStyle(int, int)}: don't include
+     * a title area.
+     */
+    public static final int STYLE_NO_TITLE = 1;
+
+    /**
+     * Style for {@link #setStyle(int, int)}: 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 #setStyle(int, int)}: 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_SHOWS_DIALOG = "android:showsDialog";
+    private static final String SAVED_BACK_STACK_ID = "android:backStackId";
+
+    int mStyle = STYLE_NORMAL;
+    int mTheme = 0;
+    boolean mCancelable = true;
+    boolean mShowsDialog = true;
+    int mBackStackId = -1;
+
+    Dialog mDialog;
+    boolean mDestroyed;
+    boolean mRemoved;
+
+    public DialogFragment() {
+    }
+
+    /**
+     * Call 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.  Calling this after the fragment's Dialog is
+     * created will have no effect.
+     *
+     * @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 void setStyle(int style, int theme) {
+        mStyle = style;
+        if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
+            mTheme = com.android.internal.R.style.Theme_Holo_Dialog_NoFrame;
+        }
+        if (theme != 0) {
+            mTheme = theme;
+        }
+    }
+
+    /**
+     * @deprecated Please use {@link #show(FragmentManager, String)}.
+     */
+    @Deprecated
+    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 FragmentManager.  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 manager The FragmentManager 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(FragmentManager manager, String tag) {
+        FragmentTransaction ft = manager.openTransaction();
+        ft.add(this, tag);
+        ft.commit();
+    }
+
+    /**
+     * Display the dialog, adding the fragment using an existing transaction
+     * and then committing the transaction.
+     * @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(FragmentTransaction transaction, String tag) {
+        transaction.add(this, tag);
+        mRemoved = false;
+        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();
+            mDialog = null;
+        }
+        mRemoved = true;
+        if (mBackStackId >= 0) {
+            getFragmentManager().popBackStack(mBackStackId,
+                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
+            mBackStackId = -1;
+        } else {
+            FragmentTransaction ft = getFragmentManager().openTransaction();
+            ft.remove(this);
+            ft.commit();
+        }
+    }
+
+    public Dialog getDialog() {
+        return mDialog;
+    }
+
+    public int getTheme() {
+        return mTheme;
+    }
+
+    /**
+     * Control whether the shown Dialog is cancelable.  Use this instead of
+     * directly calling {@link Dialog#setCancelable(boolean)
+     * Dialog.setCancelable(boolean)}, because DialogFragment needs to change
+     * its behavior based on this.
+     *
+     * @param cancelable If true, the dialog is cancelable.  The default
+     * is true.
+     */
+    public void setCancelable(boolean cancelable) {
+        mCancelable = cancelable;
+        if (mDialog != null) mDialog.setCancelable(cancelable);
+    }
+
+    /**
+     * Return the current value of {@link #setCancelable(boolean)}.
+     */
+    public boolean getCancelable() {
+        return mCancelable;
+    }
+
+    /**
+     * Controls whether this fragment should be shown in a dialog.  If not
+     * set, no Dialog will be created in {@link #onActivityCreated(Bundle)},
+     * and the fragment's view hierarchy will thus not be added to it.  This
+     * allows you to instead use it as a normal fragment (embedded inside of
+     * its activity).
+     *
+     * <p>This is normally set for you based on whether the fragment is
+     * associated with a container view ID passed to
+     * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}.
+     * If the fragment was added with a container, setShowsDialog will be
+     * initialized to false; otherwise, it will be true.
+     *
+     * @param showsDialog If true, the fragment will be displayed in a Dialog.
+     * If false, no Dialog will be created and the fragment's view hierarchly
+     * left undisturbed.
+     */
+    public void setShowsDialog(boolean showsDialog) {
+        mShowsDialog = showsDialog;
+    }
+
+    /**
+     * Return the current value of {@link #setShowsDialog(boolean)}.
+     */
+    public boolean getShowsDialog() {
+        return mShowsDialog;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mShowsDialog = mContainerId == 0;
+
+        if (savedInstanceState != null) {
+            mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
+            mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
+            mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
+            mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
+            mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
+        }
+    }
+
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return new Dialog(getActivity(), getTheme());
+    }
+
+    public void onCancel(DialogInterface dialog) {
+    }
+
+    public void onDismiss(DialogInterface dialog) {
+        if (!mRemoved) {
+            dismiss();
+        }
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        if (!mShowsDialog) {
+            return;
+        }
+
+        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();
+        if (mDialog != null) {
+            mRemoved = false;
+            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);
+            }
+        }
+        if (mStyle != STYLE_NORMAL) {
+            outState.putInt(SAVED_STYLE, mStyle);
+        }
+        if (mTheme != 0) {
+            outState.putInt(SAVED_THEME, mTheme);
+        }
+        if (!mCancelable) {
+            outState.putBoolean(SAVED_CANCELABLE, mCancelable);
+        }
+        if (!mShowsDialog) {
+            outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
+        }
+        if (mBackStackId != -1) {
+            outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mDialog != null) {
+            mDialog.hide();
+        }
+    }
+
+    /**
+     * Remove dialog.
+     */
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mDestroyed = true;
+        if (mDialog != null) {
+            // Set removed here because this dismissal is just to hide
+            // the dialog -- we don't want this to cause the fragment to
+            // actually be removed.
+            mRemoved = true;
+            mDialog.dismiss();
+            mDialog = null;
+        }
+    }
+}
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index c476b8f..013032c 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -335,8 +335,8 @@
                 throw new NullPointerException();
             }
             String scheme = uri.getScheme();
-            if (scheme == null || !scheme.equals("http")) {
-                throw new IllegalArgumentException("Can only download HTTP URIs: " + uri);
+            if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
+                throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri);
             }
             mUri = uri;
         }
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
new file mode 100644
index 0000000..12bf7e5
--- /dev/null
+++ b/core/java/android/app/Fragment.java
@@ -0,0 +1,1208 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.animation.Animator;
+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.AndroidRuntimeException;
+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.widget.AdapterView;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+final class FragmentState implements Parcelable {
+    final String mClassName;
+    final int mIndex;
+    final boolean mFromLayout;
+    final int mFragmentId;
+    final int mContainerId;
+    final String mTag;
+    final boolean mRetainInstance;
+    final Bundle mArguments;
+    
+    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;
+        mArguments = frag.mArguments;
+    }
+    
+    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;
+        mArguments = in.readBundle();
+        mSavedFragmentState = in.readBundle();
+    }
+    
+    public Fragment instantiate(Activity activity) {
+        if (mInstance != null) {
+            return mInstance;
+        }
+        
+        mInstance = Fragment.instantiate(activity, mClassName, mArguments);
+        
+        if (mSavedFragmentState != null) {
+            mSavedFragmentState.setClassLoader(activity.getClassLoader());
+            mInstance.mSavedFragmentState = mSavedFragmentState;
+        }
+        mInstance.setIndex(mIndex);
+        mInstance.mFromLayout = mFromLayout;
+        mInstance.mFragmentId = mFragmentId;
+        mInstance.mContainerId = mContainerId;
+        mInstance.mTag = mTag;
+        mInstance.mRetainInstance = mRetainInstance;
+        mInstance.mFragmentManager = activity.mFragments;
+        
+        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(mArguments);
+        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}.  Interaction with fragments
+ * is done through {@link FragmentManager}, which can be obtained via
+ * {@link Activity#getFragmentManager() Activity.getFragmentManager()} and
+ * {@link Fragment#getFragmentManager() Fragment.getFragmentManager()}.
+ *
+ * <p>The Fragment class can be used many ways to achieve a wide variety of
+ * results.  It is core, it represents a particular operation or interface
+ * that is running within a larger {@link Activity}.  A Fragment is closely
+ * tied to the Activity it is in, and can not be used apart from one.  Though
+ * Fragment defines its own lifecycle, that lifecycle is dependent on its
+ * activity: if the activity is stopped, no fragments inside of it can be
+ * started; when the activity is destroyed, all fragments will be destroyed.
+ *
+ * <p>All subclasses of Fragment must include a public empty constructor.
+ * The framework will often re-instantiate a fragment class when needed,
+ * in particular during state restore, and needs to be able to find this
+ * constructor to instantiate it.  If the empty constructor is not available,
+ * a runtime exception will occur in some cases during state restore.
+ *
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#Lifecycle">Lifecycle</a>
+ * <li><a href="#Layout">Layout</a>
+ * <li><a href="#BackStack">Back Stack</a>
+ * </ol>
+ *
+ * <a name="Lifecycle"></a>
+ * <h3>Lifecycle</h3>
+ *
+ * <p>Though a Fragment's lifecycle is tied to its owning activity, it has
+ * its own wrinkle on the standard activity lifecycle.  It includes basic
+ * activity lifecycle methods such as {@link #onResume}, but also important
+ * are methods related to interactions with the activity and UI generation.
+ *
+ * <p>The core series of lifecycle methods that are called to bring a fragment
+ * up to resumed state (interacting with the user) are:
+ *
+ * <ol>
+ * <li> {@link #onAttach} called once the fragment is associated with its activity.
+ * <li> {@link #onCreate} called to do initial creation of the fragment.
+ * <li> {@link #onCreateView} creates and returns the view hierarchy associated
+ * with the fragment.
+ * <li> {@link #onActivityCreated} tells the fragment that its activity has
+ * completed its own {@link Activity#onCreate Activity.onCreaate}.
+ * <li> {@link #onStart} makes the fragment visible to the user (based on its
+ * containing activity being started).
+ * <li> {@link #onResume} makes the fragment interacting with the user (based on its
+ * containing activity being resumed).
+ * </ol>
+ *
+ * <p>As a fragment is no longer being used, it goes through a reverse
+ * series of callbacks:
+ *
+ * <ol>
+ * <li> {@link #onPause} fragment is no longer interacting with the user either
+ * because its activity is being paused or a fragment operation is modifying it
+ * in the activity.
+ * <li> {@link #onStop} fragment is no longer visible to the user either
+ * because its activity is being stopped or a fragment operation is modifying it
+ * in the activity.
+ * <li> {@link #onDestroyView} allows the fragment to clean up resources
+ * associated with its View.
+ * <li> {@link #onDestroy} called to do final cleanup of the fragment's state.
+ * <li> {@link #onDetach} called immediately prior to the fragment no longer
+ * being associated with its activity.
+ * </ol>
+ *
+ * <a name="Layout"></a>
+ * <h3>Layout</h3>
+ *
+ * <p>Fragments can be used as part of your application's layout, allowing
+ * you to better modularize your code and more easily adjust your user
+ * interface to the screen it is running on.  As an example, we can look
+ * at a simple program consisting of a list of items, and display of the
+ * details of each item.</p>
+ *
+ * <p>An activity's layout XML can include <code>&lt;fragment&gt;</code> tags
+ * to embed fragment instances inside of the layout.  For example, here is
+ * a simple layout that embeds one fragment:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout}
+ *
+ * <p>The layout is installed in the activity in the normal way:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
+ *      main}
+ *
+ * <p>The titles fragment, showing a list of titles, is very simple, relying
+ * on {@link ListFragment} for most of its work.  Note the implementation of
+ * clicking an item, which can either update
+ * the content of the details fragment or start a new activity show the
+ * details depending on whether the current activity's layout can show the
+ * details.</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
+ *      titles}
+ *
+ * <p>The details fragment showing the contents of selected item here just
+ * displays a string of text based on an index of a string array built in to
+ * the app:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
+ *      details}
+ *
+ * <p>In this case when the user clicks on a title, there is no details
+ * fragment in the current activity, so the title title fragment's click code will
+ * launch a new activity to display the details fragment:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
+ *      details_activity}
+ *
+ * <p>However the screen may be large enough to show both the list of titles
+ * and details about the currently selected title.  To use such a layout on
+ * a landscape screen, this alternative layout can be placed under layout-land:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout}
+ *
+ * <p>Note how the prior code will adjust to this alternative UI flow: the
+ * titles fragment will now show its text inside of its activity, and the
+ * details activity will finish of it finds itself running in a configuration
+ * where the details can be shown inline.
+ *
+ * <p>When a configuration change causes the activity hosting these fragments
+ * to restart, its new instance may use a different layout that doesn't
+ * include the same fragments as the previous layout.  In this case all of
+ * the previous fragments will still be instantiated and running in the new
+ * instance; however, any that are no longer associated with a &lt;fragment&gt;
+ * tag in the view hierarchy will not have their content view created and will
+ * return false from {@link #isInLayout}.
+ * 
+ * <p>The attributes of the &lt;fragment&gt; tag are used to control the
+ * LayoutParams provider when attaching the fragment's view to the parent
+ * container.  They can alse be parsed by the fragment in {@link #onInflate}
+ * as parameters.
+ * 
+ * <p>The fragment being instantiated must have some kind of unique identifier
+ * so that it can be re-associated with a previous instance if the parent
+ * activity needs to be destroyed and recreated.  This can be provided these
+ * ways:
+ * 
+ * <ul>
+ * <li>If nothing is explicitly supplied, the view ID of the container will
+ * be used.
+ * <li><code>android:tag</code> can be used in &lt;fragment&gt; to provide
+ * a specific tag name for the fragment.
+ * <li><code>android:id</code> can be used in &lt;fragment&gt; to provide
+ * a specific identifier for the fragment.
+ * </ul>
+ * 
+ * <a name="BackStack"></a>
+ * <h3>Back Stack</h3>
+ *
+ * <p>The transaction in which fragments are modified can be placed on an
+ * internal back-stack of the owning activity.  When the user presses back
+ * in the activity, any transactions on the back stack are popped off before
+ * the activity itself is finished.
+ *
+ * <p>For example, consider this simple fragment that is instantiated with
+ * an integer argument and displays that in a TextView in its UI:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java
+ *      fragment}
+ *
+ * <p>A function that creates a new instance of the fragment, replacing
+ * whatever current fragment instance is being shown and pushing that change
+ * on to the back stack could be written as:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java
+ *      add_stack}
+ *
+ * <p>After each call to this function, a new entry is on the stack, and
+ * pressing back will pop it to return the user to whatever previous state
+ * the activity UI was in.
+ */
+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;
+    
+    // Construction arguments;
+    Bundle mArguments;
+
+    // Target fragment.
+    Fragment mTarget;
+
+    // Target request code.
+    int mTargetRequestCode;
+
+    // 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;
+    
+    // Set to true when the view has actually been inflated in its layout.
+    boolean mInLayout;
+
+    // Number of active back stack entries this fragment is in.
+    int mBackStackNesting;
+    
+    // The fragment manager we are associated with.  Set as soon as the
+    // fragment is used in a transaction; cleared after it has been removed
+    // from all transactions.
+    FragmentManager mFragmentManager;
+
+    // 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;
+    boolean mCheckedForLoaderManager;
+    
+    /**
+     * Thrown by {@link Fragment#instantiate(Context, String, Bundle)} when
+     * there is an instantiation failure.
+     */
+    static public class InstantiationException extends AndroidRuntimeException {
+        public InstantiationException(String msg, Exception cause) {
+            super(msg, cause);
+        }
+    }
+
+    /**
+     * Default constructor.  <strong>Every</strong> 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,
+     * arguments can be supplied by the caller with {@link #setArguments}
+     * and later retrieved by the Fragment with {@link #getArguments}.
+     * 
+     * <p>Applications should generally not implement a constructor.  The
+     * first place application code an run where the fragment is ready to
+     * be used is in {@link #onAttach(Activity)}, the point where the fragment
+     * is actually associated with its activity.  Some applications may also
+     * want to implement {@link #onInflate} to retrieve attributes from a
+     * layout resource, though should take care here because this happens for
+     * the fragment is attached to its activity.
+     */
+    public Fragment() {
+    }
+
+    /**
+     * Like {@link #instantiate(Context, String, Bundle)} but with a null
+     * argument Bundle.
+     */
+    public static Fragment instantiate(Context context, String fname) {
+        return instantiate(context, fname, null);
+    }
+
+    /**
+     * 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.
+     * @param args Bundle of arguments to supply to the fragment, which it
+     * can retrieve with {@link #getArguments()}.  May be null.
+     * @return Returns a new fragment instance.
+     * @throws InstantiationException If there is a failure in instantiating
+     * the given fragment class.  This is a runtime exception; it is not
+     * normally expected to happen.
+     */
+    public static Fragment instantiate(Context context, String fname, Bundle args) {
+        try {
+            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);
+            }
+            Fragment f = (Fragment)clazz.newInstance();
+            if (args != null) {
+                args.setClassLoader(f.getClass().getClassLoader());
+                f.mArguments = args;
+            }
+            return f;
+        } catch (ClassNotFoundException e) {
+            throw new InstantiationException("Unable to instantiate fragment " + fname
+                    + ": make sure class name exists, is public, and has an"
+                    + " empty constructor that is public", e);
+        } catch (java.lang.InstantiationException e) {
+            throw new InstantiationException("Unable to instantiate fragment " + fname
+                    + ": make sure class name exists, is public, and has an"
+                    + " empty constructor that is public", e);
+        } catch (IllegalAccessException e) {
+            throw new InstantiationException("Unable to instantiate fragment " + fname
+                    + ": make sure class name exists, is public, and has an"
+                    + " empty constructor that is public", e);
+        }
+    }
+    
+    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;
+    }
+    
+    /**
+     * Supply the construction arguments for this fragment.  This can only
+     * be called before the fragment has been attached to its activity; that
+     * is, you should call it immediately after constructing the fragment.  The
+     * arguments supplied here will be retained across fragment destroy and
+     * creation.
+     */
+    public void setArguments(Bundle args) {
+        if (mIndex >= 0) {
+            throw new IllegalStateException("Fragment already active");
+        }
+        mArguments = args;
+    }
+
+    /**
+     * Return the arguments supplied when the fragment was instantiated,
+     * if any.
+     */
+    final public Bundle getArguments() {
+        return mArguments;
+    }
+
+    /**
+     * Optional target for this fragment.  This may be used, for example,
+     * if this fragment is being started by another, and when done wants to
+     * give a result back to the first.  The target set here is retained
+     * across instances via {@link FragmentManager#putFragment
+     * FragmentManager.putFragment()}.
+     *
+     * @param fragment The fragment that is the target of this one.
+     * @param requestCode Optional request code, for convenience if you
+     * are going to call back with {@link #onActivityResult(int, int, Intent)}.
+     */
+    public void setTargetFragment(Fragment fragment, int requestCode) {
+        mTarget = fragment;
+        mTargetRequestCode = requestCode;
+    }
+
+    /**
+     * Return the target fragment set by {@link #setTargetFragment}.
+     */
+    final public Fragment getTargetFragment() {
+        return mTarget;
+    }
+
+    /**
+     * Return the target request code set by {@link #setTargetFragment}.
+     */
+    final public int getTargetRequestCode() {
+        return mTargetRequestCode;
+    }
+
+    /**
+     * Return the Activity this fragment is currently associated with.
+     */
+    final public Activity getActivity() {
+        return mActivity;
+    }
+    
+    /**
+     * Return the FragmentManager for interacting with fragments associated
+     * with this fragment's activity.  Note that this will be non-null slightly
+     * before {@link #getActivity()}, during the time from when the fragment is
+     * placed in a {@link FragmentTransaction} until it is committed and
+     * attached to its activity.
+     */
+    final public FragmentManager getFragmentManager() {
+        return mFragmentManager;
+    }
+
+    /**
+     * 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 layout is included as part of an activity view
+     * hierarchy via the &lt;fragment&gt; tag.  This will always be true when
+     * fragments are created through the &lt;fragment&gt; tag, <em>except</em>
+     * in the case where an old fragment is restored from a previous state and
+     * it does not appear in the layout of the current state.
+     */
+    final public boolean isInLayout() {
+        return mInLayout;
+    }
+
+    /**
+     * 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;
+        }
+        mCheckedForLoaderManager = true;
+        mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, true);
+        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 immediately after the fragment is created from a <fragment>
+     * tag in a layout file.  Note this is <em>before</em> the fragment's
+     * {@link #onAttach(Activity)} has been called; all you should do here is
+     * parse the attributes and save them away.  A convenient thing to do is
+     * simply copy them into a Bundle that is given to {@link #setArguments(Bundle)}.
+     * 
+     * <p>This is called every time the fragment is inflated, even if it is
+     * being inflated into a new instance with saved state.  Because a fragment's
+     * arguments are retained across instances, it may make no sense to re-parse
+     * the attributes into new arguments.  You may want to first check
+     * {@link #getArguments()} and only parse the attributes if it returns null,
+     * the assumption being that if it is non-null those are the same arguments
+     * from the first time the fragment was inflated.  (That said, you may want
+     * to have layouts change for different configurations such as landscape
+     * and portrait, which can have different attributes.  If so, you will need
+     * to re-parse the attributes each time this is called to generate new
+     * arguments.)</p>
+     * 
+     * @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(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;
+    }
+    
+    /**
+     * Called when a fragment loads an animation.
+     */
+    public Animator onCreateAnimator(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 (!mCheckedForLoaderManager) {
+            mCheckedForLoaderManager = true;
+            mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, false);
+        }
+        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;
+    }
+    
+    /**
+     * Called to ask the fragment to save its current dynamic state, so it
+     * can later be reconstructed in a new instance of its process is
+     * restarted.  If a new instance of the fragment later needs to be
+     * created, the data you place in the Bundle here will be available
+     * in the Bundle given to {@link #onCreate(Bundle)},
+     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}, and
+     * {@link #onActivityCreated(Bundle)}.
+     *
+     * <p>This corresponds to {@link Activity#onSaveInstanceState(Bundle)
+     * Activity.onSaveInstanceState(Bundle)} and most of the discussion there
+     * applies here as well.  Note however: <em>this method may be called
+     * at any time before {@link #onDestroy()}</em>.  There are many situations
+     * where a fragment may be mostly torn down (such as when placed on the
+     * back stack with no UI showing), but its state will not be saved until
+     * its owning activity actually needs to save its state.
+     *
+     * @param outState Bundle in which to place your saved state.
+     */
+    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 called
+     * <em>regardless</em> of whether {@link #onCreateView} returned a
+     * non-null view.  Internally it is called after the view's state has
+     * been saved but before it has been removed from its parent.
+     */
+    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;
+        //Log.v("foo", "onDestroy: mCheckedForLoaderManager=" + mCheckedForLoaderManager
+        //        + " mLoaderManager=" + mLoaderManager);
+        if (!mCheckedForLoaderManager) {
+            mCheckedForLoaderManager = true;
+            mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, false);
+        }
+        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;
+    }
+    
+    /**
+     * Print the Fragments's state into the given stream.
+     *
+     * @param prefix Text to print at the front of each line.
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param writer The PrintWriter to which you should dump your state.  This will be
+     * closed for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.print(prefix); writer.print("mFragmentId="); writer.print(mFragmentId);
+                writer.print(" mContainerId="); writer.print(mContainerId);
+                writer.print(" mTag="); writer.println(mTag);
+        writer.print(prefix); writer.print("mState="); writer.print(mState);
+                writer.print(" mIndex="); writer.print(mIndex);
+                writer.print(" mWho="); writer.print(mWho);
+                writer.print(" mBackStackNesting="); writer.println(mBackStackNesting);
+        writer.print(prefix); writer.print("mAdded="); writer.print(mAdded);
+                writer.print(" mResumed="); writer.print(mResumed);
+                writer.print(" mFromLayout="); writer.print(mFromLayout);
+                writer.print(" mInLayout="); writer.println(mInLayout);
+        writer.print(prefix); writer.print("mHidden="); writer.print(mHidden);
+                writer.print(" mRetainInstance="); writer.print(mRetainInstance);
+                writer.print(" mRetaining="); writer.print(mRetaining);
+                writer.print(" mHasMenu="); writer.println(mHasMenu);
+        if (mFragmentManager != null) {
+            writer.print(prefix); writer.print("mFragmentManager=");
+                    writer.println(mFragmentManager);
+        }
+        if (mImmediateActivity != null) {
+            writer.print(prefix); writer.print("mImmediateActivity=");
+                    writer.println(mImmediateActivity);
+        }
+        if (mActivity != null) {
+            writer.print(prefix); writer.print("mActivity=");
+                    writer.println(mActivity);
+        }
+        if (mArguments != null) {
+            writer.print(prefix); writer.print("mArguments="); writer.println(mArguments);
+        }
+        if (mSavedFragmentState != null) {
+            writer.print(prefix); writer.print("mSavedFragmentState=");
+                    writer.println(mSavedFragmentState);
+        }
+        if (mSavedViewState != null) {
+            writer.print(prefix); writer.print("mSavedViewState=");
+                    writer.println(mSavedViewState);
+        }
+        if (mTarget != null) {
+            writer.print(prefix); writer.print("mTarget="); writer.print(mTarget);
+                    writer.print(" mTargetRequestCode=");
+                    writer.println(mTargetRequestCode);
+        }
+        if (mNextAnim != 0) {
+            writer.print(prefix); writer.print("mNextAnim="); writer.println(mNextAnim);
+        }
+        if (mContainer != null) {
+            writer.print(prefix); writer.print("mContainer="); writer.println(mContainer);
+        }
+        if (mView != null) {
+            writer.print(prefix); writer.print("mView="); writer.println(mView);
+        }
+        if (mLoaderManager != null) {
+            writer.print(prefix); writer.print("mLoaderManager="); writer.print(mLoaderManager);
+                    writer.print(" mStarted="); writer.print(mStarted);
+                    writer.print(" mCheckedForLoaderManager=");
+                    writer.println(mCheckedForLoaderManager);
+        }
+    }
+
+    void performStop() {
+        onStop();
+        if (mStarted) {
+            mStarted = false;
+            if (!mCheckedForLoaderManager) {
+                mCheckedForLoaderManager = true;
+                mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, false);
+            }
+            if (mLoaderManager != null) {
+                if (mActivity == null || !mActivity.mChangingConfigurations) {
+                    mLoaderManager.doStop();
+                } else {
+                    mLoaderManager.doRetain();
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/FragmentBreadCrumbs.java b/core/java/android/app/FragmentBreadCrumbs.java
new file mode 100644
index 0000000..22e0747
--- /dev/null
+++ b/core/java/android/app/FragmentBreadCrumbs.java
@@ -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.
+ */
+
+package android.app;
+
+import android.app.FragmentManager.BackStackEntry;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * Helper class for showing "bread crumbs" representing the fragment
+ * stack in an activity.  This is intended to be used with
+ * {@link ActionBar#setCustomNavigationMode(View)
+ * ActionBar.setCustomNavigationMode(View)} to place the bread crumbs in
+ * the navigation area of the action bar.
+ *
+ * <p>The default style for this view is
+ * {@link android.R.style#Widget_FragmentBreadCrumbs}.
+ */
+public class FragmentBreadCrumbs extends ViewGroup
+        implements FragmentManager.OnBackStackChangedListener {
+    Activity mActivity;
+    LayoutInflater mInflater;
+    LinearLayout mContainer;
+
+    // Hahah
+    BackStackRecord mTopEntry;
+
+    public FragmentBreadCrumbs(Context context) {
+        this(context, null);
+    }
+
+    public FragmentBreadCrumbs(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.style.Widget_FragmentBreadCrumbs);
+    }
+
+    public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /**
+     * Attach the bread crumbs to their activity.  This must be called once
+     * when creating the bread crumbs.
+     */
+    public void setActivity(Activity a) {
+        mActivity = a;
+        mInflater = (LayoutInflater)a.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mContainer = (LinearLayout)mInflater.inflate(
+                com.android.internal.R.layout.fragment_bread_crumbs,
+                this, false);
+        addView(mContainer);
+        a.getFragmentManager().addOnBackStackChangedListener(this);
+        updateCrumbs();
+    }
+
+    /**
+     * Set a custom title for the bread crumbs.  This will be the first entry
+     * shown at the left, representing the root of the bread crumbs.  If the
+     * title is null, it will not be shown.
+     */
+    public void setTitle(CharSequence title, CharSequence shortTitle) {
+        if (title == null) {
+            mTopEntry = null;
+        } else {
+            mTopEntry = new BackStackRecord((FragmentManagerImpl)
+                    mActivity.getFragmentManager());
+            mTopEntry.setBreadCrumbTitle(title);
+            mTopEntry.setBreadCrumbShortTitle(shortTitle);
+        }
+        updateCrumbs();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        // Eventually we should implement our own layout of the views,
+        // rather than relying on a linear layout.
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+
+            int childRight = mPaddingLeft + child.getMeasuredWidth() - mPaddingRight;
+            int childBottom = mPaddingTop + child.getMeasuredHeight() - mPaddingBottom;
+            child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int count = getChildCount();
+
+        int maxHeight = 0;
+        int maxWidth = 0;
+
+        // Find rightmost and bottom-most child
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                measureChild(child, widthMeasureSpec, heightMeasureSpec);
+                maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+                maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
+            }
+        }
+
+        // Account for padding too
+        maxWidth += mPaddingLeft + mPaddingRight;
+        maxHeight += mPaddingTop + mPaddingBottom;
+
+        // Check against our minimum height and width
+        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+        setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
+                resolveSize(maxHeight, heightMeasureSpec));
+    }
+
+    @Override
+    public void onBackStackChanged() {
+        updateCrumbs();
+    }
+
+    void updateCrumbs() {
+        FragmentManager fm = mActivity.getFragmentManager();
+        int numEntries = fm.countBackStackEntries();
+        int numViews = mContainer.getChildCount();
+        for (int i = mTopEntry != null ? -1 : 0; i < numEntries; i++) {
+            BackStackEntry bse = i == -1 ? mTopEntry : fm.getBackStackEntry(i);
+            int viewI = mTopEntry != null ? i + 1 : i;
+            if (viewI < numViews) {
+                View v = mContainer.getChildAt(viewI);
+                Object tag = v.getTag();
+                if (tag != bse) {
+                    for (int j = viewI; j < numViews; j++) {
+                        mContainer.removeViewAt(viewI);
+                    }
+                    numViews = viewI;
+                }
+            }
+            if (viewI >= numViews) {
+                View item = mInflater.inflate(
+                        com.android.internal.R.layout.fragment_bread_crumb_item,
+                        this, false);
+                TextView text = (TextView)item.findViewById(com.android.internal.R.id.title);
+                text.setText(bse.getBreadCrumbTitle());
+                item.setTag(bse);
+                if (viewI == 0) {
+                    text.setCompoundDrawables(null, null, null, null);
+                }
+                mContainer.addView(item);
+                item.setOnClickListener(mOnClickListener);
+            }
+        }
+        int viewI = mTopEntry != null ? numEntries + 1 : numEntries;
+        numViews = mContainer.getChildCount();
+        while (numViews > viewI) {
+            mContainer.removeViewAt(numViews-1);
+            numViews--;
+        }
+    }
+
+    private OnClickListener mOnClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            if (v.getTag() instanceof BackStackEntry) {
+                BackStackEntry bse = (BackStackEntry) v.getTag();
+                mActivity.getFragmentManager().popBackStack(bse.getId(),
+                        bse == mTopEntry? FragmentManager.POP_BACK_STACK_INCLUSIVE : 0);
+            }
+        }
+    };
+}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
new file mode 100644
index 0000000..37e7253
--- /dev/null
+++ b/core/java/android/app/FragmentManager.java
@@ -0,0 +1,1420 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+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 java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Interface for interacting with {@link Fragment} objects inside of an
+ * {@link Activity}
+ */
+public interface FragmentManager {
+    /**
+     * Representation of an entry on the fragment back stack, as created
+     * with {@link FragmentTransaction#addToBackStack(String)
+     * FragmentTransaction.addToBackStack()}.  Entries can later be
+     * retrieved with {@link FragmentManager#getBackStackEntry(int)
+     * FragmentManager.getBackStackEntry()}.
+     *
+     * <p>Note that you should never hold on to a BackStackEntry object;
+     * the identifier as returned by {@link #getId} is the only thing that
+     * will be persisted across activity instances.
+     */
+    public interface BackStackEntry {
+        /**
+         * Return the unique identifier for the entry.  This is the only
+         * representation of the entry that will persist across activity
+         * instances.
+         */
+        public int getId();
+
+        /**
+         * Return the full bread crumb title for the entry, or null if it
+         * does not have one.
+         */
+        public CharSequence getBreadCrumbTitle();
+
+        /**
+         * Return the short bread crumb title for the entry, or null if it
+         * does not have one.
+         */
+        public CharSequence getBreadCrumbShortTitle();
+    }
+
+    /**
+     * Interface to watch for changes to the back stack.
+     */
+    public interface OnBackStackChangedListener {
+        /**
+         * Called whenever the contents of the back stack change.
+         */
+        public void onBackStackChanged();
+    }
+
+    /**
+     * Start a series of edit operations on the Fragments associated with
+     * this FragmentManager.
+     */
+    public FragmentTransaction openTransaction();
+
+    /**
+     * 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 first
+     * searches through fragments that are currently added to the manager's
+     * activity; if no such fragment is found, then all fragments currently
+     * on the back stack associated with this ID are searched.
+     * @return The fragment if found or null otherwise.
+     */
+    public Fragment findFragmentById(int 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 first
+     * searches through fragments that are currently added to the manager's
+     * activity; if no such fragment is found, then all fragments currently
+     * on the back stack are searched.
+     * @return The fragment if found or null otherwise.
+     */
+    public Fragment findFragmentByTag(String tag);
+
+    /**
+     * 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();
+
+    /**
+     * Pop the last fragment transition from the manager'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);
+
+    /**
+     * 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 the number of entries currently in the back stack.
+     */
+    public int countBackStackEntries();
+
+    /**
+     * Return the BackStackEntry at index <var>index</var> in the back stack;
+     * entries start index 0 being the bottom of the stack.
+     */
+    public BackStackEntry getBackStackEntry(int index);
+
+    /**
+     * Add a new listener for changes to the fragment back stack.
+     */
+    public void addOnBackStackChangedListener(OnBackStackChangedListener listener);
+
+    /**
+     * Remove a listener that was previously added with
+     * {@link #addOnBackStackChangedListener(OnBackStackChangedListener)}.
+     */
+    public void removeOnBackStackChangedListener(OnBackStackChangedListener listener);
+
+    /**
+     * Put a reference to a fragment in a Bundle.  This Bundle can be
+     * persisted as saved state, and when later restoring
+     * {@link #getFragment(Bundle, String)} will return the current
+     * instance of the same fragment.
+     *
+     * @param bundle The bundle in which to put the fragment reference.
+     * @param key The name of the entry in the bundle.
+     * @param fragment The Fragment whose reference is to be stored.
+     */
+    public void putFragment(Bundle bundle, String key, Fragment fragment);
+
+    /**
+     * Retrieve the current Fragment instance for a reference previously
+     * placed with {@link #putFragment(Bundle, String, Fragment)}.
+     *
+     * @param bundle The bundle from which to retrieve the fragment reference.
+     * @param key The name of the entry in the bundle.
+     * @return Returns the current Fragment instance that is associated with
+     * the given reference.
+     */
+    public Fragment getFragment(Bundle bundle, String key);
+
+    /**
+     * Print the FragmentManager's state into the given stream.
+     *
+     * @param prefix Text to print at the front of each line.
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param writer A PrintWriter to which the dump is to be set.
+     * @param args additional arguments to the dump request.
+     */
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args);
+}
+
+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];
+        }
+    };
+}
+
+/**
+ * Container for fragments associated with an activity.
+ */
+final class FragmentManagerImpl implements FragmentManager {
+    static final boolean DEBUG = true;
+    static final String TAG = "FragmentManager";
+    
+    static final String TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state";
+    static final String TARGET_STATE_TAG = "android:target_state";
+    static final String VIEW_STATE_TAG = "android:view_state";
+
+    ArrayList<Runnable> mPendingActions;
+    Runnable[] mTmpActions;
+    boolean mExecutingActions;
+    
+    ArrayList<Fragment> mActive;
+    ArrayList<Fragment> mAdded;
+    ArrayList<Integer> mAvailIndices;
+    ArrayList<BackStackRecord> mBackStack;
+    
+    // Must be accessed while locked.
+    ArrayList<BackStackRecord> mBackStackIndices;
+    ArrayList<Integer> mAvailBackStackIndices;
+
+    ArrayList<OnBackStackChangedListener> mBackStackChangeListeners;
+
+    int mCurState = Fragment.INITIALIZING;
+    Activity mActivity;
+    
+    boolean mNeedMenuInvalidate;
+    boolean mStateSaved;
+    
+    // Temporary vars for state save and restore.
+    Bundle mStateBundle = null;
+    SparseArray<Parcelable> mStateArray = null;
+    
+    Runnable mExecCommit = new Runnable() {
+        @Override
+        public void run() {
+            execPendingActions();
+        }
+    };
+
+    @Override
+    public FragmentTransaction openTransaction() {
+        return new BackStackRecord(this);
+    }
+
+    @Override
+    public boolean popBackStack() {
+        return popBackStackState(mActivity.mHandler, null, -1, 0);
+    }
+
+    @Override
+    public boolean popBackStack(String name, int flags) {
+        return popBackStackState(mActivity.mHandler, name, -1, flags);
+    }
+
+    @Override
+    public boolean popBackStack(int id, int flags) {
+        if (id < 0) {
+            throw new IllegalArgumentException("Bad id: " + id);
+        }
+        return popBackStackState(mActivity.mHandler, null, id, flags);
+    }
+
+    @Override
+    public int countBackStackEntries() {
+        return mBackStack != null ? mBackStack.size() : 0;
+    }
+
+    @Override
+    public BackStackEntry getBackStackEntry(int index) {
+        return mBackStack.get(index);
+    }
+
+    @Override
+    public void addOnBackStackChangedListener(OnBackStackChangedListener listener) {
+        if (mBackStackChangeListeners == null) {
+            mBackStackChangeListeners = new ArrayList<OnBackStackChangedListener>();
+        }
+        mBackStackChangeListeners.add(listener);
+    }
+
+    @Override
+    public void removeOnBackStackChangedListener(OnBackStackChangedListener listener) {
+        if (mBackStackChangeListeners != null) {
+            mBackStackChangeListeners.remove(listener);
+        }
+    }
+
+    @Override
+    public void putFragment(Bundle bundle, String key, Fragment fragment) {
+        if (fragment.mIndex < 0) {
+            throw new IllegalStateException("Fragment " + fragment
+                    + " is not currently in the FragmentManager");
+        }
+        bundle.putInt(key, fragment.mIndex);
+    }
+
+    @Override
+    public Fragment getFragment(Bundle bundle, String key) {
+        int index = bundle.getInt(key, -1);
+        if (index == -1) {
+            return null;
+        }
+        if (index >= mActive.size()) {
+            throw new IllegalStateException("Fragement no longer exists for key "
+                    + key + ": index " + index);
+        }
+        Fragment f = mActive.get(index);
+        if (f == null) {
+            throw new IllegalStateException("Fragement no longer exists for key "
+                    + key + ": index " + index);
+        }
+        return f;
+    }
+
+    @Override
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (mActive == null || mActive.size() <= 0) {
+            return;
+        }
+
+        writer.print(prefix); writer.println("Active Fragments:");
+
+        String innerPrefix = prefix + "    ";
+
+        int N = mActive.size();
+        for (int i=0; i<N; i++) {
+            Fragment f = mActive.get(i);
+            if (f != null) {
+                writer.print(prefix); writer.print("  #"); writer.print(i);
+                        writer.print(": "); writer.println(f.toString());
+                f.dump(innerPrefix, fd, writer, args);
+            }
+        }
+
+        if (mAdded != null) {
+            N = mAdded.size();
+            if (N > 0) {
+                writer.print(prefix); writer.println("Added Fragments:");
+                for (int i=0; i<N; i++) {
+                    Fragment f = mAdded.get(i);
+                    writer.print(prefix); writer.print("  #"); writer.print(i);
+                            writer.print(": "); writer.println(f.toString());
+                }
+            }
+        }
+
+        if (mBackStack != null) {
+            N = mBackStack.size();
+            if (N > 0) {
+                writer.print(prefix); writer.println("Back Stack:");
+                for (int i=0; i<N; i++) {
+                    BackStackRecord bs = mBackStack.get(i);
+                    writer.print(prefix); writer.print("  #"); writer.print(i);
+                            writer.print(": "); writer.println(bs.toString());
+                }
+            }
+        }
+    }
+
+    Animator loadAnimator(Fragment fragment, int transit, boolean enter,
+            int transitionStyle) {
+        Animator animObj = fragment.onCreateAnimator(transit, enter,
+                fragment.mNextAnim);
+        if (animObj != null) {
+            return animObj;
+        }
+        
+        if (fragment.mNextAnim != 0) {
+            Animator anim = AnimatorInflater.loadAnimator(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.FragmentAnimation);
+        int anim = attrs.getResourceId(styleIndex, 0);
+        attrs.recycle();
+        
+        if (anim == 0) {
+            return null;
+        }
+        
+        return AnimatorInflater.loadAnimator(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);
+                    if (f.mSavedFragmentState != null) {
+                        f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
+                                FragmentManagerImpl.VIEW_STATE_TAG);
+                        f.mTarget = getFragment(f.mSavedFragmentState,
+                                FragmentManagerImpl.TARGET_STATE_TAG);
+                        if (f.mTarget != null) {
+                            f.mTargetRequestCode = f.mSavedFragmentState.getInt(
+                                    FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
+                        }
+                    }
+                    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("No 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) {
+                                    Animator anim = loadAnimator(f, transit, true,
+                                            transitionStyle);
+                                    if (anim != null) {
+                                        anim.setTarget(f.mView);
+                                        anim.start();
+                                    }
+                                    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) {
+                            // Need to save the current view state if not
+                            // done already.
+                            if (!mActivity.isFinishing() && f.mSavedViewState == null) {
+                                saveFragmentViewState(f);
+                            }
+                        }
+                        f.mCalled = false;
+                        f.onDestroyView();
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onDestroyedView()");
+                        }
+                        if (f.mView != null && f.mContainer != null) {
+                            Animator anim = null;
+                            if (mCurState > Fragment.INITIALIZING) {
+                                anim = loadAnimator(f, transit, false,
+                                        transitionStyle);
+                            }
+                            if (anim != null) {
+                                final ViewGroup container = f.mContainer;
+                                final View view = f.mView;
+                                container.startViewTransition(view);
+                                anim.addListener(new AnimatorListenerAdapter() {
+                                    @Override
+                                    public void onAnimationEnd(Animator anim) {
+                                        container.endViewTransition(view);
+                                    }
+                                });
+                                anim.setTarget(f.mView);
+                                anim.start();
+
+                            }
+                            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.mImmediateActivity = null;
+                        f.mActivity = null;
+                    }
+            }
+        }
+        
+        f.mState = newState;
+    }
+    
+    void moveToState(Fragment f) {
+        moveToState(f, mCurState, 0, 0);
+    }
+
+    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);
+                }
+            }
+
+            if (mNeedMenuInvalidate && mActivity != null) {
+                mActivity.invalidateOptionsMenu();
+                mNeedMenuInvalidate = false;
+            }
+        }
+    }
+    
+    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;
+        }
+        
+        if (DEBUG) Log.v(TAG, "Freeing fragment index " + f.mIndex);
+        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 (mAdded == null) {
+            mAdded = new ArrayList<Fragment>();
+        }
+        mAdded.add(fragment);
+        makeActive(fragment);
+        if (DEBUG) Log.v(TAG, "add: " + fragment);
+        fragment.mAdded = true;
+        if (fragment.mHasMenu) {
+            mNeedMenuInvalidate = true;
+        }
+        if (moveToStateNow) {
+            moveToState(fragment);
+        }
+    }
+    
+    public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
+        if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
+        mAdded.remove(fragment);
+        final boolean inactive = fragment.mBackStackNesting <= 0;
+        if (fragment.mHasMenu) {
+            mNeedMenuInvalidate = true;
+        }
+        fragment.mAdded = false;
+        moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
+                transition, transitionStyle);
+        if (inactive) {
+            makeInactive(fragment);
+        }
+    }
+    
+    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) {
+                Animator anim = loadAnimator(fragment, transition, true,
+                        transitionStyle);
+                if (anim != null) {
+                    anim.setTarget(fragment.mView);
+                    anim.start();
+                }
+                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) {
+                Animator anim = loadAnimator(fragment, transition, true,
+                        transitionStyle);
+                if (anim != null) {
+                    anim.setTarget(fragment.mView);
+                    anim.start();
+                }
+                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) {
+        if (mStateSaved) {
+            throw new IllegalStateException(
+                    "Can not perform this action after onSaveInstanceState");
+        }
+        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(BackStackRecord bse) {
+        synchronized (this) {
+            if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
+                if (mBackStackIndices == null) {
+                    mBackStackIndices = new ArrayList<BackStackRecord>();
+                }
+                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, BackStackRecord bse) {
+        synchronized (this) {
+            if (mBackStackIndices == null) {
+                mBackStackIndices = new ArrayList<BackStackRecord>();
+            }
+            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;
+        }
+    }
+    
+    void reportBackStackChanged() {
+        if (mBackStackChangeListeners != null) {
+            for (int i=0; i<mBackStackChangeListeners.size(); i++) {
+                mBackStackChangeListeners.get(i).onBackStackChanged();
+            }
+        }
+    }
+
+    void addBackStackState(BackStackRecord state) {
+        if (mBackStack == null) {
+            mBackStack = new ArrayList<BackStackRecord>();
+        }
+        mBackStack.add(state);
+        reportBackStackChanged();
+    }
+    
+    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 BackStackRecord bss = mBackStack.remove(last);
+            enqueueAction(new Runnable() {
+                public void run() {
+                    if (DEBUG) Log.v(TAG, "Popping back stack state: " + bss);
+                    bss.popFromBackStack(true);
+                    reportBackStackChanged();
+                }
+            });
+        } 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) {
+                    BackStackRecord 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) {
+                        BackStackRecord 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<BackStackRecord> states
+                    = new ArrayList<BackStackRecord>();
+            for (int i=mBackStack.size()-1; i>index; i--) {
+                states.add(mBackStack.remove(i));
+            }
+            enqueueAction(new Runnable() {
+                public void run() {
+                    final int LAST = states.size()-1;
+                    for (int i=0; i<=LAST; i++) {
+                        if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
+                        states.get(i).popFromBackStack(i == LAST);
+                    }
+                    reportBackStackChanged();
+                }
+            });
+        }
+        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() {
+        mStateSaved = true;
+
+        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 (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
+                    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(
+                                    FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
+                        }
+                    }
+
+                    if (f.mTarget != null) {
+                        if (fs.mSavedFragmentState == null) {
+                            fs.mSavedFragmentState = new Bundle();
+                        }
+                        putFragment(fs.mSavedFragmentState,
+                                FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget);
+                        if (f.mTargetRequestCode != 0) {
+                            fs.mSavedFragmentState.putInt(
+                                    FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,
+                                    f.mTargetRequestCode);
+                        }
+                    }
+
+                } else {
+                    fs.mSavedFragmentState = f.mSavedFragmentState;
+                }
+                
+                if (DEBUG) Log.v(TAG, "Saved state of " + f + ": "
+                        + fs.mSavedFragmentState);
+            }
+        }
+        
+        if (!haveFragments) {
+            if (DEBUG) Log.v(TAG, "saveAllState: no fragments!");
+            return null;
+        }
+        
+        int[] added = null;
+        BackStackState[] backStack = null;
+        
+        // Build list of currently added fragments.
+        if (mAdded != null) {
+            N = mAdded.size();
+            if (N > 0) {
+                added = new int[N];
+                for (int i=0; i<N; i++) {
+                    added[i] = mAdded.get(i).mIndex;
+                    if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
+                            + ": " + mAdded.get(i));
+                }
+            }
+        }
+        
+        // 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));
+                    if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i
+                            + ": " + 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);
+                if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f);
+                FragmentState fs = fms.mActive[f.mIndex];
+                fs.mInstance = f;
+                f.mSavedViewState = null;
+                f.mBackStackNesting = 0;
+                f.mInLayout = false;
+                f.mAdded = false;
+                if (fs.mSavedFragmentState != null) {
+                    f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray(
+                            FragmentManagerImpl.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) {
+                Fragment f = fs.instantiate(mActivity);
+                if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": " + f);
+                mActive.add(f);
+            } else {
+                if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": (null)");
+                mActive.add(null);
+                if (mAvailIndices == null) {
+                    mAvailIndices = new ArrayList<Integer>();
+                }
+                if (DEBUG) Log.v(TAG, "restoreAllState: adding avail #" + i);
+                mAvailIndices.add(i);
+            }
+        }
+        
+        // Update the target of all retained fragments.
+        if (nonConfig != null) {
+            for (int i=0; i<nonConfig.size(); i++) {
+                Fragment f = nonConfig.get(i);
+                if (f.mTarget != null) {
+                    if (f.mTarget.mIndex < mActive.size()) {
+                        f.mTarget = mActive.get(f.mTarget.mIndex);
+                    } else {
+                        Log.w(TAG, "Re-attaching retained fragment " + f
+                                + " target no longer exists: " + f.mTarget);
+                        f.mTarget = null;
+                    }
+                }
+            }
+        }
+
+        // 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;
+                if (DEBUG) Log.v(TAG, "restoreAllState: making added #" + i + ": " + f);
+                mAdded.add(f);
+            }
+        } else {
+            mAdded = null;
+        }
+        
+        // Build the back stack.
+        if (fms.mBackStack != null) {
+            mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length);
+            for (int i=0; i<fms.mBackStack.length; i++) {
+                BackStackRecord bse = fms.mBackStack[i].instantiate(this);
+                if (DEBUG) Log.v(TAG, "restoreAllState: adding bse #" + i
+                        + " (index " + bse.mIndex + "): " + bse);
+                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() {
+        mStateSaved = false;
+        moveToState(Fragment.CREATED, false);
+    }
+    
+    public void dispatchActivityCreated() {
+        mStateSaved = false;
+        moveToState(Fragment.ACTIVITY_CREATED, false);
+    }
+    
+    public void dispatchStart() {
+        mStateSaved = false;
+        moveToState(Fragment.STARTED, false);
+    }
+    
+    public void dispatchResume() {
+        mStateSaved = false;
+        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_FRAGMENT_OPEN:
+                rev = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;
+                break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
+                rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
+                break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_NEXT:
+                rev = FragmentTransaction.TRANSIT_FRAGMENT_PREV;
+                break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_PREV:
+                rev = FragmentTransaction.TRANSIT_FRAGMENT_NEXT;
+                break;
+        }
+        return rev;
+        
+    }
+    
+    public static int transitToStyleIndex(int transit, boolean enter) {
+        int animAttr = -1;
+        switch (transit) {
+            case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
+                animAttr = enter
+                    ? com.android.internal.R.styleable.FragmentAnimation_fragmentOpenEnterAnimation
+                    : com.android.internal.R.styleable.FragmentAnimation_fragmentOpenExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
+                animAttr = enter
+                    ? com.android.internal.R.styleable.FragmentAnimation_fragmentCloseEnterAnimation
+                    : com.android.internal.R.styleable.FragmentAnimation_fragmentCloseExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_NEXT:
+                animAttr = enter
+                    ? com.android.internal.R.styleable.FragmentAnimation_fragmentNextEnterAnimation
+                    : com.android.internal.R.styleable.FragmentAnimation_fragmentNextExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_PREV:
+                animAttr = enter
+                    ? com.android.internal.R.styleable.FragmentAnimation_fragmentPrevEnterAnimation
+                    : com.android.internal.R.styleable.FragmentAnimation_fragmentPrevExitAnimation;
+                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..b00476bb
--- /dev/null
+++ b/core/java/android/app/FragmentTransaction.java
@@ -0,0 +1,186 @@
+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);
+
+    /**
+     * @return <code>true</code> if this transaction contains no operations,
+     * <code>false</code> otherwise.
+     */
+    public boolean isEmpty();
+    
+    /**
+     * 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;
+    /** Fragment is being added onto the stack */
+    public final int TRANSIT_FRAGMENT_OPEN = 1 | TRANSIT_ENTER_MASK;
+    /** Fragment is being removed from the stack */
+    public final int TRANSIT_FRAGMENT_CLOSE = 2 | TRANSIT_EXIT_MASK;
+    /** Fragment is being added in a 'next' operation*/
+    public final int TRANSIT_FRAGMENT_NEXT = 3 | TRANSIT_ENTER_MASK;
+    /** Fragment is being removed in a 'previous' operation */
+    public final int TRANSIT_FRAGMENT_PREV = 4 | TRANSIT_EXIT_MASK;
+
+    /**
+     * Set specific animation resources to run for the fragments that are
+     * entering and exiting in this transaction.
+     */
+    public FragmentTransaction setCustomAnimations(int enter, int exit);
+    
+    /**
+     * Select a standard transition animation for this transaction.  May be
+     * one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
+     * or {@link #TRANSIT_FRAGMENT_CLOSE}
+     */
+    public FragmentTransaction setTransition(int transit);
+
+    /**
+     * Set a custom style resource that will be used for resolving transit
+     * animations.
+     */
+    public FragmentTransaction setTransitionStyle(int styleRes);
+    
+    /**
+     * Add this transaction to the back stack.  This means that the transaction
+     * will be remembered after it is committed, and will reverse its operation
+     * when later popped off the stack.
+     *
+     * @param name An optional name for this back stack state, or null.
+     */
+    public FragmentTransaction addToBackStack(String name);
+
+    /**
+     * Set the full title to show as a bread crumb when this transaction
+     * is on the back stack, as used by {@link FragmentBreadCrumbs}.
+     *
+     * @param res A string resource containing the title.
+     */
+    public FragmentTransaction setBreadCrumbTitle(int res);
+
+    /**
+     * Like {@link #setBreadCrumbTitle(int)} but taking a raw string; this
+     * method is <em>not</em> recommended, as the string can not be changed
+     * later if the locale changes.
+     */
+    public FragmentTransaction setBreadCrumbTitle(CharSequence text);
+
+    /**
+     * Set the short title to show as a bread crumb when this transaction
+     * is on the back stack, as used by {@link FragmentBreadCrumbs}.
+     *
+     * @param res A string resource containing the title.
+     */
+    public FragmentTransaction setBreadCrumbShortTitle(int res);
+
+    /**
+     * Like {@link #setBreadCrumbShortTitle(int)} but taking a raw string; this
+     * method is <em>not</em> recommended, as the string can not be changed
+     * later if the locale changes.
+     */
+    public FragmentTransaction setBreadCrumbShortTitle(CharSequence text);
+
+    /**
+     * 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 28af0d3..cd229e3 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -311,6 +311,10 @@
     
     public void finishHeavyWeightApp() throws RemoteException;
 
+    public void setImmersive(IBinder token, boolean immersive) throws RemoteException;
+    public boolean isImmersive(IBinder token) throws RemoteException;
+    public boolean isTopActivityImmersive() throws RemoteException;
+    
     public void crashApplication(int uid, int initialPid, String packageName,
             String message) throws RemoteException;
 
@@ -322,6 +326,13 @@
     public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
             int mode) throws RemoteException;
 
+    public int checkGrantUriPermission(int callingUid, String targetPkg,
+            Uri uri, int modeFlags) 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
      */
@@ -532,4 +543,6 @@
     int NEW_URI_PERMISSION_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+115;
     int GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+116;
     int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+117;
+    int CHECK_GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118;
+    int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+119;
 }
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index c8ef17f..1f8a7c58 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -97,13 +97,17 @@
     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;
     static final int EXTERNAL_STORAGE_UNAVAILABLE = 1;
     void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException;
     void scheduleCrash(String msg) throws RemoteException;
-    
+    void dumpActivity(FileDescriptor fd, IBinder servicetoken, String[] args)
+            throws RemoteException;
+
     String descriptor = "android.app.IApplicationThread";
 
     int SCHEDULE_PAUSE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
@@ -140,4 +144,6 @@
     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;
+    int DUMP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36;
 }
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..28abcaa
--- /dev/null
+++ b/core/java/android/app/LoaderManager.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Log;
+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 {
+    static final String TAG = "LoaderManagerImpl";
+    static final boolean DEBUG = true;
+
+    // These are the currently active loaders.  A loader is here
+    // from the time its load is started until it has been explicitly
+    // stopped or restarted by the application.
+    final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
+
+    // These are previously run loaders.  This list is maintained internally
+    // to avoid destroying a loader while an application is still using it.
+    // It allows an application to restart a loader, but continue using its
+    // previously run loader until the new loader's data is available.
+    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 (DEBUG) Log.v(TAG, "  Starting: " + this);
+            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() {
+            if (DEBUG) Log.v(TAG, "  Retaining: " + this);
+            mRetaining = true;
+            mRetainingStarted = mStarted;
+            mStarted = false;
+            mCallbacks = null;
+        }
+        
+        void finishRetain() {
+            if (mRetaining) {
+                if (DEBUG) Log.v(TAG, "  Finished Retaining: " + this);
+                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() {
+            if (DEBUG) Log.v(TAG, "  Stopping: " + this);
+            mStarted = false;
+            if (!mRetaining) {
+                if (mLoader != null && mListenerRegistered) {
+                    // Let the loader know we're done with it
+                    mListenerRegistered = false;
+                    mLoader.unregisterListener(this);
+                    mLoader.stopLoading();
+                }
+                mData = null;
+            }
+        }
+        
+        void destroy() {
+            if (DEBUG) Log.v(TAG, "  Destroying: " + this);
+            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 (DEBUG) Log.v(TAG, "onLoadComplete: " + this + " mDestroyed=" + mDestroyed);
+
+            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);
+            }
+
+            if (DEBUG) Log.v(TAG, "onLoadFinished returned: " + this);
+
+            // We have now given the application the new loader with its
+            // loaded data, so it should have stopped using the previous
+            // loader.  If there is a previous loader on the inactive list,
+            // clean it up.
+            LoaderInfo info = mInactiveLoaders.get(mId);
+            if (info != null && info != this) {
+                info.destroy();
+                mInactiveLoaders.remove(mId);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(64);
+            sb.append("LoaderInfo{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" #");
+            sb.append(mId);
+            if (mArgs != null) {
+                sb.append(" ");
+                sb.append(mArgs.toString());
+            }
+            sb.append("}");
+            return sb.toString();
+        }
+    }
+    
+    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 (DEBUG) Log.v(TAG, "initLoader in " + this + ": cur=" + info);
+
+        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 (DEBUG) Log.v(TAG, "restartLoader in " + this + ": cur=" + info);
+        if (info != null) {
+            LoaderInfo inactive = mInactiveLoaders.get(id);
+            if (inactive != null) {
+                if (info.mData != null) {
+                    // This loader now has data...  we are probably being
+                    // called from within onLoadComplete, where we haven't
+                    // yet destroyed the last inactive loader.  So just do
+                    // that now.
+                    if (DEBUG) Log.v(TAG, "  Removing last inactive loader in " + this);
+                    inactive.destroy();
+                    mInactiveLoaders.put(id, info);
+                } else {
+                    // 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.
+                    if (DEBUG) Log.v(TAG, "  Removing intermediate loader in " + this);
+                    info.destroy();
+                }
+            } else {
+                // Keep track of the previous instance of this loader so we can destroy
+                // it when the new one completes.
+                if (DEBUG) Log.v(TAG, "  Making inactive: " + info);
+                mInactiveLoaders.put(id, info);
+            }
+        }
+        
+        info = createLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
+        return (Loader<D>)info.mLoader;
+    }
+    
+    public void stopLoader(int id) {
+        if (DEBUG) Log.v(TAG, "stopLoader in " + this + " of " + 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() {
+        if (DEBUG) Log.v(TAG, "Starting: " + this);
+
+        // 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() {
+        if (DEBUG) Log.v(TAG, "Stopping: " + this);
+
+        for (int i = mLoaders.size()-1; i >= 0; i--) {
+            mLoaders.valueAt(i).stop();
+        }
+        mStarted = false;
+    }
+    
+    void doRetain() {
+        if (DEBUG) Log.v(TAG, "Retaining: " + this);
+
+        mRetaining = true;
+        mStarted = false;
+        for (int i = mLoaders.size()-1; i >= 0; i--) {
+            mLoaders.valueAt(i).retain();
+        }
+    }
+    
+    void finishRetain() {
+        if (DEBUG) Log.v(TAG, "Finished Retaining: " + this);
+
+        mRetaining = false;
+        for (int i = mLoaders.size()-1; i >= 0; i--) {
+            mLoaders.valueAt(i).finishRetain();
+        }
+    }
+    
+    void doDestroy() {
+        if (!mRetaining) {
+            if (DEBUG) Log.v(TAG, "Destroying Active: " + this);
+            for (int i = mLoaders.size()-1; i >= 0; i--) {
+                mLoaders.valueAt(i).destroy();
+            }
+        }
+        
+        if (DEBUG) Log.v(TAG, "Destroying Inactive: " + this);
+        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..af71170
--- /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/Notification.java b/core/java/android/app/Notification.java
index 856943d..e602518 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -21,6 +21,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Parcel;
@@ -116,16 +117,55 @@
      * If this facility is used for something else, please give the user an option
      * to turn it off and use a normal notification, as this can be extremely
      * disruptive.
+     * 
+     * <p>Use with {@link #FLAG_HIGH_PRIORITY} to ensure that this notification
+     * will reach the user even when other notifications are suppressed.
      */
     public PendingIntent fullScreenIntent;
 
     /**
      * Text to scroll across the screen when this item is added to
-     * the status bar.
+     * the status bar on large and smaller devices.
+     *
+     * <p>This field is provided separately from the other ticker fields
+     * both for compatibility and to allow an application to choose different
+     * text for when the text scrolls in and when it is displayed all at once
+     * in conjunction with one or more icons.
+     *
+     * @see #tickerTitle
+     * @see #tickerSubtitle
+     * @see #tickerIcons
      */
     public CharSequence tickerText;
 
     /**
+     * The title line for the ticker over a the fat status bar on xlarge devices.
+     *
+     * @see #tickerText
+     * @see #tickerSubtitle
+     * @see #tickerIcons
+     */
+    public CharSequence tickerTitle;
+
+    /**
+     * The subtitle line for the ticker over a the fat status bar on xlarge devices.
+     *
+     * @see #tickerText
+     * @see #tickerTitle
+     * @see #tickerIcons
+     */
+    public CharSequence tickerSubtitle;
+
+    /**
+     * The icons to show to the left of the other ticker fields.
+     *
+     * @see #tickerText
+     * @see #tickerTitle
+     * @see #tickerSubtitle
+     */
+    public Bitmap[] tickerIcons;
+
+    /**
      * The view that will represent this notification in the expanded status bar.
      */
     public RemoteViews contentView;
@@ -275,6 +315,14 @@
      */
     public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
 
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that should be set if this notification
+     * represents a high-priority event that may be shown to the user even if notifications are
+     * otherwise unavailable (that is, when the status bar is hidden). This flag is ideally used
+     * in conjunction with {@link #fullScreenIntent}.
+     */
+    public static final int FLAG_HIGH_PRIORITY = 0x00000080;
+
     public int flags;
 
     /**
@@ -336,6 +384,21 @@
             tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         }
         if (parcel.readInt() != 0) {
+            tickerTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        }
+        if (parcel.readInt() != 0) {
+            tickerSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        }
+        final int tickerIconCount = parcel.readInt();
+        if (tickerIconCount >= 0) {
+            tickerIcons = new Bitmap[tickerIconCount];
+            for (int i=0; i<tickerIconCount; i++) {
+                if (parcel.readInt() != 0) {
+                    tickerIcons[i] = Bitmap.CREATOR.createFromParcel(parcel);
+                }
+            }
+        }
+        if (parcel.readInt() != 0) {
             contentView = RemoteViews.CREATOR.createFromParcel(parcel);
         }
         defaults = parcel.readInt();
@@ -371,6 +434,19 @@
         if (this.tickerText != null) {
             that.tickerText = this.tickerText.toString();
         }
+        if (this.tickerTitle != null) {
+            that.tickerTitle = this.tickerTitle.toString();
+        }
+        if (this.tickerSubtitle != null) {
+            that.tickerSubtitle = this.tickerSubtitle.toString();
+        }
+        if (this.tickerIcons != null) {
+            final int N = this.tickerIcons.length;
+            that.tickerIcons = new Bitmap[N];
+            for (int i=0; i<N; i++) {
+                that.tickerIcons[i] = Bitmap.createBitmap(this.tickerIcons[i]);
+            }
+        }
         if (this.contentView != null) {
             that.contentView = this.contentView.clone();
         }
@@ -427,6 +503,32 @@
         } else {
             parcel.writeInt(0);
         }
+        if (tickerTitle != null) {
+            parcel.writeInt(1);
+            TextUtils.writeToParcel(tickerTitle, parcel, flags);
+        } else {
+            parcel.writeInt(0);
+        }
+        if (tickerSubtitle != null) {
+            parcel.writeInt(1);
+            TextUtils.writeToParcel(tickerSubtitle, parcel, flags);
+        } else {
+            parcel.writeInt(0);
+        }
+        if (tickerIcons != null) {
+            final int N = tickerIcons.length;
+            parcel.writeInt(N);
+            for (int i=0; i<N; i++) {
+                if (tickerIcons[i] != null) {
+                    parcel.writeInt(1);
+                    tickerIcons[i].writeToParcel(parcel, flags);
+                } else {
+                    parcel.writeInt(0);
+                }
+            }
+        } else {
+            parcel.writeInt(-1);
+        }
         if (contentView != null) {
             parcel.writeInt(1);
             contentView.writeToParcel(parcel, 0);
@@ -543,6 +645,9 @@
         sb.append(Integer.toHexString(this.defaults));
         sb.append(",flags=0x");
         sb.append(Integer.toHexString(this.flags));
+        if ((this.flags & FLAG_HIGH_PRIORITY) != 0) {
+            sb.append("!!!1!one!");
+        }
         sb.append(")");
         return sb.toString();
     }
diff --git a/core/java/android/app/ProgressDialog.java b/core/java/android/app/ProgressDialog.java
index bdea069..07a5a22 100644
--- a/core/java/android/app/ProgressDialog.java
+++ b/core/java/android/app/ProgressDialog.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -73,7 +74,10 @@
     private Handler mViewUpdateHandler;
     
     public ProgressDialog(Context context) {
-        this(context, com.android.internal.R.style.Theme_Dialog_Alert);
+        this(context,
+                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB
+                ? com.android.internal.R.style.Theme_Holo_Dialog_Alert
+                : com.android.internal.R.style.Theme_Dialog_Alert);
     }
 
     public ProgressDialog(Context context, int theme) {
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 1f115e8..18602df 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -1083,7 +1083,7 @@
     protected void launchQuerySearch(int actionKey, String actionMsg)  {
         String query = mSearchAutoComplete.getText().toString();
         String action = Intent.ACTION_SEARCH;
-        Intent intent = createIntent(action, null, null, query, null,
+        Intent intent = createIntent(action, null, null, query,
                 actionKey, actionMsg);
         launchIntent(intent);
     }
@@ -1185,11 +1185,6 @@
             // use specific action if supplied, or default action if supplied, or fixed default
             String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
 
-            // some items are display only, or have effect via the cursor respond click reporting.
-            if (SearchManager.INTENT_ACTION_NONE.equals(action)) {
-                return null;
-            }
-
             if (action == null) {
                 action = mSearchable.getSuggestIntentAction();
             }
@@ -1211,14 +1206,10 @@
             }
             Uri dataUri = (data == null) ? null : Uri.parse(data);
 
-            String componentName = getColumnString(
-                    c, SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME);
-
             String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
             String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
 
-            return createIntent(action, dataUri, extraData, query, componentName, actionKey,
-                    actionMsg);
+            return createIntent(action, dataUri, extraData, query, actionKey, actionMsg);
         } catch (RuntimeException e ) {
             int rowNum;
             try {                       // be really paranoid now
@@ -1239,7 +1230,6 @@
      * @param data Intent data, or <code>null</code>.
      * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
      * @param query Intent query, or <code>null</code>.
-     * @param componentName Data for {@link SearchManager#COMPONENT_NAME_KEY} or <code>null</code>.
      * @param actionKey The key code of the action key that was pressed,
      *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
      * @param actionMsg The message for the action key that was pressed,
@@ -1249,7 +1239,7 @@
      * @return The intent.
      */
     private Intent createIntent(String action, Uri data, String extraData, String query,
-            String componentName, int actionKey, String actionMsg) {
+            int actionKey, String actionMsg) {
         // Now build the Intent
         Intent intent = new Intent(action);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 2e9cd96..6715012 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -16,16 +16,12 @@
 
 package android.app;
 
-import android.Manifest;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -40,7 +36,7 @@
 
 /**
  * This class provides access to the system search services.
- * 
+ *
  * <p>In practice, you won't interact with this class directly, as search
  * services are provided through methods in {@link android.app.Activity Activity}
  * and the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
@@ -56,7 +52,7 @@
  * href="{@docRoot}guide/topics/search/index.html">Search</a></strong>.</p>
  * </div>
  */
-public class SearchManager 
+public class SearchManager
         implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
 {
 
@@ -65,20 +61,20 @@
 
     /**
      * This is a shortcut definition for the default menu key to use for invoking search.
-     * 
+     *
      * See Menu.Item.setAlphabeticShortcut() for more information.
      */
     public final static char MENU_KEY = 's';
 
     /**
      * This is a shortcut definition for the default menu key to use for invoking search.
-     * 
+     *
      * See Menu.Item.setAlphabeticShortcut() for more information.
      */
     public final static int MENU_KEYCODE = KeyEvent.KEYCODE_S;
 
     /**
-     * Intent extra data key: Use this key with 
+     * Intent extra data key: Use this key with
      * {@link android.content.Intent#getStringExtra
      *  content.Intent.getStringExtra()}
      * to obtain the query string from Intent.ACTION_SEARCH.
@@ -103,7 +99,7 @@
      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
      * {@link android.content.Intent#getBundleExtra
      *  content.Intent.getBundleExtra()}
-     * to obtain any additional app-specific data that was inserted by the 
+     * to obtain any additional app-specific data that was inserted by the
      * activity that launched the search.
      */
     public final static String APP_DATA = "app_data";
@@ -127,7 +123,7 @@
      * file.
      */
     public final static String ACTION_KEY = "action_key";
-    
+
     /**
      * Intent extra data key: This key will be used for the extra populated by the
      * {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column.
@@ -153,11 +149,19 @@
      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
      * {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
      * to obtain the action message that was defined for a particular search action key and/or
-     * suggestion.  It will be null if the search was launched by typing "enter", touched the the 
-     * "GO" button, or other means not involving any action key. 
+     * suggestion.  It will be null if the search was launched by typing "enter", touched the the
+     * "GO" button, or other means not involving any action key.
      */
     public final static String ACTION_MSG = "action_msg";
-    
+
+    /**
+     * Flag to specify that the entry can be used for query refinement, i.e., the query text
+     * in the search field can be replaced with the text in this entry, when a query refinement
+     * icon is clicked. The suggestion list should show such a clickable icon beside the entry.
+     * <p>Use this flag as a bit-field for {@link #SUGGEST_COLUMN_FLAGS}.
+     */
+    public final static int FLAG_QUERY_REFINEMENT = 1 << 0;
+
     /**
      * Uri path for queried suggestions data.  This is the path that the search manager
      * will use when querying your content provider for suggestions data based on user input
@@ -182,12 +186,12 @@
      * @see #SUGGEST_COLUMN_SHORTCUT_ID
      */
     public final static String SUGGEST_URI_PATH_SHORTCUT = "search_suggest_shortcut";
-    
+
     /**
      * MIME type for shortcut validation.  You'll use this in your suggestions content provider
      * in the getType() function.
      */
-    public final static String SHORTCUT_MIME_TYPE = 
+    public final static String SHORTCUT_MIME_TYPE =
             "vnd.android.cursor.item/vnd.android.search.suggest";
 
     /**
@@ -195,7 +199,7 @@
      */
     public final static String SUGGEST_COLUMN_FORMAT = "suggest_format";
     /**
-     * Column name for suggestions cursor.  <i>Required.</i>  This is the primary line of text that 
+     * Column name for suggestions cursor.  <i>Required.</i>  This is the primary line of text that
      * will be presented to the user as the suggestion.
      */
     public final static String SUGGEST_COLUMN_TEXT_1 = "suggest_text_1";
@@ -227,8 +231,8 @@
      * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
      * </ul>
      *
-     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 
-     * for more information on these schemes. 
+     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
+     * for more information on these schemes.
      */
     public final static String SUGGEST_COLUMN_ICON_1 = "suggest_icon_1";
     /**
@@ -243,8 +247,8 @@
      * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
      * </ul>
      *
-     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 
-     * for more information on these schemes. 
+     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
+     * for more information on these schemes.
      */
     public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
     /**
@@ -276,12 +280,6 @@
      */
     public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
     /**
-     * TODO: Remove
-     *
-     * @hide
-     */
-    public final static String SUGGEST_COLUMN_INTENT_COMPONENT_NAME = "suggest_intent_component";
-    /**
      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
      * this element exists at the given row, then "/" and this value will be appended to the data
      * field in the Intent.  This should only be used if the data field has already been set to an
@@ -289,8 +287,8 @@
      */
     public final static String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id";
     /**
-     * Column name for suggestions cursor.  <i>Required if action is 
-     * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i>  If this 
+     * Column name for suggestions cursor.  <i>Required if action is
+     * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i>  If this
      * column exists <i>and</i> this element exists at the given row, this is the data that will be
      * used when forming the suggestion's query.
      */
@@ -307,15 +305,6 @@
     public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id";
 
     /**
-     * Column name for suggestions cursor. <i>Optional.</i>  This column is used to specify the
-     * cursor item's background color if it needs a non-default background color. A non-zero value
-     * indicates a valid background color to override the default.
-     *
-     * @hide For internal use, not part of the public API.
-     */
-    public final static String SUGGEST_COLUMN_BACKGROUND_COLOR = "suggest_background_color";
-    
-    /**
      * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify
      * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion
      * is being refreshed.
@@ -324,6 +313,15 @@
             "suggest_spinner_while_refreshing";
 
     /**
+     * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify
+     * additional flags per item. Multiple flags can be specified.
+     * <p>
+     * Must be one of {@link #FLAG_QUERY_REFINEMENT} or 0 to indicate no flags.
+     * </p>
+     */
+    public final static String SUGGEST_COLUMN_FLAGS = "suggest_flags";
+
+    /**
      * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion
      * should not be stored as a shortcut in global search.
      */
@@ -343,16 +341,16 @@
      * {@link #EXTRA_SELECT_QUERY},
      * {@link #APP_DATA}.
      */
-    public final static String INTENT_ACTION_GLOBAL_SEARCH 
+    public final static String INTENT_ACTION_GLOBAL_SEARCH
             = "android.search.action.GLOBAL_SEARCH";
-    
+
     /**
      * Intent action for starting the global search settings activity.
      * The global search provider should handle this intent.
      */
-    public final static String INTENT_ACTION_SEARCH_SETTINGS 
+    public final static String INTENT_ACTION_SEARCH_SETTINGS
             = "android.search.action.SEARCH_SETTINGS";
-    
+
     /**
      * Intent action for starting a web search provider's settings activity.
      * Web search providers should handle this intent if they have provider-specific
@@ -368,7 +366,7 @@
      */
     public final static String INTENT_ACTION_SEARCHABLES_CHANGED
             = "android.search.action.SEARCHABLES_CHANGED";
-    
+
     /**
      * Intent action broadcasted to inform that the search settings have changed in some way.
      * Either searchables have been enabled or disabled, or a different web search provider
@@ -378,14 +376,6 @@
             = "android.search.action.SETTINGS_CHANGED";
 
     /**
-     * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
-     * the search dialog will take no action.
-     *
-     * @hide
-     */
-    public final static String INTENT_ACTION_NONE = "android.search.action.ZILCH";
-
-    /**
      * This means that context is voice, and therefore the SearchDialog should
      * continue showing the microphone until the user indicates that he/she does
      * not want to re-speak (e.g. by typing).
@@ -413,7 +403,7 @@
      * The package associated with this seach manager.
      */
     private String mAssociatedPackage;
-    
+
     // package private since they are used by the inner class SearchManagerCallback
     /* package */ final Handler mHandler;
     /* package */ OnDismissListener mDismissListener = null;
@@ -427,15 +417,15 @@
         mService = ISearchManager.Stub.asInterface(
                 ServiceManager.getService(Context.SEARCH_SERVICE));
     }
-    
+
     /**
      * Launch search UI.
      *
      * <p>The search manager will open a search widget in an overlapping
-     * window, and the underlying activity may be obscured.  The search 
+     * window, and the underlying activity may be obscured.  The search
      * entry state will remain in effect until one of the following events:
      * <ul>
-     * <li>The user completes the search.  In most cases this will launch 
+     * <li>The user completes the search.  In most cases this will launch
      * a search intent.</li>
      * <li>The user uses the back, home, or other keys to exit the search.</li>
      * <li>The application calls the {@link #stopSearch}
@@ -443,8 +433,8 @@
      * activity from which it was launched.</li>
      *
      * <p>Most applications will <i>not</i> use this interface to invoke search.
-     * The primary method for invoking search is to call 
-     * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or 
+     * The primary method for invoking search is to call
+     * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or
      * {@link android.app.Activity#startSearch Activity.startSearch()}.
      *
      * @param initialQuery A search string can be pre-entered here, but this
@@ -456,19 +446,19 @@
      * and the user would expect to be able to keep typing.  <i>This parameter is only meaningful
      * if initialQuery is a non-empty string.</i>
      * @param launchActivity The ComponentName of the activity that has launched this search.
-     * @param appSearchData An application can insert application-specific 
-     * context here, in order to improve quality or specificity of its own 
+     * @param appSearchData An application can insert application-specific
+     * context here, in order to improve quality or specificity of its own
      * searches.  This data will be returned with SEARCH intent(s).  Null if
      * no extra data is required.
      * @param globalSearch If false, this will only launch the search that has been specifically
-     * defined by the application (which is usually defined as a local search).  If no default 
+     * defined by the application (which is usually defined as a local search).  If no default
      * search is defined in the current application or activity, global search will be launched.
      * If true, this will always launch a platform-global (e.g. web-based) search instead.
-     * 
+     *
      * @see android.app.Activity#onSearchRequested
      * @see #stopSearch
      */
-    public void startSearch(String initialQuery, 
+    public void startSearch(String initialQuery,
                             boolean selectInitialQuery,
                             ComponentName launchActivity,
                             Bundle appSearchData,
@@ -595,7 +585,7 @@
      * <p>Typically the user will terminate the search UI by launching a
      * search or by canceling.  This function allows the underlying application
      * or activity to cancel the search prematurely (for any reason).
-     * 
+     *
      * <p>This function can be safely called at any time (even if no search is active.)
      *
      * @see #startSearch
@@ -607,12 +597,12 @@
     }
 
     /**
-     * Determine if the Search UI is currently displayed.  
-     * 
+     * Determine if the Search UI is currently displayed.
+     *
      * This is provided primarily for application test purposes.
      *
      * @return Returns true if the search UI is currently displayed.
-     * 
+     *
      * @hide
      */
     public boolean isVisible() {
@@ -631,7 +621,7 @@
          */
         public void onDismiss();
     }
-    
+
     /**
      * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor
      * search UI state.
@@ -647,7 +637,7 @@
 
     /**
      * Set or clear the callback that will be invoked whenever the search UI is dismissed.
-     * 
+     *
      * @param listener The {@link OnDismissListener} to use, or null.
      */
     public void setOnDismissListener(final OnDismissListener listener) {
@@ -656,7 +646,7 @@
 
     /**
      * Set or clear the callback that will be invoked whenever the search UI is canceled.
-     * 
+     *
      * @param listener The {@link OnCancelListener} to use, or null.
      */
     public void setOnCancelListener(OnCancelListener listener) {
@@ -767,10 +757,10 @@
         // finally, make the query
         return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
     }
-     
+
     /**
      * Returns a list of the searchable activities that can be included in global search.
-     * 
+     *
      * @return a list containing searchable information for all searchable activities
      *         that have the <code>android:includeInGlobalSearch</code> attribute set
      *         in their searchable meta-data.
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index de544fb..fa7f794 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -55,6 +55,18 @@
     public static final int DISABLE_NOTIFICATION_TICKER = 0x00000008;
 
     /**
+     * Flag for {@link #disable} to hide the center system info area.
+     */
+    public static final int DISABLE_SYSTEM_INFO = 0x00000010;
+
+    /**
+     * Flag for {@link #disable} to hide only the navigation buttons.  Don't use this
+     * unless you're the setup wizard.
+     */
+    public static final int DISABLE_NAVIGATION = 0x00000020;
+
+
+    /**
      * Re-enable all of the status bar features that you've disabled.
      */
     public static final int DISABLE_NONE = 0x00000000;
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 8d8864f..5705bff 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -68,7 +68,6 @@
     private SearchableInfo mSearchable;
     private Context mProviderContext;
     private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
-    private SparseArray<Drawable.ConstantState> mBackgroundsCache;
     private boolean mClosed = false;
 
     // URL color
@@ -80,7 +79,6 @@
     private int mText2UrlCol;
     private int mIconName1Col;
     private int mIconName2Col;
-    private int mBackgroundColorCol;
 
     static final int NONE = -1;
 
@@ -109,7 +107,6 @@
         mProviderContext = mSearchable.getProviderContext(mContext, activityContext);
 
         mOutsideDrawablesCache = outsideDrawablesCache;
-        mBackgroundsCache = new SparseArray<Drawable.ConstantState>();
 
         mStartSpinnerRunnable = new Runnable() {
                 public void run() {
@@ -243,8 +240,6 @@
                 mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
                 mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
                 mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
-                mBackgroundColorCol =
-                        c.getColumnIndex(SearchManager.SUGGEST_COLUMN_BACKGROUND_COLOR);
             }
         } catch (Exception e) {
             Log.e(LOG_TAG, "error changing cursor and caching columns", e);
@@ -283,13 +278,6 @@
     public void bindView(View view, Context context, Cursor cursor) {
         ChildViewCache views = (ChildViewCache) view.getTag();
 
-        int backgroundColor = 0;
-        if (mBackgroundColorCol != -1) {
-            backgroundColor = cursor.getInt(mBackgroundColorCol);
-        }
-        Drawable background = getItemBackground(backgroundColor);
-        view.setBackgroundDrawable(background);
-
         if (views.mText1 != null) {
             String text1 = getStringOrNull(cursor, mText1Col);
             setViewText(views.mText1, text1);
@@ -342,33 +330,6 @@
         return text;
     }
 
-    /**
-     * Gets a drawable with no color when selected or pressed, and the given color when
-     * neither selected nor pressed.
-     *
-     * @return A drawable, or {@code null} if the given color is transparent.
-     */
-    private Drawable getItemBackground(int backgroundColor) {
-        if (backgroundColor == 0) {
-            return null;
-        } else {
-            Drawable.ConstantState cachedBg = mBackgroundsCache.get(backgroundColor);
-            if (cachedBg != null) {
-                if (DBG) Log.d(LOG_TAG, "Background cache hit for color " + backgroundColor);
-                return cachedBg.newDrawable(mProviderContext.getResources());
-            }
-            if (DBG) Log.d(LOG_TAG, "Creating new background for color " + backgroundColor);
-            ColorDrawable transparent = new ColorDrawable(0);
-            ColorDrawable background = new ColorDrawable(backgroundColor);
-            StateListDrawable newBg = new StateListDrawable();
-            newBg.addState(new int[]{android.R.attr.state_selected}, transparent);
-            newBg.addState(new int[]{android.R.attr.state_pressed}, transparent);
-            newBg.addState(new int[]{}, background);
-            mBackgroundsCache.put(backgroundColor, newBg.getConstantState());
-            return newBg;
-        }
-    }
-
     private void setViewText(TextView v, CharSequence text) {
         // Set the text even if it's null, since we need to clear any previous text.
         v.setText(text);
@@ -601,21 +562,7 @@
      * @return A non-null drawable.
      */
     private Drawable getDefaultIcon1(Cursor cursor) {
-        // First check the component that the suggestion is originally from
-        String c = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME);
-        if (c != null) {
-            ComponentName component = ComponentName.unflattenFromString(c);
-            if (component != null) {
-                Drawable drawable = getActivityIconWithCache(component);
-                if (drawable != null) {
-                    return drawable;
-                }
-            } else {
-                Log.w(LOG_TAG, "Bad component name: " + c);
-            }
-        }
-
-        // Then check the component that gave us the suggestion
+        // Check the component that gave us the suggestion
         Drawable drawable = getActivityIconWithCache(mSearchable.getSearchActivity());
         if (drawable != null) {
             return drawable;
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index 521d41c..381143c 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
+import android.os.Build;
 import android.os.Bundle;
 import android.text.format.DateFormat;
 import android.view.LayoutInflater;
@@ -76,7 +77,10 @@
     public TimePickerDialog(Context context,
             OnTimeSetListener callBack,
             int hourOfDay, int minute, boolean is24HourView) {
-        this(context, com.android.internal.R.style.Theme_Dialog_Alert,
+        this(context,
+                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB
+                        ? com.android.internal.R.style.Theme_Holo_Dialog_Alert
+                        : com.android.internal.R.style.Theme_Dialog_Alert,
                 callBack, hourOfDay, minute, is24HourView);
     }
 
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..7730942 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,13 @@
             msg.obj = info;
             msg.sendToTarget();
         }
+
+        public void viewDataChanged(int appWidgetId, int viewId) {
+            Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED);
+            msg.arg1 = appWidgetId;
+            msg.arg2 = viewId;
+            msg.sendToTarget();
+        }
     }
 
     class UpdateHandler extends Handler {
@@ -77,6 +85,10 @@
                     onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
                     break;
                 }
+                case HANDLE_VIEW_DATA_CHANGED: {
+                    viewDataChanged(msg.arg1, msg.arg2);
+                    break;
+                }
             }
         }
     }
@@ -250,6 +262,16 @@
             v.updateAppWidget(views);
         }
     }
+
+    void viewDataChanged(int appWidgetId, int viewId) {
+        AppWidgetHostView v;
+        synchronized (mViews) {
+            v = mViews.get(appWidgetId);
+        }
+        if (v != null) {
+            v.viewDataChanged(viewId);
+        }
+    }
 }
 
 
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index b33b097..4f8ee93 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(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..2a583c1 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -233,6 +233,10 @@
     /**
      * Set the RemoteViews to use for the specified appWidgetIds.
      *
+     * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
+     * contain a complete representation of the widget. For performing partial widget updates, see
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)}.
+     *
      * <p>
      * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
      * and outside of the handler.
@@ -253,6 +257,10 @@
     /**
      * Set the RemoteViews to use for the specified appWidgetId.
      *
+     * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
+     * contain a complete representation of the widget. For performing partial widget updates, see
+     * {@link #partiallyUpdateAppWidget(int, RemoteViews)}.
+     *
      * <p>
      * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
      * and outside of the handler.
@@ -266,6 +274,59 @@
     }
 
     /**
+     * Perform an incremental update or command on the widget(s) specified by appWidgetIds.
+     *
+     * This update  differs from {@link #updateAppWidget(int[], RemoteViews)} in that the
+     * RemoteViews object which is passed is understood to be an incomplete representation of the 
+     * widget, and hence is not cached by the AppWidgetService. Note that because these updates are 
+     * not cached, any state that they modify that is not restored by restoreInstanceState will not
+     * persist in the case that the widgets are restored using the cached version in
+     * AppWidgetService.
+     *
+     * Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)},
+     * {@link RemoteViews#setScrollPosition(int, int)} and similar commands.
+     *
+     * <p>
+     * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
+     * and outside of the handler.
+     * This method will only work when called from the uid that owns the AppWidget provider.
+     *
+     * @param appWidgetIds     The AppWidget instances for which to set the RemoteViews.
+     * @param views            The RemoteViews object containing the incremental update / command.
+     */
+    public void partiallyUpdateAppWidget(int[] appWidgetIds, RemoteViews views) {
+        try {
+            sService.partiallyUpdateAppWidgetIds(appWidgetIds, views);
+        } catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Perform an incremental update or command on the widget specified by appWidgetId.
+     *
+     * This update  differs from {@link #updateAppWidget(int, RemoteViews)} in that the RemoteViews
+     * object which is passed is understood to be an incomplete representation of the widget, and
+     * hence is not cached by the AppWidgetService. Note that because these updates are not cached,
+     * any state that they modify that is not restored by restoreInstanceState will not persist in
+     * the case that the widgets are restored using the cached version in AppWidgetService.
+     *
+     * Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)},
+     * {@link RemoteViews#setScrollPosition(int, int)} and similar commands.
+     *
+     * <p>
+     * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
+     * and outside of the handler.
+     * This method will only work when called from the uid that owns the AppWidget provider.
+     *
+     * @param appWidgetId      The AppWidget instance for which to set the RemoteViews.
+     * @param views            The RemoteViews object containing the incremental update / command.
+     */
+    public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views) {
+        partiallyUpdateAppWidget(new int[] { appWidgetId }, views);
+    }
+
+    /**
      * Set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider.
      *
      * <p>
@@ -288,11 +349,46 @@
     }
 
     /**
+     * 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 viewId        The collection view id.
+     */
+    public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
+        try {
+            sService.notifyAppWidgetViewDataChanged(appWidgetIds, 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 viewId        The collection view id.
+     */
+    public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
+        notifyAppWidgetViewDataChanged(new int[] { appWidgetId }, 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/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 7e5f858..d308a5c 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -18,88 +18,104 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.server.BluetoothA2dpService;
 import android.content.Context;
-import android.os.ServiceManager;
-import android.os.RemoteException;
 import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.server.BluetoothA2dpService;
 import android.util.Log;
 
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.Set;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Set;
+
 
 /**
- * Public API for controlling the Bluetooth A2DP Profile Service.
+ * This class provides the public APIs to control the Bluetooth A2DP
+ * profile.
  *
- * BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
- * Service via IPC.
+ *<p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothA2dp proxy object.
  *
- * Creating a BluetoothA2dp object will initiate a binding with the
- * BluetoothHeadset service. Users of this object should call close() when they
- * are finished, so that this proxy object can unbind from the service.
- *
- * Currently the BluetoothA2dp service runs in the system server and this
- * proxy object will be immediately bound to the service on construction.
- *
- * Currently this class provides methods to connect to A2DP audio sinks.
- *
- * @hide
+ * <p> Android only supports one connected Bluetooth A2dp device at a time.
+ * Each method is protected with its appropriate permission.
  */
-public final class BluetoothA2dp {
+public final class BluetoothA2dp implements BluetoothProfile {
     private static final String TAG = "BluetoothA2dp";
     private static final boolean DBG = false;
 
-    /** int extra for ACTION_SINK_STATE_CHANGED */
-    public static final String EXTRA_SINK_STATE =
-        "android.bluetooth.a2dp.extra.SINK_STATE";
-    /** int extra for ACTION_SINK_STATE_CHANGED */
-    public static final String EXTRA_PREVIOUS_SINK_STATE =
-        "android.bluetooth.a2dp.extra.PREVIOUS_SINK_STATE";
-
-    /** Indicates the state of an A2DP audio sink has changed.
-     * This intent will always contain EXTRA_SINK_STATE,
-     * EXTRA_PREVIOUS_SINK_STATE and BluetoothDevice.EXTRA_DEVICE
-     * extras.
+    /**
+     * Intent used to broadcast the change in connection state of the A2DP
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * {@link #EXTRA_STATE} - The current state of the profile.
+     * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
+     * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+     *
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_SINK_STATE_CHANGED =
-        "android.bluetooth.a2dp.action.SINK_STATE_CHANGED";
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.a2dp.profile.action.CONNECTION_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;
-    /** Playing implies connected */
-    public static final int STATE_PLAYING    = 4;
+    /**
+     * Intent used to broadcast the change in the Playing state of the A2DP
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * {@link #EXTRA_STATE} - The current state of the profile.
+     * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
+     * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+     *
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PLAYING_STATE_CHANGED =
+        "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
 
-    /** Default priority for a2dp devices that we try to auto-connect
-     * and allow incoming connections */
-    public static final int PRIORITY_AUTO_CONNECT = 1000;
-    /** Default priority for a2dp devices that should allow incoming
-     * connections */
-    public static final int PRIORITY_ON = 100;
-    /** Default priority for a2dp devices that should not allow incoming
-     * connections */
-    public static final int PRIORITY_OFF = 0;
-    /** Default priority when not set or when the device is unpaired */
-    public static final int PRIORITY_UNDEFINED = -1;
+    /**
+     * A2DP sink device is streaming music. This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+     */
+    public static final int STATE_PLAYING   =  10;
 
-    private final IBluetoothA2dp mService;
-    private final Context mContext;
+    /**
+     * A2DP sink device is NOT streaming music. This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+     */
+    public static final int STATE_NOT_PLAYING   =  11;
+
+    private ServiceListener mServiceListener;
+    private IBluetoothA2dp mService;
+    private BluetoothAdapter mAdapter;
 
     /**
      * Create a BluetoothA2dp proxy object for interacting with the local
      * Bluetooth A2DP service.
-     * @param c Context
+     *
      */
-    public BluetoothA2dp(Context c) {
-        mContext = c;
-
+    /*package*/ BluetoothA2dp(Context mContext, ServiceListener l) {
         IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE);
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
         if (b != null) {
             mService = IBluetoothA2dp.Stub.asInterface(b);
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.A2DP, this);
+            }
         } else {
             Log.w(TAG, "Bluetooth A2DP service not available!");
 
@@ -109,167 +125,222 @@
         }
     }
 
-    /** Initiate a connection to an A2DP sink.
-     *  Listen for SINK_STATE_CHANGED_ACTION to find out when the
-     *  connection is completed.
-     *  @param device Remote BT device.
-     *  @return false on immediate error, true otherwise
-     *  @hide
+    /**
+     * {@inheritDoc}
+     * @hide
      */
-    public boolean connectSink(BluetoothDevice device) {
-        if (DBG) log("connectSink(" + device + ")");
-        try {
-            return mService.connectSink(device);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return false;
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
+            try {
+                return mService.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
         }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
     }
 
-    /** Initiate disconnect from an A2DP sink.
-     *  Listen for SINK_STATE_CHANGED_ACTION to find out when
-     *  disconnect is completed.
-     *  @param device Remote BT device.
-     *  @return false on immediate error, true otherwise
-     *  @hide
+    /**
+     * {@inheritDoc}
+     * @hide
      */
-    public boolean disconnectSink(BluetoothDevice device) {
-        if (DBG) log("disconnectSink(" + device + ")");
-        try {
-            return mService.disconnectSink(device);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return false;
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
+            try {
+                return mService.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
         }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
     }
 
-    /** Initiate suspend from an A2DP sink.
-     *  Listen for SINK_STATE_CHANGED_ACTION to find out when
-     *  suspend is completed.
-     *  @param device Remote BT device.
-     *  @return false on immediate error, true otherwise
-     *  @hide
+    /**
+     * {@inheritDoc}
+     */
+    public Set<BluetoothDevice> getConnectedDevices() {
+        if (DBG) log("getConnectedDevices()");
+        if (mService != null && isEnabled()) {
+            try {
+                return toDeviceSet(mService.getConnectedDevices());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return toDeviceSet(new BluetoothDevice[0]);
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return toDeviceSet(new BluetoothDevice[0]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) log("getDevicesMatchingStates()");
+        if (mService != null && isEnabled()) {
+            try {
+                return toDeviceSet(mService.getDevicesMatchingConnectionStates(states));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return toDeviceSet(new BluetoothDevice[0]);
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return toDeviceSet(new BluetoothDevice[0]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) log("getState(" + device + ")");
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF &&
+                priority != BluetoothProfile.PRIORITY_ON) {
+              return false;
+            }
+            try {
+                return mService.setPriority(device, priority);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    public int getPriority(BluetoothDevice device) {
+        if (DBG) log("getPriority(" + device + ")");
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.getPriority(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.PRIORITY_OFF;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.PRIORITY_OFF;
+    }
+
+    /**
+     * Check if A2DP profile is streaming music.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param device BluetoothDevice device
+     */
+    public boolean isA2dpPlaying(BluetoothDevice device) {
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.isA2dpPlaying(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Initiate suspend from an A2DP sink.
+     *
+     * <p> This API will return false in scenarios like the A2DP
+     * device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED}
+     * intent will be broadcasted with the state. Users can get the
+     * state of the A2DP device from this intent.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @param device Remote A2DP sink
+     * @return false on immediate error,
+     *               true otherwise
+     * @hide
      */
     public boolean suspendSink(BluetoothDevice device) {
-        try {
-            return mService.suspendSink(device);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return false;
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.suspendSink(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
         }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
     }
 
-    /** Initiate resume from an suspended A2DP sink.
-     *  Listen for SINK_STATE_CHANGED_ACTION to find out when
-     *  resume is completed.
-     *  @param device Remote BT device.
-     *  @return false on immediate error, true otherwise
-     *  @hide
+    /**
+     * Initiate resume from a suspended A2DP sink.
+     *
+     * <p> This API will return false in scenarios like the A2DP
+     * device is not in suspended state etc. When this API returns,
+     * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED}
+     * intent will be broadcasted with the state. Users can get the
+     * state of the A2DP device from this intent.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @param device Remote A2DP sink
+     * @return false on immediate error,
+     *               true otherwise
+     * @hide
      */
     public boolean resumeSink(BluetoothDevice device) {
-        try {
-            return mService.resumeSink(device);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return false;
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.resumeSink(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
         }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
     }
 
-    /** Check if a specified A2DP sink is connected.
-     *  @param device Remote BT device.
-     *  @return True if connected (or playing), false otherwise and on error.
-     *  @hide
-     */
-    public boolean isSinkConnected(BluetoothDevice device) {
-        if (DBG) log("isSinkConnected(" + device + ")");
-        int state = getSinkState(device);
-        return state == STATE_CONNECTED || state == STATE_PLAYING;
-    }
-
-    /** Check if any A2DP sink is connected.
-     * @return a unmodifiable set of connected A2DP sinks, or null on error.
-     * @hide
-     */
-    public Set<BluetoothDevice> getConnectedSinks() {
-        if (DBG) log("getConnectedSinks()");
-        try {
-            return Collections.unmodifiableSet(
-                    new HashSet<BluetoothDevice>(Arrays.asList(mService.getConnectedSinks())));
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return null;
-        }
-    }
-
-    /** Check if any A2DP sink is in Non Disconnected state
-     * i.e playing, connected, connecting, disconnecting.
-     * @return a unmodifiable set of connected A2DP sinks, or null on error.
-     * @hide
-     */
-    public Set<BluetoothDevice> getNonDisconnectedSinks() {
-        if (DBG) log("getNonDisconnectedSinks()");
-        try {
-            return Collections.unmodifiableSet(
-                    new HashSet<BluetoothDevice>(Arrays.asList(mService.getNonDisconnectedSinks())));
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return null;
-        }
-    }
-
-    /** Get the state of an A2DP sink
-     *  @param device Remote BT device.
-     *  @return State code, one of STATE_
-     *  @hide
-     */
-    public int getSinkState(BluetoothDevice device) {
-        if (DBG) log("getSinkState(" + device + ")");
-        try {
-            return mService.getSinkState(device);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return BluetoothA2dp.STATE_DISCONNECTED;
-        }
-    }
-
-    /**
-     * Set priority of a2dp sink.
-     * Priority is a non-negative integer. By default paired sinks will have
-     * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
-     * Sinks with priority greater than zero will accept incoming connections
-     * (if no sink is currently connected).
-     * Priority for unpaired sink must be PRIORITY_NONE.
-     * @param device Paired sink
-     * @param priority Integer priority, for example PRIORITY_AUTO or
-     *                 PRIORITY_NONE
-     * @return true if priority is set, false on error
-     */
-    public boolean setSinkPriority(BluetoothDevice device, int priority) {
-        if (DBG) log("setSinkPriority(" + device + ", " + priority + ")");
-        try {
-            return mService.setSinkPriority(device, priority);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return false;
-        }
-    }
-
-    /**
-     * Get priority of a2dp sink.
-     * @param device Sink
-     * @return non-negative priority, or negative error code on error.
-     */
-    public int getSinkPriority(BluetoothDevice device) {
-        if (DBG) log("getSinkPriority(" + device + ")");
-        try {
-            return mService.getSinkPriority(device);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return PRIORITY_OFF;
-        }
-    }
-
-    /** Helper for converting a state to a string.
+     /**
+     * Helper for converting a state to a string.
+     *
      * For debug use only - strings are not internationalized.
      * @hide
      */
@@ -285,12 +356,31 @@
             return "disconnecting";
         case STATE_PLAYING:
             return "playing";
+        case STATE_NOT_PLAYING:
+          return "not playing";
         default:
             return "<unknown state " + state + ">";
         }
     }
 
+    private boolean isEnabled() {
+       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+       return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+       if (device == null) return false;
+
+       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+       return false;
+    }
+
+    private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) {
+       return Collections.unmodifiableSet(
+          new HashSet<BluetoothDevice>(Arrays.asList(devices)));
+    }
+
     private static void log(String msg) {
-        Log.d(TAG, msg);
+      Log.d(TAG, msg);
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 3040319..c66b2de 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -279,6 +280,61 @@
      */
     public static final String EXTRA_LOCAL_NAME = "android.bluetooth.adapter.extra.LOCAL_NAME";
 
+    /**
+     * Intent used to broadcast the change in connection state of the local
+     * Bluetooth adapter to a profile of the remote device. When the adapter is
+     * not connected to any profiles of any remote devices and it attempts a
+     * connection to a profile this intent will sent. Once connected, this intent
+     * will not be sent for any more connection attempts to any profiles of any
+     * remote device. When the adapter disconnects from the last profile its
+     * connected to of any remote device, this intent will be sent.
+     *
+     * <p> This intent is useful for applications that are only concerned about
+     * whether the local adapter is connected to any profile of any device and
+     * are not really concerned about which profile. For example, an application
+     * which displays an icon to display whether Bluetooth is connected or not
+     * can use this intent.
+     *
+     * <p>This intent will have 3 extras:
+     * {@link #EXTRA_STATE} - The current state.
+     * {@link #EXTRA_PREVIOUS_STATE}- The previous.
+     * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+     *
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED}
+     *
+     * This extra represents the current connection state.
+     */
+    public static final String EXTRA_CONNECTION_STATE =
+        "android.bluetooth.adapter.extra.CONNECTION_STATE";
+
+    /**
+     * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED}
+     *
+     * This extra represents the previous connection state.
+     */
+    public static final String EXTRA_PREVIOUS_CONNECTION_STATE =
+          "android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE";
+
+    /** The profile is in disconnected state */
+    public static final int STATE_DISCONNECTED  = 0;
+    /** The profile is in connecting state */
+    public static final int STATE_CONNECTING    = 1;
+    /** The profile is in connected state */
+    public static final int STATE_CONNECTED     = 2;
+    /** The profile is in disconnecting state */
+    public static final int STATE_DISCONNECTING = 3;
+
     /** @hide */
     public static final String BLUETOOTH_SERVICE = "bluetooth";
 
@@ -896,6 +952,54 @@
         return null;
     }
 
+    /*
+     * Get the profile proxy object associated with the profile.
+     *
+     * <p>Profile can be one of {@link BluetoothProfile.HEADSET} or
+     * {@link BluetoothProfile.A2DP}. Clients must implements
+     * {@link BluetoothProfile.ServiceListener} to get notified of
+     * the connection status and to get the proxy object.
+     *
+     * @param context Context of the application
+     * @param listener The service Listener for connection callbacks.
+     * @param profile
+     * @return true on success, false on error
+     */
+    public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
+                                   int profile) {
+        if (context == null || listener == null) return false;
+
+        if (profile == BluetoothProfile.HEADSET) {
+            BluetoothHeadset headset = new BluetoothHeadset(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.A2DP) {
+            BluetoothA2dp a2dp = new BluetoothA2dp(context, listener);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Close the connection of the profile proxy to the Service.
+     *
+     * <p> Clients should call this when they are no longer using
+     * the proxy obtained from {@link #getProfileProxy}.
+     * Profile can be one of {@link BluetoothProfile#HEADSET} or
+     * {@link BluetoothProfile#A2DP}
+     *
+     * @param profile
+     * @param proxy Profile proxy object
+     */
+    public void closeProfileProxy(int profile, BluetoothProfile proxy) {
+        if (profile == BluetoothProfile.HEADSET) {
+            BluetoothHeadset headset = (BluetoothHeadset)proxy;
+            if (headset != null) {
+                headset.close();
+            }
+        }
+    }
+
     private Set<BluetoothDevice> toDeviceSet(String[] addresses) {
         Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length);
         for (int i = 0; i < addresses.length; i++) {
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
index 6a878d7..e604e6b 100644
--- a/core/java/android/bluetooth/BluetoothClass.java
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -259,6 +259,12 @@
     public static final int PROFILE_A2DP = 1;
     /** @hide */
     public static final int PROFILE_OPP = 2;
+    /** @hide */
+    public static final int PROFILE_HID = 3;
+    /** @hide */
+    public static final int PROFILE_PANU = 4;
+    /** @hide */
+    public static final int PROFILE_NAP = 5;
 
     /**
      * Check class bits for possible bluetooth profile support.
@@ -324,6 +330,14 @@
                 default:
                     return false;
             }
+        } else if (profile == PROFILE_HID) {
+            return (getDeviceClass() & Device.Major.PERIPHERAL) == Device.Major.PERIPHERAL;
+        } else if (profile == PROFILE_PANU || profile == PROFILE_NAP){
+            // No good way to distinguish between the two, based on class bits.
+            if (hasService(Service.NETWORKING)) {
+                return true;
+            }
+            return (getDeviceClass() & Device.Major.NETWORKING) == Device.Major.NETWORKING;
         } else {
             return false;
         }
diff --git a/core/java/android/bluetooth/BluetoothDevicePicker.java b/core/java/android/bluetooth/BluetoothDevicePicker.java
index 3a07033..c794be2 100644
--- a/core/java/android/bluetooth/BluetoothDevicePicker.java
+++ b/core/java/android/bluetooth/BluetoothDevicePicker.java
@@ -64,4 +64,9 @@
     public static final int FILTER_TYPE_AUDIO = 1;
     /** Ask device picker to show BT devices that support Object Transfer */
     public static final int FILTER_TYPE_TRANSFER = 2;
+    /** Ask device picker to show BT devices that support
+     * Personal Area Networking User (PANU) profile*/
+    public static final int FILTER_TYPE_PANU = 3;
+    /** Ask device picker to show BT devices that support Network Access Point (NAP) profile */
+    public static final int FILTER_TYPE_NAP = 4;
 }
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index 8e655e2..e460839 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Message;
+import android.bluetooth.BluetoothAdapter;
 import android.server.BluetoothA2dpService;
 import android.server.BluetoothService;
 import android.util.Log;
@@ -28,6 +29,8 @@
 import com.android.internal.util.HierarchicalState;
 import com.android.internal.util.HierarchicalStateMachine;
 
+import java.util.Set;
+
 /**
  * This class is the Profile connection state machine associated with a remote
  * device. When the device bonds an instance of this class is created.
@@ -58,19 +61,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,16 +87,18 @@
     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;
     private BluetoothA2dpService mA2dpService;
     private BluetoothHeadset  mHeadsetService;
-    private boolean mHeadsetServiceConnected;
 
     private BluetoothDevice mDevice;
-    private int mHeadsetState;
-    private int mA2dpState;
+    private int mHeadsetState = BluetoothProfile.STATE_DISCONNECTED;
+    private int mA2dpState = BluetoothProfile.STATE_DISCONNECTED;
+    private int mHidState = BluetoothProfile.STATE_DISCONNECTED;
 
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -97,32 +107,42 @@
             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
             if (!device.equals(mDevice)) return;
 
-            if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
-                int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
-                int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0);
-                int initiator = intent.getIntExtra(
-                    BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
-                    BluetoothHeadset.LOCAL_DISCONNECT);
-                mHeadsetState = newState;
-                if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
-                    initiator == BluetoothHeadset.REMOTE_DISCONNECT) {
-                    sendMessage(DISCONNECT_HFP_INCOMING);
-                }
-                if (newState == BluetoothHeadset.STATE_CONNECTED ||
-                    newState == BluetoothHeadset.STATE_DISCONNECTED) {
-                    sendMessage(TRANSITION_TO_STABLE);
-                }
-            } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
-                int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
-                int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0);
+            if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
+                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
+                int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
                 mA2dpState = newState;
-                if ((oldState == BluetoothA2dp.STATE_CONNECTED ||
-                           oldState == BluetoothA2dp.STATE_PLAYING) &&
-                           newState == BluetoothA2dp.STATE_DISCONNECTED) {
+                if (oldState == BluetoothA2dp.STATE_CONNECTED &&
+                    newState == BluetoothA2dp.STATE_DISCONNECTED) {
                     sendMessage(DISCONNECT_A2DP_INCOMING);
                 }
-                if (newState == BluetoothA2dp.STATE_CONNECTED ||
-                    newState == BluetoothA2dp.STATE_DISCONNECTED) {
+                if (newState == BluetoothProfile.STATE_CONNECTED ||
+                    newState == BluetoothProfile.STATE_DISCONNECTED) {
+                    sendMessage(TRANSITION_TO_STABLE);
+                }
+            } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
+                int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
+
+                mHeadsetState = newState;
+                if (oldState == BluetoothHeadset.STATE_CONNECTED &&
+                    newState == BluetoothHeadset.STATE_DISCONNECTED) {
+                    sendMessage(DISCONNECT_HFP_INCOMING);
+                }
+                if (newState == BluetoothProfile.STATE_CONNECTED ||
+                    newState == BluetoothProfile.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)) {
@@ -165,34 +185,37 @@
         addState(mIncomingHandsfree);
         addState(mIncomingA2dp);
         addState(mOutgoingA2dp);
+        addState(mOutgoingHid);
+        addState(mIncomingHid);
         setInitialState(mBondedDevice);
 
         IntentFilter filter = new IntentFilter();
         // Fine-grained state broadcasts
-        filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
-        filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);
 
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
-        HeadsetServiceListener l = new HeadsetServiceListener();
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+                                BluetoothProfile.HEADSET);
     }
 
-    private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
-        public HeadsetServiceListener() {
-            mHeadsetService = new BluetoothHeadset(mContext, this);
-        }
-        public void onServiceConnected() {
+    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+        new BluetoothProfile.ServiceListener() {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
             synchronized(BluetoothDeviceProfileState.this) {
-                mHeadsetServiceConnected = true;
+                mHeadsetService = (BluetoothHeadset) proxy;
             }
         }
-        public void onServiceDisconnected() {
+        public void onServiceDisconnected(int profile) {
             synchronized(BluetoothDeviceProfileState.this) {
-                mHeadsetServiceConnected = false;
+                mHeadsetService = null;
             }
         }
-    }
+    };
 
     private class BondedDevice extends HierarchicalState {
         @Override
@@ -224,6 +247,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 +264,10 @@
                         sendMessage(DISCONNECT_A2DP_OUTGOING);
                         deferMessage(message);
                         break;
+                    } else if (mHidState != BluetoothInputDevice.STATE_DISCONNECTED) {
+                        sendMessage(DISCONNECT_HID_OUTGOING);
+                        deferMessage(message);
+                        break;
                     }
                     processCommand(UNPAIR);
                     break;
@@ -240,19 +275,29 @@
                     if (isPhoneDocked(mDevice)) {
                         // Don't auto connect to docks.
                         break;
-                    } else if (!mHeadsetServiceConnected) {
+                    } else if (mHeadsetService == null) {
                         deferMessage(message);
                     } else {
                         if (mHeadsetService.getPriority(mDevice) ==
                               BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
-                              !mHeadsetService.isConnected(mDevice)) {
-                            mHeadsetService.connectHeadset(mDevice);
+                              mHeadsetService.getDevicesMatchingConnectionStates(
+                                  new int[] {BluetoothProfile.STATE_CONNECTED,
+                                             BluetoothProfile.STATE_CONNECTING,
+                                             BluetoothProfile.STATE_DISCONNECTING}).size() == 0) {
+                            mHeadsetService.connect(mDevice);
                         }
                         if (mA2dpService != null &&
-                              mA2dpService.getSinkPriority(mDevice) ==
+                              mA2dpService.getPriority(mDevice) ==
                               BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
-                              mA2dpService.getConnectedSinks().length == 0) {
-                            mA2dpService.connectSink(mDevice);
+                              mA2dpService.getDevicesMatchingConnectionStates(
+                                  new int[] {BluetoothA2dp.STATE_CONNECTED,
+                                             BluetoothProfile.STATE_CONNECTING,
+                                             BluetoothProfile.STATE_DISCONNECTING}).length == 0) {
+                            mA2dpService.connect(mDevice);
+                        }
+                        if (mService.getInputDevicePriority(mDevice) ==
+                              BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
+                            mService.connectInputDevice(mDevice);
                         }
                     }
                     break;
@@ -342,6 +387,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 +471,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 +565,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 +647,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,11 +669,148 @@
     }
 
 
+    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 ) {
             // Cancel the outgoing thread.
-            if (mHeadsetServiceConnected) {
+            if (mHeadsetService != null) {
                 mHeadsetService.cancelConnectThread();
             }
             // HeadsetService is down. Phone process most likely crashed.
@@ -598,12 +828,14 @@
         log("Processing command:" + command);
         switch(command) {
             case  CONNECT_HFP_OUTGOING:
-                if (mHeadsetService != null) {
+                if (mHeadsetService == null) {
+                    deferHeadsetMessage(command);
+                } else {
                     return mHeadsetService.connectHeadsetInternal(mDevice);
                 }
                 break;
             case CONNECT_HFP_INCOMING:
-                if (!mHeadsetServiceConnected) {
+                if (mHeadsetService == null) {
                     deferHeadsetMessage(command);
                 } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
                     return mHeadsetService.acceptIncomingConnect(mDevice);
@@ -619,8 +851,12 @@
             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) {
+                if (mHeadsetService == null) {
                     deferHeadsetMessage(command);
                 } else {
                     if (mHeadsetService.getPriority(mDevice) ==
@@ -638,13 +874,22 @@
                 return true;
             case DISCONNECT_A2DP_OUTGOING:
                 if (mA2dpService != null) {
-                    if (mA2dpService.getSinkPriority(mDevice) ==
+                    if (mA2dpService.getPriority(mDevice) ==
                         BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
-                        mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
+                        mA2dpService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
                     }
                     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 +898,7 @@
         return false;
     }
 
+
     /*package*/ BluetoothDevice getDevice() {
         return mDevice;
     }
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index da1aa45..0496b1f 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -18,6 +18,8 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -26,63 +28,66 @@
 import android.os.IBinder;
 import android.util.Log;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
- * The Android Bluetooth API is not finalized, and *will* change. Use at your
- * own risk.
- *
  * Public API for controlling the Bluetooth Headset Service. This includes both
- * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will
- * attempt a handsfree connection first, and fall back to headset.
+ * Bluetooth Headset and Handsfree (v1.5) profiles.
  *
- * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
+ * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
  * Service via IPC.
  *
- * Creating a BluetoothHeadset object will create a binding with the
- * BluetoothHeadset service. Users of this object should call close() when they
- * are finished with the BluetoothHeadset, so that this proxy object can unbind
- * from the service.
+ * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHeadset proxy object. Use
+ * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
  *
- * This BluetoothHeadset object is not immediately bound to the
- * BluetoothHeadset service. Use the ServiceListener interface to obtain a
- * notification when it is bound, this is especially important if you wish to
- * immediately call methods on BluetoothHeadset after construction.
- *
- * Android only supports one connected Bluetooth Headset at a time.
- *
- * @hide
+ * <p> Android only supports one connected Bluetooth Headset at a time.
+ * Each method is protected with its appropriate permission.
  */
-public final class BluetoothHeadset {
-
+public final class BluetoothHeadset implements BluetoothProfile {
     private static final String TAG = "BluetoothHeadset";
     private static final boolean DBG = false;
 
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_STATE_CHANGED =
-            "android.bluetooth.headset.action.STATE_CHANGED";
     /**
-     * TODO(API release): Consider incorporating as new state in
-     * HEADSET_STATE_CHANGED
+     * Intent used to broadcast the change in connection state of the Headset
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * {@link #EXTRA_STATE} - The current state of the profile.
+     * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
+     * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+     *
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the change in the Audio Connection state of the
+     * A2DP profile.
+     *
+     * <p>This intent will have 3 extras:
+     * {@link #EXTRA_STATE} - The current state of the profile.
+     * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
+     * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+     *
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_AUDIO_STATE_CHANGED =
-            "android.bluetooth.headset.action.AUDIO_STATE_CHANGED";
-    public static final String EXTRA_STATE =
-            "android.bluetooth.headset.extra.STATE";
-    public static final String EXTRA_PREVIOUS_STATE =
-            "android.bluetooth.headset.extra.PREVIOUS_STATE";
-    public static final String EXTRA_AUDIO_STATE =
-            "android.bluetooth.headset.extra.AUDIO_STATE";
+        "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
 
-    /** Extra to be used with the Headset State change intent.
-     * This will be used only when Headset state changes to
-     * {@link #STATE_DISCONNECTED} from any previous state.
-     * This extra field is optional and will be used when
-     * we have deterministic information regarding whether
-     * the disconnect was initiated by the remote device or
-     * by the local adapter.
-     */
-    public static final String EXTRA_DISCONNECT_INITIATOR =
-            "android.bluetooth.headset.extra.DISCONNECT_INITIATOR";
 
     /**
      * Broadcast Action: Indicates a headset has posted a vendor-specific event.
@@ -90,6 +95,7 @@
      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD}, and
      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS}.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
@@ -98,6 +104,7 @@
     /**
      * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
      * intents that contains the name of the vendor-specific command.
+     * @hide
      */
     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
@@ -108,6 +115,7 @@
      * command.
      * @see <a href="https://www.bluetooth.org/Technical/AssignedNumbers/identifiers.htm">
      * Bluetooth SIG Assigned Numbers - Company Identifiers</a>
+     * @hide
      */
     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID =
             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID";
@@ -116,104 +124,52 @@
      * A Parcelable String array extra field in
      * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
      * the arguments to the vendor-specific command.
+     * @hide
      */
     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
 
+    /*
+     * Headset state when SCO audio is connected
+     * This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
+     */
+    public static final int STATE_AUDIO_CONNECTED = 10;
 
     /**
-     * TODO(API release): Consider incorporating as new state in
-     * HEADSET_STATE_CHANGED
+     * Headset state when SCO audio is NOT connected
+     * This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
      */
+    public static final int STATE_AUDIO_DISCONNECTED = 11;
+
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
     private IBluetoothHeadset mService;
-    private final Context mContext;
-    private final ServiceListener mServiceListener;
-
-    /** There was an error trying to obtain the state */
-    public static final int STATE_ERROR        = -1;
-    /** No headset currently connected */
-    public static final int STATE_DISCONNECTED = 0;
-    /** Connection attempt in progress */
-    public static final int STATE_CONNECTING   = 1;
-    /** A headset is currently connected */
-    public static final int STATE_CONNECTED    = 2;
-
-    /** A SCO audio channel is not established */
-    public static final int AUDIO_STATE_DISCONNECTED = 0;
-    /** A SCO audio channel is established */
-    public static final int AUDIO_STATE_CONNECTED = 1;
-
-    public static final int RESULT_FAILURE = 0;
-    public static final int RESULT_SUCCESS = 1;
-    /** Connection canceled before completion. */
-    public static final int RESULT_CANCELED = 2;
-
-    /** Values for {@link #EXTRA_DISCONNECT_INITIATOR} */
-    public static final int REMOTE_DISCONNECT = 0;
-    public static final int LOCAL_DISCONNECT = 1;
-
-
-    /** Default priority for headsets for which we will accept
-     * incoming connections and auto-connect. */
-    public static final int PRIORITY_AUTO_CONNECT = 1000;
-    /** Default priority for headsets for which we will accept
-     * incoming connections but not auto-connect. */
-    public static final int PRIORITY_ON = 100;
-    /** Default priority for headsets that should not be auto-connected
-     * and not allow incoming connections. */
-    public static final int PRIORITY_OFF = 0;
-    /** Default priority when not set or when the device is unpaired */
-    public static final int PRIORITY_UNDEFINED = -1;
-
-    /**
-     * An interface for notifying BluetoothHeadset IPC clients when they have
-     * been connected to the BluetoothHeadset service.
-     */
-    public interface ServiceListener {
-        /**
-         * Called to notify the client when this proxy object has been
-         * connected to the BluetoothHeadset service. Clients must wait for
-         * this callback before making IPC calls on the BluetoothHeadset
-         * service.
-         */
-        public void onServiceConnected();
-
-        /**
-         * Called to notify the client that this proxy object has been
-         * disconnected from the BluetoothHeadset service. Clients must not
-         * make IPC calls on the BluetoothHeadset service after this callback.
-         * This callback will currently only occur if the application hosting
-         * the BluetoothHeadset service, but may be called more often in future.
-         */
-        public void onServiceDisconnected();
-    }
+    BluetoothAdapter mAdapter;
 
     /**
      * Create a BluetoothHeadset proxy object.
      */
-    public BluetoothHeadset(Context context, ServiceListener l) {
+    /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
         mContext = context;
         mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
         if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
             Log.e(TAG, "Could not bind to Bluetooth Headset Service");
         }
     }
 
-    protected void finalize() throws Throwable {
-        try {
-            close();
-        } finally {
-            super.finalize();
-        }
-    }
-
     /**
      * Close the connection to the backing service.
      * Other public functions of BluetoothHeadset will return default error
      * results once close() has been called. Multiple invocations of close()
      * are ok.
      */
-    public synchronized void close() {
+    /*package*/ synchronized void close() {
         if (DBG) log("close()");
         if (mConnection != null) {
             mContext.unbindService(mConnection);
@@ -222,190 +178,212 @@
     }
 
     /**
-     * Get the current state of the Bluetooth Headset service.
-     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
-     *         object is currently not connected to the Headset service.
+     * {@inheritDoc}
+     * @hide
      */
-    public int getState(BluetoothDevice device) {
-        if (DBG) log("getState()");
-        if (mService != null) {
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
             try {
-                return mService.getState(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+                return mService.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
         }
-        return BluetoothHeadset.STATE_ERROR;
-    }
-
-    /**
-     * Get the BluetoothDevice for the current headset.
-     * @return current headset, or null if not in connected or connecting
-     *         state, or if this proxy object is not connected to the Headset
-     *         service.
-     */
-    public BluetoothDevice getCurrentHeadset() {
-        if (DBG) log("getCurrentHeadset()");
-        if (mService != null) {
-            try {
-                return mService.getCurrentHeadset();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return null;
-    }
-
-    /**
-     * Request to initiate a connection to a headset.
-     * This call does not block. Fails if a headset is already connecting
-     * or connected.
-     * Initiates auto-connection if device is null. Tries to connect to all
-     * devices with priority greater than PRIORITY_AUTO in descending order.
-     * @param device device to connect to, or null to auto-connect last connected
-     *               headset
-     * @return       false if there was a problem initiating the connection
-     *               procedure, and no further HEADSET_STATE_CHANGED intents
-     *               will be expected.
-     */
-    public boolean connectHeadset(BluetoothDevice device) {
-        if (DBG) log("connectHeadset(" + device + ")");
-        if (mService != null) {
-            try {
-                if (mService.connectHeadset(device)) {
-                    return true;
-                }
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
     /**
-     * Returns true if the specified headset is connected (does not include
-     * connecting). Returns false if not connected, or if this proxy object
-     * if not currently connected to the headset service.
+     * {@inheritDoc}
+     * @hide
      */
-    public boolean isConnected(BluetoothDevice device) {
-        if (DBG) log("isConnected(" + device + ")");
-        if (mService != null) {
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
             try {
-                return mService.isConnected(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+                return mService.disconnect(device);
+            } catch (RemoteException e) {
+              Log.e(TAG, Log.getStackTraceString(new Throwable()));
+              return false;
+            }
         }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
     /**
-     * Disconnects the current headset. Currently this call blocks, it may soon
-     * be made asynchronous. Returns false if this proxy object is
-     * not currently connected to the Headset service.
+     * {@inheritDoc}
      */
-    public boolean disconnectHeadset(BluetoothDevice device) {
-        if (DBG) log("disconnectHeadset()");
-        if (mService != null) {
+    public Set<BluetoothDevice> getConnectedDevices() {
+        if (DBG) log("getConnectedDevices()");
+        if (mService != null && isEnabled()) {
             try {
-                mService.disconnectHeadset(device);
-                return true;
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+                return toDeviceSet(mService.getConnectedDevices());
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return toDeviceSet(new BluetoothDevice[0]);
+            }
         }
-        return false;
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return toDeviceSet(new BluetoothDevice[0]);
     }
 
     /**
-     * Start BT Voice Recognition mode, and set up Bluetooth audio path.
-     * Returns false if there is no headset connected, or if the
-     * connected headset does not support voice recognition, or on
-     * error.
+     * {@inheritDoc}
      */
-    public boolean startVoiceRecognition() {
-        if (DBG) log("startVoiceRecognition()");
-        if (mService != null) {
+    public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) log("getDevicesMatchingStates()");
+        if (mService != null && isEnabled()) {
             try {
-                return mService.startVoiceRecognition();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+                return toDeviceSet(mService.getDevicesMatchingConnectionStates(states));
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return toDeviceSet(new BluetoothDevice[0]);
+            }
         }
-        return false;
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return toDeviceSet(new BluetoothDevice[0]);
     }
 
     /**
-     * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
-     * Returns false if there is no headset connected, or the connected
-     * headset is not in voice recognition mode, or on error.
+     * {@inheritDoc}
      */
-    public boolean stopVoiceRecognition() {
-        if (DBG) log("stopVoiceRecognition()");
-        if (mService != null) {
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) log("getConnectionState(" + device + ")");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
             try {
-                return mService.stopVoiceRecognition();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+                return mService.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
         }
-        return false;
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
     }
 
     /**
-     * Set priority of headset.
-     * Priority is a non-negative integer. By default paired headsets will have
-     * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
-     * Headsets with priority greater than zero will be auto-connected, and
-     * incoming connections will be accepted (if no other headset is
-     * connected).
-     * Auto-connection occurs at the following events: boot, incoming phone
-     * call, outgoing phone call.
-     * Headsets with priority equal to zero, or that are unpaired, are not
-     * auto-connected.
-     * Incoming connections are ignored regardless of priority if there is
-     * already a headset connected.
-     * @param device paired headset
-     * @param priority Integer priority, for example PRIORITY_AUTO or
-     *                 PRIORITY_NONE
-     * @return true if successful, false if there was some error
+     * {@inheritDoc}
+     * @hide
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        if (mService != null) {
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF &&
+                priority != BluetoothProfile.PRIORITY_ON) {
+              return false;
+            }
             try {
                 return mService.setPriority(device, priority);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
         }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
     /**
-     * Get priority of headset.
-     * @param device headset
-     * @return non-negative priority, or negative error code on error
+     * {@inheritDoc}
+     * @hide
      */
     public int getPriority(BluetoothDevice device) {
         if (DBG) log("getPriority(" + device + ")");
-        if (mService != null) {
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
             try {
                 return mService.getPriority(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return PRIORITY_OFF;
+            }
         }
-        return -1;
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return PRIORITY_OFF;
+    }
+
+    /**
+     * Start Bluetooth voice recognition. This methods sends the voice
+     * recognition AT command to the headset and establishes the
+     * audio connection.
+     *
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * {@link #EXTRA_STATE} will be set to {@link #STATE_AUDIO_CONNECTED}
+     * when the audio connection is established.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param device Bluetooth headset
+     * @return false if there is no headset connected of if the
+     *               connected headset doesn't support voice recognition
+     *               or on error, true otherwise
+     */
+    public boolean startVoiceRecognition(BluetoothDevice device) {
+        if (DBG) log("startVoiceRecognition()");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
+            try {
+                return mService.startVoiceRecognition(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Stop Bluetooth Voice Recognition mode, and shut down the
+     * Bluetooth audio path.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param device Bluetooth headset
+     * @return false if there is no headset connected
+     *               or on error, true otherwise
+     */
+    public boolean stopVoiceRecognition(BluetoothDevice device) {
+        if (DBG) log("stopVoiceRecognition()");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
+            try {
+                return mService.stopVoiceRecognition(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Check if Bluetooth SCO audio is connected.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param device Bluetooth headset
+     * @return true if SCO is connected,
+     *         false otherwise or on error
+     */
+    public boolean isAudioConnected(BluetoothDevice device) {
+        if (DBG) log("isAudioConnected()");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
+            try {
+              return mService.isAudioConnected(device);
+            } catch (RemoteException e) {
+              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
     }
 
     /**
@@ -416,24 +394,29 @@
      * boot. This is a good indicator for spammy headset/handsfree units that
      * can keep the device awake by polling for cellular status updates. As a
      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
+     *
+     * @param device the bluetooth headset.
      * @return monotonically increasing battery usage hint, or a negative error
      *         code on error
      * @hide
      */
-    public int getBatteryUsageHint() {
+    public int getBatteryUsageHint(BluetoothDevice device) {
         if (DBG) log("getBatteryUsageHint()");
-        if (mService != null) {
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
             try {
-                return mService.getBatteryUsageHint();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+                return mService.getBatteryUsageHint(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
         }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
         return -1;
     }
+
     /**
      * Indicates if current platform supports voice dialing over bluetooth SCO.
+     *
      * @return true if voice dialing over bluetooth is supported, false otherwise.
      * @hide
      */
@@ -444,11 +427,13 @@
 
     /**
      * Cancel the outgoing connection.
+     * Note: This is an internal function and shouldn't be exposed
+     *
      * @hide
      */
     public boolean cancelConnectThread() {
         if (DBG) log("cancelConnectThread");
-        if (mService != null) {
+        if (mService != null && isEnabled()) {
             try {
                 return mService.cancelConnectThread();
             } catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -461,11 +446,13 @@
 
     /**
      * Accept the incoming connection.
+     * Note: This is an internal function and shouldn't be exposed
+     *
      * @hide
      */
     public boolean acceptIncomingConnect(BluetoothDevice device) {
         if (DBG) log("acceptIncomingConnect");
-        if (mService != null) {
+        if (mService != null && isEnabled()) {
             try {
                 return mService.acceptIncomingConnect(device);
             } catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -477,12 +464,14 @@
     }
 
     /**
-     * Create the connect thread the incoming connection.
+     * Create the connect thread for the incoming connection.
+     * Note: This is an internal function and shouldn't be exposed
+     *
      * @hide
      */
     public boolean createIncomingConnect(BluetoothDevice device) {
         if (DBG) log("createIncomingConnect");
-        if (mService != null) {
+        if (mService != null && isEnabled()) {
             try {
                 return mService.createIncomingConnect(device);
             } catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -496,11 +485,12 @@
     /**
      * Connect to a Bluetooth Headset.
      * Note: This is an internal function and shouldn't be exposed
+     *
      * @hide
      */
     public boolean connectHeadsetInternal(BluetoothDevice device) {
         if (DBG) log("connectHeadsetInternal");
-        if (mService != null) {
+        if (mService != null && isEnabled()) {
             try {
                 return mService.connectHeadsetInternal(device);
             } catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -514,11 +504,12 @@
     /**
      * Disconnect a Bluetooth Headset.
      * Note: This is an internal function and shouldn't be exposed
+     *
      * @hide
      */
     public boolean disconnectHeadsetInternal(BluetoothDevice device) {
         if (DBG) log("disconnectHeadsetInternal");
-        if (mService != null) {
+        if (mService != null && isEnabled()) {
             try {
                  return mService.disconnectHeadsetInternal(device);
             } catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -528,23 +519,61 @@
         }
         return false;
     }
+
+    /**
+     * Set the audio state of the Headset.
+     * Note: This is an internal function and shouldn't be exposed
+     *
+     * @hide
+     */
+    public boolean setAudioState(BluetoothDevice device, int state) {
+        if (DBG) log("setAudioState");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.setAudioState(device, state);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
     private ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
             mService = IBluetoothHeadset.Stub.asInterface(service);
+
             if (mServiceListener != null) {
-                mServiceListener.onServiceConnected();
+                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
             }
         }
         public void onServiceDisconnected(ComponentName className) {
             if (DBG) Log.d(TAG, "Proxy object disconnected");
             mService = null;
             if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected();
+                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
             }
         }
     };
 
+    private boolean isEnabled() {
+       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+       return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+       if (device == null) return false;
+
+       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+       return false;
+    }
+
+    private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) {
+       return Collections.unmodifiableSet(
+          new HashSet<BluetoothDevice>(Arrays.asList(devices)));
+    }
+
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
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/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
new file mode 100644
index 0000000..9d0b3f2
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothPan.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.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public final class BluetoothPan {
+    private static final String TAG = "BluetoothPan";
+    private static final boolean DBG = false;
+
+    /** int extra for ACTION_PAN_STATE_CHANGED */
+    public static final String EXTRA_PAN_STATE =
+        "android.bluetooth.pan.extra.STATE";
+    /** int extra for ACTION_PAN_STATE_CHANGED */
+    public static final String EXTRA_PREVIOUS_PAN_STATE =
+        "android.bluetooth.pan.extra.PREVIOUS_STATE";
+
+    /** Indicates the state of an PAN device has changed.
+     * This intent will always contain EXTRA_DEVICE_STATE,
+     * EXTRA_PREVIOUS_DEVICE_STATE and BluetoothDevice.EXTRA_DEVICE
+     * extras.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PAN_STATE_CHANGED =
+        "android.bluetooth.pan.action.STATE_CHANGED";
+
+    public static final String NAP_ROLE = "nap";
+    public static final String NAP_BRIDGE = "pan1";
+
+    public static final int MAX_CONNECTIONS = 7;
+
+    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;
+
+    private final IBluetooth mService;
+    private final Context mContext;
+
+    /**
+     * Create a BluetoothPan proxy object for interacting with the local
+     * Bluetooth Pan service.
+     * @param c Context
+     */
+    public BluetoothPan(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 PAN connection.
+     *
+     * This function returns false on error and true if the connection
+     * attempt is being made.
+     *
+     * Listen for {@link #ACTION_PAN_STATE_CHANGED} to find out when the
+     * connection is completed.
+     *
+     * @param device Remote BT device.
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        try {
+            return mService.connectPanDevice(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
+     * Initiate disconnect from PAN.
+     *
+     * This function return false on error and true if the disconnection
+     * attempt is being made.
+     *
+     * Listen for {@link #ACTION_PAN_STATE_CHANGED} to find out when
+     * disconnect is completed.
+     *
+     * @param device Remote BT device.
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        try {
+            return mService.disconnectPanDevice(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /** Get the state of a PAN Device.
+    *
+    * This function returns an int representing the state of the PAN connection
+    *
+    *  @param device Remote BT device.
+    *  @return The current state of the PAN Device
+    *  @hide
+    */
+   public int getPanDeviceState(BluetoothDevice device) {
+       if (DBG) log("getPanDeviceState(" + device + ")");
+       try {
+           return mService.getPanDeviceState(device);
+       } catch (RemoteException e) {
+           Log.e(TAG, "", e);
+           return STATE_DISCONNECTED;
+       }
+   }
+
+   /** Returns a set of all the connected PAN Devices
+   *
+   * Does not include devices that are currently connecting or disconnecting
+   *
+   * @return a unmodifiable set of connected PAN Devices, or null on error.
+   * @hide
+   */
+   public Set<BluetoothDevice> getConnectedDevices() {
+      if (DBG) log("getConnectedDevices");
+      try {
+          return Collections.unmodifiableSet(
+                  new HashSet<BluetoothDevice>(
+                      Arrays.asList(mService.getConnectedPanDevices())));
+      } catch (RemoteException e) {
+          Log.e(TAG, "", e);
+          return null;
+      }
+   }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    public void setBluetoothTethering(boolean value) {
+        try {
+            mService.setBluetoothTethering(value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    public boolean isTetheringOn() {
+        try {
+            return mService.isTetheringOn();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
new file mode 100644
index 0000000..3b4c84c
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 java.util.Set;
+
+/**
+ * Public APIs for the Bluetooth Profiles.
+ *
+ * <p> Clients should call {@link BluetoothAdapter#getProfileProxy},
+ * to get the Profile Proxy. Each public profile implements this
+ * interface.
+ */
+public interface BluetoothProfile {
+
+    /**
+     * Extra for the connection state intents of the individual profiles.
+     *
+     * This extra represents the current connection state of the profile of the
+     * Bluetooth device.
+     */
+    public static final String EXTRA_STATE = "android.bluetooth.profile.extra.STATE";
+
+    /**
+     * Extra for the connection state intents of the individual profiles.
+     *
+     * This extra represents the previous connection state of the profile of the
+     * Bluetooth device.
+     */
+    public static final String EXTRA_PREVIOUS_STATE =
+        "android.bluetooth.profile.extra.PREVIOUS_STATE";
+
+    /** The profile is in disconnected state */
+    public static final int STATE_DISCONNECTED  = 0;
+    /** The profile is in connecting state */
+    public static final int STATE_CONNECTING    = 1;
+    /** The profile is in connected state */
+    public static final int STATE_CONNECTED     = 2;
+    /** The profile is in disconnecting state */
+    public static final int STATE_DISCONNECTING = 3;
+
+    /**
+     * Headset and Handsfree profile
+     */
+    public static final int HEADSET = 1;
+    /**
+     * A2DP profile.
+     */
+    public static final int A2DP = 2;
+
+    /**
+     * Default priority for devices that we try to auto-connect to and
+     * and allow incoming connections for the profile
+     * @hide
+     **/
+    public static final int PRIORITY_AUTO_CONNECT = 1000;
+
+    /**
+     *  Default priority for devices that allow incoming
+     * and outgoing connections for the profile
+     * @hide
+     **/
+    public static final int PRIORITY_ON = 100;
+
+    /**
+     * Default priority for devices that does not allow incoming
+     * connections and outgoing connections for the profile.
+     * @hide
+     **/
+    public static final int PRIORITY_OFF = 0;
+
+    /**
+     * Default priority when not set or when the device is unpaired
+     * @hide
+     * */
+    public static final int PRIORITY_UNDEFINED = -1;
+
+    /**
+     * Initiate connection to a profile of the remote bluetooth device.
+     *
+     * <p> Currently, the system supports only 1 connection to the
+     * A2DP and Headset/Handsfree profile. The API will automatically
+     * disconnect connected devices before connecting.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is already connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that
+     * connection state intent for the profile will be broadcasted with
+     * the state. Users can get the connection state of the profile
+     * from this intent.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error,
+     *               true otherwise
+     * @hide
+     */
+    public boolean connect(BluetoothDevice device);
+
+    /**
+     * Initiate disconnection from a profile
+     *
+     * <p> This API will return false in scenarios like the profile on the
+     * Bluetooth device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that the connection state change
+     * intent will be broadcasted with the state. Users can get the
+     * disconnection state of the profile from this intent.
+     *
+     * <p> If the disconnection is initiated by a remote device, the state
+     * will transition from {@link #STATE_CONNECTED} to
+     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+     * host (local) device the state will transition from
+     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+     * state {@link #STATE_DISCONNECTED}. The transition to
+     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+     * two scenarios.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error,
+     *               true otherwise
+     * @hide
+     */
+    public boolean disconnect(BluetoothDevice device);
+
+    /**
+     * Get connected devices for this specific profile.
+     *
+     * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @return An unmodifiable set of devices. The set will be empty on error.
+     */
+    public Set<BluetoothDevice> getConnectedDevices();
+
+    /**
+     * Get a set of devices that match any of the given connection
+     * states.
+     *
+     * <p> If none of devices match any of the given states,
+     * an empty set will be returned.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param states Array of states. States can be one of
+     *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
+     * @return An unmodifiable set of devices. The set will be empty on error.
+     */
+    public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states);
+
+    /**
+     * Get the current connection state of the profile
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param device Remote bluetooth device.
+     * @return State of the profile connection. One of
+     *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
+     */
+    public int getConnectionState(BluetoothDevice device);
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     *  Priority can be one of {@link #PRIORITY_ON} or
+     * {@link #PRIORITY_OFF},
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    public boolean setPriority(BluetoothDevice device, int priority);
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
+     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    public int getPriority(BluetoothDevice device);
+
+    /**
+     * An interface for notifying BluetoothProfile IPC clients when they have
+     * been connected or disconnected to the service.
+     */
+    public interface ServiceListener {
+        /**
+         * Called to notify the client when the proxy object has been
+         * connected to the service.
+         * @param profile - One of {@link #HEADSET} or
+         *                  {@link #A2DP}
+         * @param proxy - One of {@link BluetoothHeadset} or
+         *                {@link BluetoothA2dp}
+         */
+        public void onServiceConnected(int profile, BluetoothProfile proxy);
+
+        /**
+         * Called to notify the client that this proxy object has been
+         * disconnected from the service.
+         * @param profile - One of {@link #HEADSET} or
+         *                  {@link #A2DP}
+         */
+        public void onServiceDisconnected(int profile);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java
index 946dcaa..7f42baf 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;
 
@@ -58,16 +59,22 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
 
-            if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
-                int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
-                if (mProfile == HFP && (newState == BluetoothHeadset.STATE_CONNECTED ||
-                    newState == BluetoothHeadset.STATE_DISCONNECTED)) {
+            if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
+                if (mProfile == HFP && (newState == BluetoothProfile.STATE_CONNECTED ||
+                    newState == BluetoothProfile.STATE_DISCONNECTED)) {
                     sendMessage(TRANSITION_TO_STABLE);
                 }
-            } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
-                int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
-                if (mProfile == A2DP && (newState == BluetoothA2dp.STATE_CONNECTED ||
-                    newState == BluetoothA2dp.STATE_DISCONNECTED)) {
+            } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
+                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
+                if (mProfile == A2DP && (newState == BluetoothProfile.STATE_CONNECTED ||
+                    newState == BluetoothProfile.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);
                 }
             }
@@ -82,8 +89,9 @@
         setInitialState(mStableState);
 
         IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
-        filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_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..3fbfc70 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -49,10 +49,18 @@
             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 PANU =
+            ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB");
+    public static final ParcelUuid NAP =
+            ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB");
+    public static final ParcelUuid BNEP =
+            ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB");
 
     public static final ParcelUuid[] RESERVED_UUIDS = {
         AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
-        ObexObjectPush};
+        ObexObjectPush, PANU, NAP};
 
     public static boolean isAudioSource(ParcelUuid uuid) {
         return uuid.equals(AudioSource);
@@ -82,6 +90,21 @@
         return uuid.equals(AvrcpTarget);
     }
 
+    public static boolean isInputDevice(ParcelUuid uuid) {
+        return uuid.equals(Hid);
+    }
+
+    public static boolean isPanu(ParcelUuid uuid) {
+        return uuid.equals(PANU);
+    }
+
+    public static boolean isNap(ParcelUuid uuid) {
+        return uuid.equals(NAP);
+    }
+
+    public static boolean isBnep(ParcelUuid uuid) {
+        return uuid.equals(BNEP);
+    }
     /**
      * 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 6dd8dd6..cc23146 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;
 
 /**
@@ -76,4 +77,19 @@
     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);
+
+    boolean isTetheringOn();
+    void setBluetoothTethering(boolean value);
+    int getPanDeviceState(in BluetoothDevice device);
+    BluetoothDevice[] getConnectedPanDevices();
+    boolean connectPanDevice(in BluetoothDevice device);
+    boolean disconnectPanDevice(in BluetoothDevice device);
 }
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index 40f1058..c5044c2 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -19,21 +19,25 @@
 import android.bluetooth.BluetoothDevice;
 
 /**
- * System private API for Bluetooth A2DP service
+ * APIs for Bluetooth A2DP service
  *
- * {@hide}
+ * @hide
  */
 interface IBluetoothA2dp {
-    boolean connectSink(in BluetoothDevice device);
-    boolean disconnectSink(in BluetoothDevice device);
+    // Public API
+    boolean connect(in BluetoothDevice device);
+    boolean disconnect(in BluetoothDevice device);
+    // change to Set<> once AIDL supports
+    BluetoothDevice[] getConnectedDevices();
+    BluetoothDevice[] getDevicesMatchingConnectionStates(in int[] states);
+    int getConnectionState(in BluetoothDevice device);
+    boolean setPriority(in BluetoothDevice device, int priority);
+    int getPriority(in BluetoothDevice device);
+    boolean isA2dpPlaying(in BluetoothDevice device);
+
+    // Internal APIs
     boolean suspendSink(in BluetoothDevice device);
     boolean resumeSink(in BluetoothDevice device);
-    BluetoothDevice[] getConnectedSinks();  // change to Set<> once AIDL supports
-    BluetoothDevice[] getNonDisconnectedSinks();  // change to Set<> once AIDL supports
-    int getSinkState(in BluetoothDevice device);
-    boolean setSinkPriority(in BluetoothDevice device, int priority);
-    int getSinkPriority(in BluetoothDevice device);
-
     boolean connectSinkInternal(in BluetoothDevice device);
     boolean disconnectSinkInternal(in BluetoothDevice device);
 }
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index d96f0ca..8bcf103 100644
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -19,25 +19,32 @@
 import android.bluetooth.BluetoothDevice;
 
 /**
- * System private API for Bluetooth Headset service
+ * API for Bluetooth Headset service
  *
  * {@hide}
  */
 interface IBluetoothHeadset {
-    int getState(in BluetoothDevice device);
-    BluetoothDevice getCurrentHeadset();
-    boolean connectHeadset(in BluetoothDevice device);
-    void disconnectHeadset(in BluetoothDevice device);
-    boolean isConnected(in BluetoothDevice device);
-    boolean startVoiceRecognition();
-    boolean stopVoiceRecognition();
+    // Public API
+    boolean connect(in BluetoothDevice device);
+    boolean disconnect(in BluetoothDevice device);
+    // Change to Set<> when AIDL supports
+    BluetoothDevice[] getConnectedDevices();
+    BluetoothDevice[] getDevicesMatchingConnectionStates(in int[] states);
+    int getConnectionState(in BluetoothDevice device);
     boolean setPriority(in BluetoothDevice device, int priority);
     int getPriority(in BluetoothDevice device);
-    int getBatteryUsageHint();
+    boolean startVoiceRecognition(in BluetoothDevice device);
+    boolean stopVoiceRecognition(in BluetoothDevice device);
+    boolean isAudioConnected(in BluetoothDevice device);
 
+    // APIs that can be made public in future
+    int getBatteryUsageHint(in BluetoothDevice device);
+
+    // Internal functions, not be made public
     boolean createIncomingConnect(in BluetoothDevice device);
     boolean acceptIncomingConnect(in BluetoothDevice device);
     boolean cancelConnectThread();
     boolean connectHeadsetInternal(in BluetoothDevice device);
     boolean disconnectHeadsetInternal(in BluetoothDevice device);
+    boolean setAudioState(in BluetoothDevice device, int state);
 }
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/ClipData.aidl b/core/java/android/content/ClipData.aidl
new file mode 100644
index 0000000..5fc12ce
--- /dev/null
+++ b/core/java/android/content/ClipData.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 ClipData;
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
new file mode 100644
index 0000000..a19b132
--- /dev/null
+++ b/core/java/android/content/ClipData.java
@@ -0,0 +1,494 @@
+/**
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+
+/**
+ * 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>A ClipData is a sub-class of {@link ClipDescription}, which describes
+ * important meta-data about the clip.  In particular, its {@link #getMimeType(int)}
+ * must return correct MIME type(s) describing the data in the clip.  For help
+ * in correctly constructing a clip with the correct MIME type, use
+ * {@link #newPlainText(CharSequence, Bitmap, CharSequence)},
+ * {@link #newUri(ContentResolver, CharSequence, Bitmap, Uri)}, and
+ * {@link #newIntent(CharSequence, Bitmap, Intent)}.
+ *
+ * <p>Each Item instance can be one of three main classes of data: a simple
+ * CharSequence of text, a single Intent object, or a Uri.  See {@link Item}
+ * for more details.
+ *
+ * <a name="ImplementingPaste"></a>
+ * <h3>Implementing Paste or Drop</h3>
+ *
+ * <p>To implement a paste or drop of a ClippedData object into an application,
+ * the application must correctly interpret the data for its use.  If the {@link Item}
+ * it contains is simple text or an Intent, there is little to be done: text
+ * can only be interpreted as text, and an Intent will typically be used for
+ * creating shortcuts (such as placing icons on the home screen) or other
+ * actions.
+ *
+ * <p>If all you want is the textual representation of the clipped data, you
+ * can use the convenience method {@link Item#coerceToText Item.coerceToText}.
+ * In this case there is generally no need to worry about the MIME types
+ * reported by {@link #getMimeType(int)}, since any clip item an always be
+ * converted to a string.
+ *
+ * <p>More complicated exchanges will be done through URIs, in particular
+ * "content:" URIs.  A content URI allows the recipient of a ClippedData item
+ * to interact closely with the ContentProvider holding the data in order to
+ * negotiate the transfer of that data.  The clip must also be filled in with
+ * the available MIME types; {@link #newUri(ContentResolver, CharSequence, Bitmap, Uri)}
+ * will take care of correctly doing this.
+ *
+ * <p>For example, here is the paste function of a simple NotePad application.
+ * When retrieving the data from the clipboard, it can do either two things:
+ * if the clipboard contains a URI reference to an existing note, it copies
+ * the entire structure of the note into a new note; otherwise, it simply
+ * coerces the clip into text and uses that as the new note's contents.
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
+ *      paste}
+ *
+ * <p>In many cases an application can paste various types of streams of data.  For
+ * example, an e-mail application may want to allow the user to paste an image
+ * or other binary data as an attachment.  This is accomplished through the
+ * ContentResolver {@link ContentResolver#getStreamTypes(Uri, String)} and
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, android.os.Bundle)}
+ * methods.  These allow a client to discover the type(s) of data that a particular
+ * content URI can make available as a stream and retrieve the stream of data.
+ *
+ * <p>For example, the implementation of {@link Item#coerceToText Item.coerceToText}
+ * itself uses this to try to retrieve a URI clip as a stream of text:
+ *
+ * {@sample frameworks/base/core/java/android/content/ClipData.java coerceToText}
+ *
+ * <a name="ImplementingCopy"></a>
+ * <h3>Implementing Copy or Drag</h3>
+ *
+ * <p>To be the source of a clip, the application must construct a ClippedData
+ * object that any recipient can interpret best for their context.  If the clip
+ * is to contain a simple text, Intent, or URI, this is easy: an {@link Item}
+ * containing the appropriate data type can be constructed and used.
+ *
+ * <p>More complicated data types require the implementation of support in
+ * a ContentProvider for describing and generating the data for the recipient.
+ * A common scenario is one where an application places on the clipboard the
+ * content: URI of an object that the user has copied, with the data at that
+ * URI consisting of a complicated structure that only other applications with
+ * direct knowledge of the structure can use.
+ *
+ * <p>For applications that do not have intrinsic knowledge of the data structure,
+ * the content provider holding it can make the data available as an arbitrary
+ * number of types of data streams.  This is done by implementing the
+ * ContentProvider {@link ContentProvider#getStreamTypes(Uri, String)} and
+ * {@link ContentProvider#openTypedAssetFile(Uri, String, android.os.Bundle)}
+ * methods.
+ *
+ * <p>Going back to our simple NotePad application, this is the implementation
+ * it may have to convert a single note URI (consisting of a title and the note
+ * text) into a stream of plain text data.
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
+ *      stream}
+ *
+ * <p>The copy operation in our NotePad application is now just a simple matter
+ * of making a clip containing the URI of the note being copied:
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NotesList.java
+ *      copy}
+ *
+ * <p>Note if a paste operation needs this clip as text (for example to paste
+ * into an editor), then {@link Item#coerceToText(Context)} will ask the content
+ * provider for the clip URI as text and successfully paste the entire note.
+ */
+public class ClipData extends ClipDescription {
+    static final String[] MIMETYPES_TEXT_PLAIN = new String[] { MIMETYPE_TEXT_PLAIN };
+    static final String[] MIMETYPES_TEXT_URILIST = new String[] { MIMETYPE_TEXT_URILIST };
+    static final String[] MIMETYPES_TEXT_INTENT = new String[] { MIMETYPE_TEXT_INTENT };
+
+    final Bitmap mIcon;
+
+    final ArrayList<Item> mItems = new ArrayList<Item>();
+
+    /**
+     * Description of a single item in a ClippedData.
+     *
+     * <p>The types than an individual item can currently contain are:</p>
+     *
+     * <ul>
+     * <li> Text: a basic string of text.  This is actually a CharSequence,
+     * so it can be formatted text supported by corresponding Android built-in
+     * style spans.  (Custom application spans are not supported and will be
+     * stripped when transporting through the clipboard.)
+     * <li> Intent: an arbitrary Intent object.  A typical use is the shortcut
+     * to create when pasting a clipped item on to the home screen.
+     * <li> Uri: a URI reference.  This may be any URI (such as an http: URI
+     * representing a bookmark), however it is often a content: URI.  Using
+     * content provider references as clips like this allows an application to
+     * share complex or large clips through the standard content provider
+     * facilities.
+     * </ul>
+     */
+    public static class Item {
+        final CharSequence mText;
+        final Intent mIntent;
+        final Uri mUri;
+
+        /**
+         * Create an Item consisting of a single block of (possibly styled) text.
+         */
+        public Item(CharSequence text) {
+            mText = text;
+            mIntent = null;
+            mUri = null;
+        }
+
+        /**
+         * Create an Item consisting of an arbitrary Intent.
+         */
+        public Item(Intent intent) {
+            mText = null;
+            mIntent = intent;
+            mUri = null;
+        }
+
+        /**
+         * Create an Item consisting of an arbitrary URI.
+         */
+        public Item(Uri uri) {
+            mText = null;
+            mIntent = null;
+            mUri = uri;
+        }
+
+        /**
+         * Create a complex Item, containing multiple representations of
+         * text, intent, and/or URI.
+         */
+        public Item(CharSequence text, Intent intent, Uri uri) {
+            mText = text;
+            mIntent = intent;
+            mUri = uri;
+        }
+
+        /**
+         * Retrieve the raw text contained in this Item.
+         */
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Retrieve the raw Intent contained in this Item.
+         */
+        public Intent getIntent() {
+            return mIntent;
+        }
+
+        /**
+         * Retrieve the raw URI contained in this Item.
+         */
+        public Uri getUri() {
+            return mUri;
+        }
+
+        /**
+         * Turn this item into text, regardless of the type of data it
+         * actually contains.
+         *
+         * <p>The algorithm for deciding what text to return is:
+         * <ul>
+         * <li> If {@link #getText} is non-null, return that.
+         * <li> If {@link #getUri} is non-null, try to retrieve its data
+         * as a text stream from its content provider.  If this succeeds, copy
+         * the text into a String and return it.  If it is not a content: URI or
+         * the content provider does not supply a text representation, return
+         * the raw URI as a string.
+         * <li> If {@link #getIntent} is non-null, convert that to an intent:
+         * URI and returnit.
+         * <li> Otherwise, return an empty string.
+         * </ul>
+         *
+         * @param context The caller's Context, from which its ContentResolver
+         * and other things can be retrieved.
+         * @return Returns the item's textual representation.
+         */
+//BEGIN_INCLUDE(coerceToText)
+        public CharSequence coerceToText(Context context) {
+            // If this Item has an explicit textual value, simply return that.
+            if (mText != null) {
+                return mText;
+            }
+
+            // If this Item has a URI value, try using that.
+            if (mUri != null) {
+
+                // First see if the URI can be opened as a plain text stream
+                // (of any sub-type).  If so, this is the best textual
+                // representation for it.
+                FileInputStream stream = null;
+                try {
+                    // Ask for a stream of the desired type.
+                    AssetFileDescriptor descr = context.getContentResolver()
+                            .openTypedAssetFileDescriptor(mUri, "text/*", null);
+                    stream = descr.createInputStream();
+                    InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
+
+                    // Got it...  copy the stream into a local string and return it.
+                    StringBuilder builder = new StringBuilder(128);
+                    char[] buffer = new char[8192];
+                    int len;
+                    while ((len=reader.read(buffer)) > 0) {
+                        builder.append(buffer, 0, len);
+                    }
+                    return builder.toString();
+
+                } catch (FileNotFoundException e) {
+                    // Unable to open content URI as text...  not really an
+                    // error, just something to ignore.
+
+                } catch (IOException e) {
+                    // Something bad has happened.
+                    Log.w("ClippedData", "Failure loading text", e);
+                    return e.toString();
+
+                } finally {
+                    if (stream != null) {
+                        try {
+                            stream.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+
+                // If we couldn't open the URI as a stream, then the URI itself
+                // probably serves fairly well as a textual representation.
+                return mUri.toString();
+            }
+
+            // Finally, if all we have is an Intent, then we can just turn that
+            // into text.  Not the most user-friendly thing, but it's something.
+            if (mIntent != null) {
+                return mIntent.toUri(Intent.URI_INTENT_SCHEME);
+            }
+
+            // Shouldn't get here, but just in case...
+            return "";
+        }
+//END_INCLUDE(coerceToText)
+    }
+
+    /**
+     * Create a new clip.
+     *
+     * @param label Label to show to the user describing this clip.
+     * @param mimeTypes An array of MIME types this data is available as.
+     * @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 ClipData(CharSequence label, String[] mimeTypes, Bitmap icon, Item item) {
+        super(label, mimeTypes);
+        if (item == null) {
+            throw new NullPointerException("item is null");
+        }
+        mIcon = icon;
+        mItems.add(item);
+    }
+
+    /**
+     * Create a new ClipData holding data of the type {@link #MIMETYPE_TEXT_PLAIN}.
+     *
+     * @param label User-visible label for the clip data.
+     * @param icon Iconic representation of the clip data.
+     * @param text The actual text in the clip.
+     * @return Returns a new ClipData containing the specified data.
+     */
+    static public ClipData newPlainText(CharSequence label, Bitmap icon, CharSequence text) {
+        Item item = new Item(text);
+        return new ClipData(label, MIMETYPES_TEXT_PLAIN, icon, item);
+    }
+
+    /**
+     * Create a new ClipData holding an Intent with MIME type {@link #MIMETYPE_TEXT_INTENT}.
+     *
+     * @param label User-visible label for the clip data.
+     * @param icon Iconic representation of the clip data.
+     * @param intent The actual Intent in the clip.
+     * @return Returns a new ClipData containing the specified data.
+     */
+    static public ClipData newIntent(CharSequence label, Bitmap icon, Intent intent) {
+        Item item = new Item(intent);
+        return new ClipData(label, MIMETYPES_TEXT_INTENT, icon, item);
+    }
+
+    /**
+     * Create a new ClipData holding a URI.  If the URI is a content: URI,
+     * this will query the content provider for the MIME type of its data and
+     * use that as the MIME type.  Otherwise, it will use the MIME type
+     * {@link #MIMETYPE_TEXT_URILIST}.
+     *
+     * @param resolver ContentResolver used to get information about the URI.
+     * @param label User-visible label for the clip data.
+     * @param icon Iconic representation of the clip data.
+     * @param uri The URI in the clip.
+     * @return Returns a new ClipData containing the specified data.
+     */
+    static public ClipData newUri(ContentResolver resolver, CharSequence label,
+            Bitmap icon, Uri uri) {
+        Item item = new Item(uri);
+        String[] mimeTypes = null;
+        if ("content".equals(uri.getScheme())) {
+            String realType = resolver.getType(uri);
+            mimeTypes = resolver.getStreamTypes(uri, "*/*");
+            if (mimeTypes == null) {
+                if (realType != null) {
+                    mimeTypes = new String[] { realType, MIMETYPE_TEXT_URILIST };
+                }
+            } else {
+                String[] tmp = new String[mimeTypes.length + (realType != null ? 2 : 1)];
+                int i = 0;
+                if (realType != null) {
+                    tmp[0] = realType;
+                    i++;
+                }
+                System.arraycopy(mimeTypes, 0, tmp, i, mimeTypes.length);
+                tmp[i + mimeTypes.length] = MIMETYPE_TEXT_URILIST;
+                mimeTypes = tmp;
+            }
+        }
+        if (mimeTypes == null) {
+            mimeTypes = MIMETYPES_TEXT_URILIST;
+        }
+        return new ClipData(label, mimeTypes, icon, item);
+    }
+
+    /**
+     * Create a new ClipData holding an URI with MIME type {@link #MIMETYPE_TEXT_URILIST}.
+     * Unlike {@link #newUri(ContentResolver, CharSequence, Bitmap, Uri)}, nothing
+     * is inferred about the URI -- if it is a content: URI holding a bitmap,
+     * the reported type will still be uri-list.  Use this with care!
+     *
+     * @param label User-visible label for the clip data.
+     * @param icon Iconic representation of the clip data.
+     * @param uri The URI in the clip.
+     * @return Returns a new ClipData containing the specified data.
+     */
+    static public ClipData newRawUri(CharSequence label, Bitmap icon, Uri uri) {
+        Item item = new Item(uri);
+        return new ClipData(label, MIMETYPES_TEXT_URILIST, icon, item);
+    }
+
+    public void addItem(Item item) {
+        if (item == null) {
+            throw new NullPointerException("item is null");
+        }
+        mItems.add(item);
+    }
+
+    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) {
+        super.writeToParcel(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);
+            }
+        }
+    }
+
+    ClipData(Parcel in) {
+        super(in);
+        if (in.readInt() != 0) {
+            mIcon = Bitmap.CREATOR.createFromParcel(in);
+        } else {
+            mIcon = null;
+        }
+        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<ClipData> CREATOR =
+        new Parcelable.Creator<ClipData>() {
+
+            public ClipData createFromParcel(Parcel source) {
+                return new ClipData(source);
+            }
+
+            public ClipData[] newArray(int size) {
+                return new ClipData[size];
+            }
+        };
+}
diff --git a/core/java/android/content/ClipDescription.aidl b/core/java/android/content/ClipDescription.aidl
new file mode 100644
index 0000000..391fd5a
--- /dev/null
+++ b/core/java/android/content/ClipDescription.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 ClipDescription;
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
new file mode 100644
index 0000000..0d9d807
--- /dev/null
+++ b/core/java/android/content/ClipDescription.java
@@ -0,0 +1,206 @@
+/**
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Meta-data describing the contents of a {@link ClipData}.  Provides enough
+ * information to know if you can handle the ClipData, but not the data
+ * itself.
+ */
+public class ClipDescription implements Parcelable {
+    /**
+     * The MIME type for a clip holding plain text.
+     */
+    public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
+
+    /**
+     * The MIME type for a clip holding one or more URIs.  This should be
+     * used for URIs that are meaningful to a user (such as an http: URI).
+     * It should <em>not</em> be used for a content: URI that references some
+     * other piece of data; in that case the MIME type should be the type
+     * of the referenced data.
+     */
+    public static final String MIMETYPE_TEXT_URILIST = "text/uri-list";
+
+    /**
+     * The MIME type for a clip holding an Intent.
+     */
+    public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
+
+    final CharSequence mLabel;
+    final String[] mMimeTypes;
+
+    /**
+     * Create a new clip.
+     *
+     * @param label Label to show to the user describing this clip.
+     * @param mimeTypes An array of MIME types this data is available as.
+     */
+    public ClipDescription(CharSequence label, String[] mimeTypes) {
+        if (mimeTypes == null) {
+            throw new NullPointerException("mimeTypes is null");
+        }
+        mLabel = label;
+        mMimeTypes = mimeTypes;
+    }
+
+    /**
+     * Create a copy of a ClipDescription.
+     */
+    public ClipDescription(ClipDescription o) {
+        mLabel = o.mLabel;
+        mMimeTypes = o.mMimeTypes;
+    }
+
+    /**
+     * Helper to compare two MIME types, where one may be a pattern.
+     * @param concreteType A fully-specified MIME type.
+     * @param desiredType A desired MIME type that may be a pattern such as *\/*.
+     * @return Returns true if the two MIME types match.
+     */
+    public static boolean compareMimeTypes(String concreteType, String desiredType) {
+        final int typeLength = desiredType.length();
+        if (typeLength == 3 && desiredType.equals("*/*")) {
+            return true;
+        }
+
+        final int slashpos = desiredType.indexOf('/');
+        if (slashpos > 0) {
+            if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') {
+                if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) {
+                    return true;
+                }
+            } else if (desiredType.equals(concreteType)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Return the label for this clip.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Check whether the clip description contains the given MIME type.
+     *
+     * @param mimeType The desired MIME type.  May be a pattern.
+     * @return Returns true if one of the MIME types in the clip description
+     * matches the desired MIME type, else false.
+     */
+    public boolean hasMimeType(String mimeType) {
+        for (int i=0; i<mMimeTypes.length; i++) {
+            if (compareMimeTypes(mMimeTypes[i], mimeType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Filter the clip description MIME types by the given MIME type.  Returns
+     * all MIME types in the clip that match the given MIME type.
+     *
+     * @param mimeType The desired MIME type.  May be a pattern.
+     * @return Returns an array of all matching MIME types.  If there are no
+     * matching MIME types, null is returned.
+     */
+    public String[] filterMimeTypes(String mimeType) {
+        ArrayList<String> array = null;
+        for (int i=0; i<mMimeTypes.length; i++) {
+            if (compareMimeTypes(mMimeTypes[i], mimeType)) {
+                if (array == null) {
+                    array = new ArrayList<String>();
+                }
+                array.add(mMimeTypes[i]);
+            }
+        }
+        if (array == null) {
+            return null;
+        }
+        String[] rawArray = new String[array.size()];
+        array.toArray(rawArray);
+        return rawArray;
+    }
+
+    /**
+     * Return the number of MIME types the clip is available in.
+     */
+    public int getMimeTypeCount() {
+        return mMimeTypes.length;
+    }
+
+    /**
+     * Return one of the possible clip MIME types.
+     */
+    public String getMimeType(int index) {
+        return mMimeTypes[index];
+    }
+
+    /** @hide */
+    public void validate() {
+        if (mMimeTypes == null) {
+            throw new NullPointerException("null mime types");
+        }
+        if (mMimeTypes.length <= 0) {
+            throw new IllegalArgumentException("must have at least 1 mime type");
+        }
+        for (int i=0; i<mMimeTypes.length; i++) {
+            if (mMimeTypes[i] == null) {
+                throw new NullPointerException("mime type at " + i + " is null");
+            }
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        TextUtils.writeToParcel(mLabel, dest, flags);
+        dest.writeStringArray(mMimeTypes);
+    }
+
+    ClipDescription(Parcel in) {
+        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mMimeTypes = in.createStringArray();
+    }
+
+    public static final Parcelable.Creator<ClipDescription> CREATOR =
+        new Parcelable.Creator<ClipDescription>() {
+
+            public ClipDescription createFromParcel(Parcel source) {
+                return new ClipDescription(source);
+            }
+
+            public ClipDescription[] newArray(int size) {
+                return new ClipDescription[size];
+            }
+        };
+}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
new file mode 100644
index 0000000..85a6765
--- /dev/null
+++ b/core/java/android/content/ClipboardManager.java
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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}.
+ *
+ * <p>
+ * The ClipboardManager API itself is very simple: it consists of methods
+ * to atomically get and set the current primary clipboard data.  That data
+ * is expressed as a {@link ClipData} object, which defines the protocol
+ * for data exchange between applications.
+ *
+ * @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(ClipData clip) {
+        try {
+            getService().setPrimaryClip(clip);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Returns the current primary clip on the clipboard.
+     */
+    public ClipData getPrimaryClip() {
+        try {
+            return getService().getPrimaryClip(mContext.getPackageName());
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns a description of the current primary clip on the clipboard
+     * but not a copy of its data.
+     */
+    public ClipDescription getPrimaryClipDescription() {
+        try {
+            return getService().getPrimaryClipDescription();
+        } 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() {
+        ClipData clip = getPrimaryClip();
+        if (clip != null && clip.getItemCount() > 0) {
+            return clip.getItem(0).coerceToText(mContext);
+        }
+        return null;
+    }
+
+    /**
+     * @deprecated Use {@link #setPrimaryClip(ClipData)} 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(ClipData.newPlainText(null, null, text));
+    }
+
+    /**
+     * @deprecated Use {@link #hasPrimaryClip()} instead.
+     */
+    public boolean hasText() {
+        try {
+            return getService().hasPrimaryClip();
+        } 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/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 1d6e8b8..6bb32c1 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -28,13 +28,16 @@
 import android.database.IContentObserver;
 import android.database.SQLException;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.util.Log;
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.ArrayList;
 
 /**
@@ -76,6 +79,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.
@@ -249,6 +254,18 @@
             return ContentProvider.this.call(method, request, args);
         }
 
+        @Override
+        public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+            return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter);
+        }
+
+        @Override
+        public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeType, Bundle opts)
+                throws FileNotFoundException {
+            enforceReadPermission(uri);
+            return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
+        }
+
         private void enforceReadPermission(Uri uri) {
             final int uid = Binder.getCallingUid();
             if (uid == mMyUid) {
@@ -756,6 +773,144 @@
     }
 
     /**
+     * Called by a client to determine the types of data streams that this
+     * content provider supports for the given URI.  The default implementation
+     * returns null, meaning no types.  If your content provider stores data
+     * of a particular type, return that MIME type if it matches the given
+     * mimeTypeFilter.  If it can perform type conversions, return an array
+     * of all supported MIME types that match mimeTypeFilter.
+     *
+     * @param uri The data in the content provider being queried.
+     * @param mimeTypeFilter The type of data the client desires.  May be
+     * a pattern, such as *\/* to retrieve all possible data types.
+     * @return Returns null if there are no possible data streams for the
+     * given mimeTypeFilter.  Otherwise returns an array of all available
+     * concrete MIME types.
+     *
+     * @see #getType(Uri)
+     * @see #openTypedAssetFile(Uri, String, Bundle)
+     * @see ClipDescription#compareMimeTypes(String, String)
+     */
+    public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+        return null;
+    }
+
+    /**
+     * Called by a client to open a read-only stream containing data of a
+     * particular MIME type.  This is like {@link #openAssetFile(Uri, String)},
+     * except the file can only be read-only and the content provider may
+     * perform data conversions to generate data of the desired type.
+     *
+     * <p>The default implementation compares the given mimeType against the
+     * result of {@link #getType(Uri)} and, if the match, simple calls
+     * {@link #openAssetFile(Uri, String)}.
+     *
+     * <p>See {@link ClipData} for examples of the use and implementation
+     * of this method.
+     *
+     * @param uri The data in the content provider being queried.
+     * @param mimeTypeFilter The type of data the client desires.  May be
+     * a pattern, such as *\/*, if the caller does not have specific type
+     * requirements; in this case the content provider will pick its best
+     * type matching the pattern.
+     * @param opts Additional options from the client.  The definitions of
+     * these are specific to the content provider being called.
+     *
+     * @return Returns a new AssetFileDescriptor from which the client can
+     * read data of the desired type.
+     *
+     * @throws FileNotFoundException Throws FileNotFoundException if there is
+     * no file associated with the given URI or the mode is invalid.
+     * @throws SecurityException Throws SecurityException if the caller does
+     * not have permission to access the data.
+     * @throws IllegalArgumentException Throws IllegalArgumentException if the
+     * content provider does not support the requested MIME type.
+     *
+     * @see #getStreamTypes(Uri, String)
+     * @see #openAssetFile(Uri, String)
+     * @see ClipDescription#compareMimeTypes(String, String)
+     */
+    public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
+            throws FileNotFoundException {
+        if ("*/*".equals(mimeTypeFilter)) {
+            // If they can take anything, the untyped open call is good enough.
+            return openAssetFile(uri, "r");
+        }
+        String baseType = getType(uri);
+        if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
+            // Use old untyped open call if this provider has a type for this
+            // URI and it matches the request.
+            return openAssetFile(uri, "r");
+        }
+        throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter);
+    }
+
+    /**
+     * Interface to write a stream of data to a pipe.  Use with
+     * {@link ContentProvider#openPipeHelper}.
+     */
+    public interface PipeDataWriter<T> {
+        /**
+         * Called from a background thread to stream data out to a pipe.
+         * Note that the pipe is blocking, so this thread can block on
+         * writes for an arbitrary amount of time if the client is slow
+         * at reading.
+         *
+         * @param output The pipe where data should be written.  This will be
+         * closed for you upon returning from this function.
+         * @param uri The URI whose data is to be written.
+         * @param mimeType The desired type of data to be written.
+         * @param opts Options supplied by caller.
+         * @param args Your own custom arguments.
+         */
+        public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
+                Bundle opts, T args);
+    }
+
+    /**
+     * A helper function for implementing {@link #openTypedAssetFile}, for
+     * creating a data pipe and background thread allowing you to stream
+     * generated data back to the client.  This function returns a new
+     * ParcelFileDescriptor that should be returned to the caller (the caller
+     * is responsible for closing it).
+     *
+     * @param uri The URI whose data is to be written.
+     * @param mimeType The desired type of data to be written.
+     * @param opts Options supplied by caller.
+     * @param args Your own custom arguments.
+     * @param func Interface implementing the function that will actually
+     * stream the data.
+     * @return Returns a new ParcelFileDescriptor holding the read side of
+     * the pipe.  This should be returned to the caller for reading; the caller
+     * is responsible for closing it when done.
+     */
+    public <T> ParcelFileDescriptor openPipeHelper(final Uri uri, final String mimeType,
+            final Bundle opts, final T args, final PipeDataWriter<T> func)
+            throws FileNotFoundException {
+        try {
+            final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+
+            AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() {
+                @Override
+                protected Object doInBackground(Object... params) {
+                    func.writeDataToPipe(fds[1], uri, mimeType, opts, args);
+                    try {
+                        fds[1].close();
+                    } catch (IOException e) {
+                        Log.w(TAG, "Failure closing pipe", e);
+                    }
+                    return null;
+                }
+            };
+            task.execute((Object[])null);
+
+            return fds[0];
+        } catch (IOException e) {
+            throw new FileNotFoundException("failure making pipe");
+        }
+    }
+
+    /**
      * Returns true if this instance is a temporary content provider.
      * @return true if this instance is a temporary content provider
      */
@@ -781,6 +936,11 @@
      * @param info Registered information about this content provider
      */
     public void attachInfo(Context context, ProviderInfo info) {
+        /*
+         * We may be using AsyncTask from binder threads.  Make it init here
+         * so its static handler is on the main thread.
+         */
+        AsyncTask.init();
 
         /*
          * Only allow it to be set once, so after the content service gives
@@ -829,7 +989,7 @@
     /**
      * @hide -- until interface has proven itself
      *
-     * Call an provider-defined method.  This can be used to implement
+     * Call a provider-defined method.  This can be used to implement
      * interfaces that are cheaper than using a Cursor.
      *
      * @param method Method name to call.  Opaque to framework.
@@ -839,4 +999,31 @@
     public Bundle call(String method, String request, Bundle args) {
         return null;
     }
+
+    /**
+     * Implement this to shut down the ContentProvider instance. You can then
+     * invoke this method in unit tests.
+     * 
+     * <p>
+     * Android normally handles ContentProvider startup and shutdown
+     * automatically. You do not need to start up or shut down a
+     * ContentProvider. When you invoke a test method on a ContentProvider,
+     * however, a ContentProvider instance is started and keeps running after
+     * the test finishes, even if a succeeding test instantiates another
+     * ContentProvider. A conflict develops because the two instances are
+     * usually running against the same underlying data source (for example, an
+     * sqlite database).
+     * </p>
+     * <p>
+     * Implementing shutDown() avoids this conflict by providing a way to
+     * terminate the ContentProvider. This method can also prevent memory leaks
+     * from multiple instantiations of the ContentProvider, and it can ensure
+     * unit test isolation by allowing you to completely clean up the test
+     * fixture before moving on to the next test.
+     * </p>
+     */
+    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/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 0858ea5..0540109 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -18,6 +18,7 @@
 
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ParcelFileDescriptor;
 import android.content.res.AssetFileDescriptor;
@@ -43,53 +44,77 @@
         mContentResolver = contentResolver;
     }
 
-    /** see {@link ContentProvider#query} */
+    /** See {@link ContentProvider#query ContentProvider.query} */
     public Cursor query(Uri url, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) throws RemoteException {
         return mContentProvider.query(url, projection, selection,  selectionArgs, sortOrder);
     }
 
-    /** see {@link ContentProvider#getType} */
+    /** See {@link ContentProvider#getType ContentProvider.getType} */
     public String getType(Uri url) throws RemoteException {
         return mContentProvider.getType(url);
     }
 
-    /** see {@link ContentProvider#insert} */
+    /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+        return mContentProvider.getStreamTypes(url, mimeTypeFilter);
+    }
+
+    /** See {@link ContentProvider#insert ContentProvider.insert} */
     public Uri insert(Uri url, ContentValues initialValues)
             throws RemoteException {
         return mContentProvider.insert(url, initialValues);
     }
 
-    /** see {@link ContentProvider#bulkInsert} */
+    /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
     public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
         return mContentProvider.bulkInsert(url, initialValues);
     }
 
-    /** see {@link ContentProvider#delete} */
+    /** See {@link ContentProvider#delete ContentProvider.delete} */
     public int delete(Uri url, String selection, String[] selectionArgs)
             throws RemoteException {
         return mContentProvider.delete(url, selection, selectionArgs);
     }
 
-    /** see {@link ContentProvider#update} */
+    /** See {@link ContentProvider#update ContentProvider.update} */
     public int update(Uri url, ContentValues values, String selection,
             String[] selectionArgs) throws RemoteException {
         return mContentProvider.update(url, values, selection, selectionArgs);
     }
 
-    /** see {@link ContentProvider#openFile} */
+    /**
+     * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
+     * this <em>does not</em>
+     * take care of non-content: URIs such as file:.  It is strongly recommended
+     * you use the {@link ContentResolver#openFileDescriptor
+     * ContentResolver.openFileDescriptor} API instead.
+     */
     public ParcelFileDescriptor openFile(Uri url, String mode)
             throws RemoteException, FileNotFoundException {
         return mContentProvider.openFile(url, mode);
     }
 
-    /** see {@link ContentProvider#openAssetFile} */
+    /**
+     * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
+     * Note that this <em>does not</em>
+     * take care of non-content: URIs such as file:.  It is strongly recommended
+     * you use the {@link ContentResolver#openAssetFileDescriptor
+     * ContentResolver.openAssetFileDescriptor} API instead.
+     */
     public AssetFileDescriptor openAssetFile(Uri url, String mode)
             throws RemoteException, FileNotFoundException {
         return mContentProvider.openAssetFile(url, mode);
     }
 
-     /** see {@link ContentProvider#applyBatch} */
+    /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
+    public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
+            String mimeType, Bundle opts)
+            throws RemoteException, FileNotFoundException {
+        return mContentProvider.openTypedAssetFile(uri, mimeType, opts);
+    }
+
+    /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException {
         return mContentProvider.applyBatch(operations);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index fdb3d20..d1ab8d5 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -257,6 +257,38 @@
                     reply.writeBundle(responseBundle);
                     return true;
                 }
+
+                case GET_STREAM_TYPES_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    String mimeTypeFilter = data.readString();
+                    String[] types = getStreamTypes(url, mimeTypeFilter);
+                    reply.writeNoException();
+                    reply.writeStringArray(types);
+
+                    return true;
+                }
+
+                case OPEN_TYPED_ASSET_FILE_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    String mimeType = data.readString();
+                    Bundle opts = data.readBundle();
+
+                    AssetFileDescriptor fd;
+                    fd = openTypedAssetFile(url, mimeType, opts);
+                    reply.writeNoException();
+                    if (fd != null) {
+                        reply.writeInt(1);
+                        fd.writeToParcel(reply,
+                                Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                    } else {
+                        reply.writeInt(0);
+                    }
+                    return true;
+                }
             }
         } catch (Exception e) {
             DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -568,5 +600,50 @@
         return bundle;
     }
 
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        data.writeString(mimeTypeFilter);
+
+        mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionFromParcel(reply);
+        String[] out = reply.createStringArray();
+
+        data.recycle();
+        reply.recycle();
+
+        return out;
+    }
+
+    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+            throws RemoteException, FileNotFoundException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+
+        data.writeInterfaceToken(IContentProvider.descriptor);
+
+        url.writeToParcel(data, 0);
+        data.writeString(mimeType);
+        data.writeBundle(opts);
+
+        mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+        DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+        int has = reply.readInt();
+        AssetFileDescriptor fd = has != 0
+                ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+
+        data.recycle();
+        reply.recycle();
+
+        return fd;
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/content/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 81ff414..3289120 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -45,9 +45,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;
 
 
 /**
@@ -220,6 +220,38 @@
     }
 
     /**
+     * Query for the possible MIME types for the representations the given
+     * content URL can be returned when opened as as stream with
+     * {@link #openTypedAssetFileDescriptor}.  Note that the types here are
+     * not necessarily a superset of the type returned by {@link #getType} --
+     * many content providers can not return a raw stream for the structured
+     * data that they contain.
+     *
+     * @param url A Uri identifying content (either a list or specific type),
+     * using the content:// scheme.
+     * @param mimeTypeFilter The desired MIME type.  This may be a pattern,
+     * such as *\/*, to query for all available MIME types that match the
+     * pattern.
+     * @return Returns an array of MIME type strings for all availablle
+     * data streams that match the given mimeTypeFilter.  If there are none,
+     * null is returned.
+     */
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            return null;
+        }
+
+        try {
+            return provider.getStreamTypes(url, mimeTypeFilter);
+        } catch (RemoteException e) {
+            return null;
+        } finally {
+			releaseProvider(provider);
+		}
+    }
+
+    /**
      * <p>
      * Query the given URI, returning a {@link Cursor} over the result set.
      * </p>
@@ -365,7 +397,7 @@
     }
 
     /**
-     * Open a raw file descriptor to access data under a "content:" URI.  This
+     * Open a raw file descriptor to access data under a URI.  This
      * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
      * underlying {@link ContentProvider#openFile}
      * ContentProvider.openFile()} method, so will <em>not</em> work with
@@ -415,10 +447,9 @@
     }
 
     /**
-     * Open a raw file descriptor to access data under a "content:" URI.  This
+     * Open a raw file descriptor to access data under a URI.  This
      * interacts with the underlying {@link ContentProvider#openAssetFile}
-     * 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>
@@ -450,6 +481,11 @@
      * </li>
      * </ul>
      *
+     * <p>Note that if this function is called for read-only input (mode is "r")
+     * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor}
+     * for you with a MIME type of "*\/*".  This allows such callers to benefit
+     * from any built-in data conversion that a provider implements.
+     *
      * @param uri The desired URI to open.
      * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile
      * ContentProvider.openAssetFile}.
@@ -476,29 +512,97 @@
                     new File(uri.getPath()), modeToMode(uri, mode));
             return new AssetFileDescriptor(pfd, 0, -1);
         } else {
-            IContentProvider provider = acquireProvider(uri);
-            if (provider == null) {
-                throw new FileNotFoundException("No content provider: " + uri);
-            }
-            try {
-                AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
-                if(fd == null) {
-                    releaseProvider(provider);
-                    return null;
+            if ("r".equals(mode)) {
+                return openTypedAssetFileDescriptor(uri, "*/*", null);
+            } else {
+                IContentProvider provider = acquireProvider(uri);
+                if (provider == null) {
+                    throw new FileNotFoundException("No content provider: " + uri);
                 }
-                ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
-                        fd.getParcelFileDescriptor(), provider);
-                return new AssetFileDescriptor(pfd, fd.getStartOffset(),
-                        fd.getDeclaredLength());
-            } catch (RemoteException e) {
+                try {
+                    AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
+                    if(fd == null) {
+                        releaseProvider(provider);
+                        return null;
+                    }
+                    ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+                            fd.getParcelFileDescriptor(), provider);
+
+                    // Success!  Don't release the provider when exiting, let
+                    // ParcelFileDescriptorInner do that when it is closed.
+                    provider = null;
+
+                    return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+                            fd.getDeclaredLength());
+                } catch (RemoteException e) {
+                    throw new FileNotFoundException("Dead content provider: " + uri);
+                } catch (FileNotFoundException e) {
+                    throw e;
+                } finally {
+                    if (provider != null) {
+                        releaseProvider(provider);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Open a raw file descriptor to access (potentially type transformed)
+     * data from a "content:" URI.  This interacts with the underlying
+     * {@link ContentProvider#openTypedAssetFile} method of the provider
+     * associated with the given URI, to retrieve retrieve any appropriate
+     * data stream for the data stored there.
+     *
+     * <p>Unlike {@link #openAssetFileDescriptor}, this function only works
+     * with "content:" URIs, because content providers are the only facility
+     * with an associated MIME type to ensure that the returned data stream
+     * is of the desired type.
+     *
+     * <p>All text/* streams are encoded in UTF-8.
+     *
+     * @param uri The desired URI to open.
+     * @param mimeType The desired MIME type of the returned data.  This can
+     * be a pattern such as *\/*, which will allow the content provider to
+     * select a type, though there is no way for you to determine what type
+     * it is returning.
+     * @param opts Additional provider-dependent options.
+     * @return Returns a new ParcelFileDescriptor from which you can read the
+     * data stream from the provider.  Note that this may be a pipe, meaning
+     * you can't seek in it.  The only seek you should do is if the
+     * AssetFileDescriptor contains an offset, to move to that offset before
+     * reading.  You own this descriptor and are responsible for closing it when done.
+     * @throws FileNotFoundException Throws FileNotFoundException of no
+     * data of the desired type exists under the URI.
+     */
+    public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
+            String mimeType, Bundle opts) throws FileNotFoundException {
+        IContentProvider provider = acquireProvider(uri);
+        if (provider == null) {
+            throw new FileNotFoundException("No content provider: " + uri);
+        }
+        try {
+            AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts);
+            if (fd == null) {
                 releaseProvider(provider);
-                throw new FileNotFoundException("Dead content provider: " + uri);
-            } catch (FileNotFoundException e) {
+                return null;
+            }
+            ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+                    fd.getParcelFileDescriptor(), provider);
+
+            // Success!  Don't release the provider when exiting, let
+            // ParcelFileDescriptorInner do that when it is closed.
+            provider = null;
+
+            return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+                    fd.getDeclaredLength());
+        } catch (RemoteException e) {
+            throw new FileNotFoundException("Dead content provider: " + uri);
+        } catch (FileNotFoundException e) {
+            throw e;
+        } finally {
+            if (provider != null) {
                 releaseProvider(provider);
-                throw e;
-            } catch (RuntimeException e) {
-                releaseProvider(provider);
-                throw e;
             }
         }
     }
@@ -1376,7 +1480,7 @@
     }
 
     private final class CursorWrapperInner extends CursorWrapper {
-        private IContentProvider mContentProvider;
+        private final IContentProvider mContentProvider;
         public static final String TAG="CursorWrapperInner";
         private boolean mCloseFlag = false;
 
@@ -1405,7 +1509,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..0d25f80 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -414,6 +414,8 @@
         } catch (ClassCastException e) {
             if (value instanceof CharSequence) {
                 return Boolean.valueOf(value.toString());
+            } else if (value instanceof Number) {
+                return ((Number) value).intValue() != 0;
             } else {
                 Log.e(TAG, "Cannot cast value for " + key + " to a Boolean: " + value, e);
                 return null;
@@ -446,6 +448,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"})
@@ -488,6 +499,10 @@
         return (ArrayList<String>) mValues.get(key);
     }
 
+    /**
+     * Returns a string containing a concise, human-readable description of this object.
+     * @return a printable representation of this object.
+     */
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 693be21..9c8d698 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..7776874
--- /dev/null
+++ b/core/java/android/content/CursorLoader.java
@@ -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.
+ */
+
+package android.content;
+
+import android.database.ContentObserver;
+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);
+        if (cursor != null) {
+            // Ensure the cursor window is filled
+            cursor.getCount();
+            registerContentObserver(cursor, mObserver);
+        }
+        return cursor;
+    }
+
+    /**
+     * Registers an observer to get notifications from the content provider
+     * when the cursor needs to be refreshed.
+     */
+    public void registerContentObserver(Cursor cursor, ContentObserver observer) {
+        cursor.registerContentObserver(mObserver);
+    }
+
+    /* 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..254901b
--- /dev/null
+++ b/core/java/android/content/IClipboard.aidl
@@ -0,0 +1,40 @@
+/**
+ * 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.ClipData;
+import android.content.ClipDescription;
+import android.content.IOnPrimaryClipChangedListener;
+
+/**
+ * Programming interface to the clipboard, which allows copying and pasting
+ * between applications.
+ * {@hide}
+ */
+interface IClipboard {
+    void setPrimaryClip(in ClipData clip);
+    ClipData getPrimaryClip(String pkg);
+    ClipDescription getPrimaryClipDescription();
+    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/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 67e7581..8f122ce 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -59,6 +59,7 @@
             throws RemoteException, FileNotFoundException;
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException;
+
     /**
      * @hide -- until interface has proven itself
      *
@@ -71,6 +72,11 @@
      */
     public Bundle call(String method, String request, Bundle args) throws RemoteException;
 
+    // Data interchange.
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
+    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+            throws RemoteException, FileNotFoundException;
+
     /* IPC constants */
     static final String descriptor = "android.content.IContentProvider";
 
@@ -84,4 +90,6 @@
     static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
     static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19;
     static final int CALL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 20;
+    static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21;
+    static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22;
 }
diff --git a/core/java/android/content/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/Intent.java b/core/java/android/content/Intent.java
index 7154aee..4d0b8b0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -534,6 +534,7 @@
  *     <li> {@link #CATEGORY_CAR_DOCK}
  *     <li> {@link #CATEGORY_DESK_DOCK}
  *     <li> {@link #CATEGORY_CAR_MODE}
+ *     <li> {@link #CATEGORY_APP_MARKET}
  * </ul>
  *
  * <h3>Standard Extra Data</h3>
@@ -985,6 +986,15 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_INSERT = "android.intent.action.INSERT";
     /**
+     * Activity Action: Create a new item in the given container, initializing it
+     * from the current contents of the clipboard.
+     * <p>Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*)
+     * in which to place the data.
+     * <p>Output: URI of the new data that was created.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PASTE = "android.intent.action.PASTE";
+    /**
      * Activity Action: Delete the given data from its container.
      * <p>Input: {@link #getData} is URI of data to be deleted.
      * <p>Output: nothing.
@@ -2001,6 +2011,11 @@
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_EMBED = "android.intent.category.EMBED";
     /**
+     * This activity allows the user to browse and download new applications.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_APP_MARKET = "android.intent.category.APP_MARKET";
+    /**
      * This activity may be exercised by the monkey or other automated test tools.
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
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 1484204..c0788f5 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
@@ -71,6 +72,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} or {@link #apply} are called.
          * 
@@ -218,6 +230,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 26b6ad7..c9115c5 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,6 +16,20 @@
 
 package android.content;
 
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
 
@@ -34,17 +48,6 @@
 import android.content.pm.RegisteredServicesCacheListener;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.format.DateUtils;
@@ -56,10 +59,13 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Random;
-import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -78,30 +84,17 @@
     private static final long MAX_TIME_PER_SYNC;
 
     static {
-        String localSyncDelayString = SystemProperties.get("sync.local_sync_delay");
-        long localSyncDelay = 30 * 1000; // 30 seconds
-        if (localSyncDelayString != null) {
-            try {
-                localSyncDelay = Long.parseLong(localSyncDelayString);
-            } catch (NumberFormatException nfe) {
-                // ignore, use default
-            }
-        }
-        LOCAL_SYNC_DELAY = localSyncDelay;
-
-        String maxTimePerSyncString = SystemProperties.get("sync.max_time_per_sync");
-        long maxTimePerSync = 5 * 60 * 1000; // 5 minutes
-        if (maxTimePerSyncString != null) {
-            try {
-                maxTimePerSync = Long.parseLong(maxTimePerSyncString);
-            } catch (NumberFormatException nfe) {
-                // ignore, use default
-            }
-        }
-        MAX_TIME_PER_SYNC = maxTimePerSync;
+        MAX_SIMULTANEOUS_INITIALIZATION_SYNCS = SystemProperties.getInt("sync.max_init_syncs", 5);
+        MAX_SIMULTANEOUS_REGULAR_SYNCS = SystemProperties.getInt("sync.max_regular_syncs", 2);
+        LOCAL_SYNC_DELAY =
+                SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */);
+        MAX_TIME_PER_SYNC =
+                SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */);
+        SYNC_NOTIFICATION_DELAY =
+                SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */);
     }
 
-    private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds
+    private static final long SYNC_NOTIFICATION_DELAY;
 
     /**
      * When retrying a sync for the first time use this delay. After that
@@ -120,22 +113,21 @@
      */
     private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
 
-    /**
-     * An error notification is sent if sync of any of the providers has been failing for this long.
-     */
-    private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
-
     private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
 
-    private static final String SYNC_WAKE_LOCK = "*sync*";
+    private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
     private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
+    private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
+
+    private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS;
+    private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS;
 
     private Context mContext;
 
     private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
 
-    volatile private PowerManager.WakeLock mSyncWakeLock;
     volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
+    volatile private PowerManager.WakeLock mSyncManagerWakeLock;
     volatile private boolean mDataConnectionIsConnected = false;
     volatile private boolean mStorageIsLow = false;
 
@@ -145,10 +137,8 @@
     private final SyncStorageEngine mSyncStorageEngine;
     public final SyncQueue mSyncQueue;
 
-    private ActiveSyncContext mActiveSyncContext = null;
+    private final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
 
-    // set if the sync error indicator should be reported.
-    private boolean mNeedSyncErrorNotification = false;
     // set if the sync active indicator should be reported
     private boolean mNeedSyncActiveNotification = false;
 
@@ -196,6 +186,11 @@
 
     private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0];
 
+    private final PowerManager mPowerManager;
+
+    private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
+    private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
+
     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;
@@ -203,11 +198,10 @@
 
         // if a sync is in progress yet it is no longer in the accounts list,
         // cancel it
-        ActiveSyncContext activeSyncContext = mActiveSyncContext;
-        if (activeSyncContext != null) {
-            if (!ArrayUtils.contains(accounts, activeSyncContext.mSyncOperation.account)) {
+        for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+            if (!ArrayUtils.contains(accounts, currentSyncContext.mSyncOperation.account)) {
                 Log.d(TAG, "canceling sync since the account has been removed");
-                sendSyncFinishedOrCanceledMessage(activeSyncContext,
+                sendSyncFinishedOrCanceledMessage(currentSyncContext,
                         null /* no result since this is a cancel */);
             }
         }
@@ -234,6 +228,7 @@
             // If this was the bootup case then don't sync everything, instead only
             // sync those that have an unknown syncable state, which will give them
             // a chance to set their syncable state.
+
             boolean onlyThoseWithUnkownSyncableState = justBootedUp;
             scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState);
         }
@@ -357,18 +352,25 @@
         } 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);
 
+        // This WakeLock is used to ensure that we stay awake while running the sync loop
+        // message handler. Normally we will hold a sync adapter wake lock while it is being
+        // synced but during the execution of the sync loop it might finish a sync for
+        // one sync adapter before starting the sync for the other sync adapter and we
+        // don't want the device to go to sleep during that window.
+        mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                SYNC_LOOP_WAKE_LOCK);
+        mSyncManagerWakeLock.setReferenceCounted(false);
+
         mSyncStorageEngine.addStatusChangeListener(
                 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
             public void onStatusChanged(int which) {
@@ -425,8 +427,8 @@
         Intent intent = new Intent();
         intent.setAction("android.content.SyncAdapter");
         intent.setComponent(syncAdapterInfo.componentName);
-        if (!mContext.bindService(intent, new InitializerServiceConnection(account, authority, mContext,
-                mMainHandler),
+        if (!mContext.bindService(intent,
+                new InitializerServiceConnection(account, authority, mContext, mMainHandler),
                 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) {
             Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent);
         }
@@ -506,8 +508,8 @@
      * @param requestedAccount the account to sync, may be null to signify all accounts
      * @param requestedAuthority the authority to sync, may be null to indicate all authorities
      * @param extras a Map of SyncAdapter-specific information to control
-*          syncs of a specific provider. Can be null. Is ignored
-*          if the url is null.
+     *          syncs of a specific provider. Can be null. Is ignored
+     *          if the url is null.
      * @param delay how many milliseconds in the future to wait before performing this
      * @param onlyThoseWithUnkownSyncableState
      */
@@ -611,16 +613,29 @@
                         continue;
                     }
 
-                    if (isLoggable) {
-                        Log.v(TAG, "scheduleSync:"
-                                + " delay " + delay
-                                + ", source " + source
-                                + ", account " + account
-                                + ", authority " + authority
-                                + ", extras " + extras);
+                    Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(account, authority);
+                    long delayUntil = mSyncStorageEngine.getDelayUntilTime(account, authority);
+                    final long backoffTime = backoff != null ? backoff.first : 0;
+                    if (isSyncable < 0) {
+                        Bundle newExtras = new Bundle();
+                        newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+                        scheduleSyncOperation(
+                                new SyncOperation(account, source, authority, newExtras, 0,
+                                        backoffTime, delayUntil));
                     }
-                    scheduleSyncOperation(
-                            new SyncOperation(account, source, authority, extras, delay));
+                    if (!onlyThoseWithUnkownSyncableState) {
+                        if (isLoggable) {
+                            Log.v(TAG, "scheduleSync:"
+                                    + " delay " + delay
+                                    + ", source " + source
+                                    + ", account " + account
+                                    + ", authority " + authority
+                                    + ", extras " + extras);
+                        }
+                        scheduleSyncOperation(
+                                new SyncOperation(account, source, authority, extras, delay,
+                                        backoffTime, delayUntil));
+                    }
                 }
             }
         }
@@ -634,7 +649,8 @@
     }
 
     public SyncAdapterType[] getSyncAdapterTypes() {
-        final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos =
+        final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>
+                serviceInfos =
                 mSyncAdapters.getAllServices();
         SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
         int i = 0;
@@ -664,6 +680,14 @@
         mSyncHandler.sendMessage(msg);
     }
 
+    private void sendCancelSyncsMessage(final Account account, final String authority) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
+        Message msg = mSyncHandler.obtainMessage();
+        msg.what = SyncHandler.MESSAGE_CANCEL;
+        msg.obj = Pair.create(account, authority);
+        mSyncHandler.sendMessage(msg);
+    }
+
     class SyncHandlerMessagePayload {
         public final ActiveSyncContext activeSyncContext;
         public final SyncResult syncResult;
@@ -681,11 +705,6 @@
         }
     }
 
-    private void clearBackoffSetting(SyncOperation op) {
-        mSyncStorageEngine.setBackoff(op.account, op.authority,
-                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
-    }
-
     private void increaseBackoffSetting(SyncOperation op) {
         final long now = SystemClock.elapsedRealtime();
 
@@ -711,6 +730,9 @@
 
         mSyncStorageEngine.setBackoff(op.account, op.authority,
                 now + newDelayInMs, newDelayInMs);
+        synchronized (mSyncQueue) {
+            mSyncQueue.onBackoffChanged(op.account, op.authority, now + newDelayInMs);
+        }
     }
 
     private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
@@ -723,6 +745,9 @@
             newDelayUntilTime = 0;
         }
         mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
+        synchronized (mSyncQueue) {
+            mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
+        }
     }
 
     /**
@@ -731,23 +756,7 @@
      * @param authority limit the cancelations to syncs with this authority, if non-null
      */
     public void cancelActiveSync(Account account, String authority) {
-        ActiveSyncContext activeSyncContext = mActiveSyncContext;
-        if (activeSyncContext != null) {
-            // if an authority was specified then only cancel the sync if it matches
-            if (account != null) {
-                if (!account.equals(activeSyncContext.mSyncOperation.account)) {
-                    return;
-                }
-            }
-            // if an account was specified then only cancel the sync if it matches
-            if (authority != null) {
-                if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
-                    return;
-                }
-            }
-            sendSyncFinishedOrCanceledMessage(activeSyncContext,
-                    null /* no result since this is a cancel */);
-        }
+        sendCancelSyncsMessage(account, authority);
     }
 
     /**
@@ -756,22 +765,6 @@
      * @param syncOperation the SyncOperation to schedule
      */
     public void scheduleSyncOperation(SyncOperation syncOperation) {
-        // If this operation is expedited and there is a sync in progress then
-        // reschedule the current operation and send a cancel for it.
-        final ActiveSyncContext activeSyncContext = mActiveSyncContext;
-        if (syncOperation.expedited && activeSyncContext != null) {
-            final boolean hasSameKey =
-                    activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
-            // This request is expedited and there is a sync in progress.
-            // Interrupt the current sync only if it is not expedited and if it has a different
-            // key than the one we are scheduling.
-            if (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) {
-                scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
-                sendSyncFinishedOrCanceledMessage(activeSyncContext,
-                        null /* no result since this is a cancel */);
-            }
-        }
-
         boolean queueChanged;
         synchronized (mSyncQueue) {
             queueChanged = mSyncQueue.add(syncOperation);
@@ -796,11 +789,11 @@
      * @param authority limit the removals to operations with this authority, if non-null
      */
     public void clearScheduledSyncOperations(Account account, String authority) {
-        mSyncStorageEngine.setBackoff(account, authority,
-                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
         synchronized (mSyncQueue) {
             mSyncQueue.remove(account, authority);
         }
+        mSyncStorageEngine.setBackoff(account, authority,
+                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
     }
 
     void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
@@ -827,7 +820,8 @@
         if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
             Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
                     + operation);
-        } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+        } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
+                && !syncResult.syncAlreadyInProgress) {
             operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
             Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
                     + "encountered an error: " + operation);
@@ -848,7 +842,8 @@
             }
             scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource,
                     operation.authority, operation.extras,
-                    DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000));
+                    DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000,
+                    operation.backoff, operation.delayUntil));
         } else if (syncResult.hasSoftError()) {
             if (isLoggable) {
                 Log.d(TAG, "retrying sync operation because it encountered a soft error: "
@@ -871,15 +866,33 @@
         final long mStartTime;
         long mTimeoutStartTime;
         boolean mBound;
+        final PowerManager.WakeLock mSyncWakeLock;
+        final int mSyncAdapterUid;
+        SyncInfo mSyncInfo;
 
-        public ActiveSyncContext(SyncOperation syncOperation,
-                long historyRowId) {
+        /**
+         * Create an ActiveSyncContext for an impending sync and grab the wakelock for that
+         * sync adapter. Since this grabs the wakelock you need to be sure to call
+         * close() when you are done with this ActiveSyncContext, whether the sync succeeded
+         * or not.
+         * @param syncOperation the SyncOperation we are about to sync
+         * @param historyRowId the row in which to record the history info for this sync
+         * @param syncAdapterUid the UID of the application that contains the sync adapter
+         * for this sync. This is used to attribute the wakelock hold to that application.
+         */
+        public ActiveSyncContext(SyncOperation syncOperation, long historyRowId,
+                int syncAdapterUid) {
             super();
+            mSyncAdapterUid = syncAdapterUid;
             mSyncOperation = syncOperation;
             mHistoryRowId = historyRowId;
             mSyncAdapter = null;
             mStartTime = SystemClock.elapsedRealtime();
             mTimeoutStartTime = mStartTime;
+            mSyncWakeLock = mSyncHandler.getSyncWakeLock(
+                    mSyncOperation.account.type, mSyncOperation.authority);
+            mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
+            mSyncWakeLock.acquire();
         }
 
         public void sendHeartbeat() {
@@ -887,6 +900,7 @@
         }
 
         public void onFinished(SyncResult result) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this);
             // include "this" in the message so that the handler can ignore it if this
             // ActiveSyncContext is no longer the mActiveSyncContext at message handling
             // time
@@ -934,6 +948,10 @@
             return bindResult;
         }
 
+        /**
+         * Performs the required cleanup, which is the releasing of the wakelock and
+         * unbinding from the sync adapter (if actually bound).
+         */
         protected void close() {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
@@ -942,6 +960,8 @@
                 mBound = false;
                 mContext.unbindService(this);
             }
+            mSyncWakeLock.setWorkSource(null);
+            mSyncWakeLock.release();
         }
 
         @Override
@@ -1001,62 +1021,28 @@
             pw.println("no alarm is scheduled (there had better not be any pending syncs)");
         }
 
-        final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext;
-
-        pw.print("active sync: "); pw.println(activeSyncContext);
-
         pw.print("notification info: ");
         sb.setLength(0);
         mSyncHandler.mSyncNotificationInfo.toString(sb);
         pw.println(sb.toString());
 
+        pw.println();
+        pw.println("Active Syncs: " + mActiveSyncContexts.size());
+        for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+            final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000;
+            pw.print("  ");
+            pw.print(DateUtils.formatElapsedTime(durationInSeconds));
+            pw.print(" - ");
+            pw.print(activeSyncContext.mSyncOperation.dump(false));
+            pw.println();
+        }
+
         synchronized (mSyncQueue) {
-            pw.print("sync queue: ");
             sb.setLength(0);
             mSyncQueue.dump(sb);
-            pw.println(sb.toString());
         }
-
-        SyncInfo active = mSyncStorageEngine.getCurrentSync();
-        if (active != null) {
-            SyncStorageEngine.AuthorityInfo authority
-                    = mSyncStorageEngine.getAuthority(active.authorityId);
-            final long durationInSeconds = (now - active.startTime) / 1000;
-            pw.print("Active sync: ");
-                    pw.print(authority != null ? authority.account : "<no account>");
-                    pw.print(" ");
-                    pw.print(authority != null ? authority.authority : "<no account>");
-                    if (activeSyncContext != null) {
-                        pw.print(" ");
-                        pw.print(SyncStorageEngine.SOURCES[
-                                activeSyncContext.mSyncOperation.syncSource]);
-                    }
-                    pw.print(", duration is ");
-                    pw.println(DateUtils.formatElapsedTime(durationInSeconds));
-        } else {
-            pw.println("No sync is in progress.");
-        }
-
-        ArrayList<SyncStorageEngine.PendingOperation> ops
-                = mSyncStorageEngine.getPendingOperations();
-        if (ops != null && ops.size() > 0) {
-            pw.println();
-            pw.println("Pending Syncs");
-            final int N = ops.size();
-            for (int i=0; i<N; i++) {
-                SyncStorageEngine.PendingOperation op = ops.get(i);
-                pw.print("  #"); pw.print(i); pw.print(": account=");
-                pw.print(op.account.name); pw.print(":");
-                pw.print(op.account.type); pw.print(" authority=");
-                pw.print(op.authority); pw.print(" expedited=");
-                pw.println(op.expedited);
-                if (op.extras != null && op.extras.size() > 0) {
-                    sb.setLength(0);
-                    SyncOperation.extrasToStringBuilder(op.extras, sb, false /* asKey */);
-                    pw.print("    extras: "); pw.println(sb.toString());
-                }
-            }
-        }
+        pw.println();
+        pw.print(sb.toString());
 
         // join the installed sync adapter with the accounts list and emit for everything
         pw.println();
@@ -1259,7 +1245,7 @@
 
         /** Call to let the tracker know that the sync state may have changed */
         public synchronized void update() {
-            final boolean isSyncInProgress = mActiveSyncContext != null;
+            final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty();
             if (isSyncInProgress == mLastWasSyncing) return;
             final long now = SystemClock.elapsedRealtime();
             if (isSyncInProgress) {
@@ -1299,14 +1285,14 @@
         private static final int MESSAGE_CHECK_ALARMS = 3;
         private static final int MESSAGE_SERVICE_CONNECTED = 4;
         private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
+        private static final int MESSAGE_CANCEL = 6;
 
         public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
         private Long mAlarmScheduleTime = null;
         public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
+        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
-        private boolean mErrorNotificationInstalled = false;
         private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
         public void onBootCompleted() {
             mBootCompleted = true;
@@ -1316,6 +1302,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) {
@@ -1334,12 +1332,6 @@
          * Used to keep track of whether a sync notification is active and who it is for.
          */
         class SyncNotificationInfo {
-            // only valid if isActive is true
-            public Account account;
-
-            // only valid if isActive is true
-            public String authority;
-
             // true iff the notification manager has been asked to send the notification
             public boolean isActive = false;
 
@@ -1348,10 +1340,7 @@
             public Long startTime = null;
 
             public void toString(StringBuilder sb) {
-                sb.append("account ").append(account)
-                        .append(", authority ").append(authority)
-                        .append(", isActive ").append(isActive)
-                        .append(", startTime ").append(startTime);
+                sb.append("isActive ").append(isActive).append(", startTime ").append(startTime);
             }
 
             @Override
@@ -1367,60 +1356,72 @@
         }
 
         public void handleMessage(Message msg) {
-            Long earliestFuturePollTime = null;
+            long earliestFuturePollTime = Long.MAX_VALUE;
+            long nextPendingSyncTime = Long.MAX_VALUE;
             try {
                 waitUntilReadyToRun();
+                mSyncManagerWakeLock.acquire();
                 // Always do this first so that we be sure that any periodic syncs that
                 // are ready to run have been converted into pending syncs. This allows the
                 // logic that considers the next steps to take based on the set of pending syncs
                 // to also take into account the periodic syncs.
                 earliestFuturePollTime = scheduleReadyPeriodicSyncs();
                 switch (msg.what) {
+                    case SyncHandler.MESSAGE_CANCEL: {
+                        Pair<Account, String> payload = (Pair<Account, String>)msg.obj;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
+                                    + payload.first + ", " + payload.second);
+                        }
+                        cancelActiveSyncLocked(payload.first, payload.second);
+                        nextPendingSyncTime = maybeStartNextSyncLocked();
+                        break;
+                    }
+
                     case SyncHandler.MESSAGE_SYNC_FINISHED:
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
                         }
                         SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
-                        if (mActiveSyncContext != payload.activeSyncContext) {
-                            Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
-                                    + "dropping: mActiveSyncContext " + mActiveSyncContext
-                                    + " != " + payload.activeSyncContext);
-                            return;
+                        if (!isSyncStillActive(payload.activeSyncContext)) {
+                            Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
+                                    + "sync is no longer active: "
+                                    + payload.activeSyncContext);
+                            break;
                         }
-                        runSyncFinishedOrCanceled(payload.syncResult);
+                        runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
 
-                        // since we are no longer syncing, check if it is time to start a new sync
-                        runStateIdle();
+                        // since a sync just finished check if it is time to start a new sync
+                        nextPendingSyncTime = maybeStartNextSyncLocked();
                         break;
 
                     case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
                         ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
-                                    + msgData.activeSyncContext
-                                    + " active is " + mActiveSyncContext);
+                                    + msgData.activeSyncContext);
                         }
                         // check that this isn't an old message
-                        if (mActiveSyncContext == msgData.activeSyncContext) {
-                            runBoundToSyncAdapter(msgData.syncAdapter);
+                        if (isSyncStillActive(msgData.activeSyncContext)) {
+                            runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
                         }
                         break;
                     }
 
                     case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
-                        ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
+                        final ActiveSyncContext currentSyncContext =
+                                ((ServiceConnectionData)msg.obj).activeSyncContext;
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
-                                    + msgData.activeSyncContext
-                                    + " active is " + mActiveSyncContext);
+                                    + currentSyncContext);
                         }
                         // check that this isn't an old message
-                        if (mActiveSyncContext == msgData.activeSyncContext) {
+                        if (isSyncStillActive(currentSyncContext)) {
                             // cancel the sync if we have a syncadapter, which means one is
                             // outstanding
-                            if (mActiveSyncContext.mSyncAdapter != null) {
+                            if (currentSyncContext.mSyncAdapter != null) {
                                 try {
-                                    mActiveSyncContext.mSyncAdapter.cancelSync(mActiveSyncContext);
+                                    currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
                                 } catch (RemoteException e) {
                                     // we don't need to retry this in this case
                                 }
@@ -1430,11 +1431,10 @@
                             // which is a soft error
                             SyncResult syncResult = new SyncResult();
                             syncResult.stats.numIoExceptions++;
-                            runSyncFinishedOrCanceled(syncResult);
+                            runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext);
 
-                            // since we are no longer syncing, check if it is time to start a new
-                            // sync
-                            runStateIdle();
+                            // since a sync just finished check if it is time to start a new sync
+                            nextPendingSyncTime = maybeStartNextSyncLocked();
                         }
 
                         break;
@@ -1447,22 +1447,7 @@
                         }
                         mAlarmScheduleTime = null;
                         try {
-                            if (mActiveSyncContext != null) {
-                                if (isLoggable) {
-                                    Log.v(TAG, "handleSyncHandlerMessage: sync context is active");
-                                }
-                                runStateSyncing();
-                            }
-
-                            // if the above call to runStateSyncing() resulted in the end of a sync,
-                            // check if it is time to start a new sync
-                            if (mActiveSyncContext == null) {
-                                if (isLoggable) {
-                                    Log.v(TAG, "handleSyncHandlerMessage: "
-                                            + "sync context is not active");
-                                }
-                                runStateIdle();
-                            }
+                            nextPendingSyncTime = maybeStartNextSyncLocked();
                         } finally {
                             mHandleAlarmWakeLock.release();
                         }
@@ -1473,18 +1458,14 @@
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
                         }
-                        // we do all the work for this case in the finally block
+                        nextPendingSyncTime = maybeStartNextSyncLocked();
                         break;
                 }
             } finally {
-                final boolean isSyncInProgress = mActiveSyncContext != null;
-                if (!isSyncInProgress) {
-                    mSyncWakeLock.release();
-                }
-                manageSyncNotification();
-                manageErrorNotification();
-                manageSyncAlarm(earliestFuturePollTime);
+                manageSyncNotificationLocked();
+                manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
                 mSyncTimeTracker.update();
+                mSyncManagerWakeLock.release();
             }
         }
 
@@ -1493,10 +1474,10 @@
          * @return the desired start time of the earliest future  periodic sync operation,
          * in milliseconds since boot
          */
-        private Long scheduleReadyPeriodicSyncs() {
+        private long scheduleReadyPeriodicSyncs() {
             final boolean backgroundDataUsageAllowed =
                     getConnectivityManager().getBackgroundDataSetting();
-            Long earliestFuturePollTime = null;
+            long earliestFuturePollTime = Long.MAX_VALUE;
             if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) {
                 return earliestFuturePollTime;
             }
@@ -1526,23 +1507,27 @@
                     long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
                     // if it is ready to run then schedule it and mark it as having been scheduled
                     if (nextPollTimeAbsolute <= nowAbsolute) {
+                        final Pair<Long, Long> backoff =
+                                mSyncStorageEngine.getBackoff(info.account, info.authority);
                         scheduleSyncOperation(
                                 new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC,
-                                        info.authority, extras, 0 /* delay */));
+                                        info.authority, extras, 0 /* delay */,
+                                        backoff != null ? backoff.first : 0,
+                                        mSyncStorageEngine.getDelayUntilTime(
+                                                info.account, info.authority)));
                         status.setPeriodicSyncTime(i, nowAbsolute);
                     } else {
                         // it isn't ready to run, remember this time if it is earlier than
                         // earliestFuturePollTime
-                        if (earliestFuturePollTime == null
-                                || nextPollTimeAbsolute < earliestFuturePollTime) {
+                        if (nextPollTimeAbsolute < earliestFuturePollTime) {
                             earliestFuturePollTime = nextPollTimeAbsolute;
                         }
                     }
                 }
             }
 
-            if (earliestFuturePollTime == null) {
-                return null;
+            if (earliestFuturePollTime == Long.MAX_VALUE) {
+                return Long.MAX_VALUE;
             }
 
             // convert absolute time to elapsed time
@@ -1552,47 +1537,23 @@
                       : (earliestFuturePollTime - nowAbsolute));
         }
 
-        private void runStateSyncing() {
-            // if the sync timeout has been reached then cancel it
-            ActiveSyncContext activeSyncContext = mActiveSyncContext;
-
-            final long now = SystemClock.elapsedRealtime();
-            if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
-                Pair<SyncOperation, Long> nextOpAndRunTime;
-                synchronized (mSyncQueue) {
-                    nextOpAndRunTime = mSyncQueue.nextOperation();
-                }
-                if (nextOpAndRunTime != null && nextOpAndRunTime.second <= now) {
-                    Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
-                            + activeSyncContext.mSyncOperation);
-                    scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
-                    sendSyncFinishedOrCanceledMessage(activeSyncContext,
-                            null /* no result since this is a cancel */);
-                } else {
-                    activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC;
-                }
-            }
-
-            // no need to schedule an alarm, as that will be done by our caller.
-        }
-
-        private void runStateIdle() {
-            boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-            if (isLoggable) Log.v(TAG, "runStateIdle");
+        private long maybeStartNextSyncLocked() {
+            final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+            if (isLoggable) Log.v(TAG, "maybeStartNextSync");
 
             // If we aren't ready to run (e.g. the data connection is down), get out.
             if (!mDataConnectionIsConnected) {
                 if (isLoggable) {
-                    Log.v(TAG, "runStateIdle: no data connection, skipping");
+                    Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
                 }
-                return;
+                return Long.MAX_VALUE;
             }
 
             if (mStorageIsLow) {
                 if (isLoggable) {
-                    Log.v(TAG, "runStateIdle: memory low, skipping");
+                    Log.v(TAG, "maybeStartNextSync: memory low, skipping");
                 }
-                return;
+                return Long.MAX_VALUE;
             }
 
             // If the accounts aren't known yet then we aren't ready to run. We will be kicked
@@ -1600,46 +1561,56 @@
             Account[] accounts = mAccounts;
             if (accounts == INITIAL_ACCOUNTS_ARRAY) {
                 if (isLoggable) {
-                    Log.v(TAG, "runStateIdle: accounts not known, skipping");
+                    Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
                 }
-                return;
+                return Long.MAX_VALUE;
             }
 
             // Otherwise consume SyncOperations from the head of the SyncQueue until one is
             // found that is runnable (not disabled, etc). If that one is ready to run then
             // start it, otherwise just get out.
-            SyncOperation op;
-            int syncableState;
             final boolean backgroundDataUsageAllowed =
                     getConnectivityManager().getBackgroundDataSetting();
             final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
 
-            synchronized (mSyncQueue) {
-                final long now = SystemClock.elapsedRealtime();
-                while (true) {
-                    Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
-                    if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
-                        if (isLoggable) {
-                            Log.v(TAG, "runStateIdle: no more ready sync operations, returning");
-                        }
-                        return;
-                    }
-                    op = nextOpAndRunTime.first;
+            final long now = SystemClock.elapsedRealtime();
 
-                    // we are either going to run this sync or drop it so go ahead and
-                    // remove it from the queue now
-                    mSyncQueue.remove(op);
+            // will be set to the next time that a sync should be considered for running
+            long nextReadyToRunTime = Long.MAX_VALUE;
+
+            // order the sync queue, dropping syncs that are not allowed
+            ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>();
+            synchronized (mSyncQueue) {
+                if (isLoggable) {
+                    Log.v(TAG, "build the operation array, syncQueue size is "
+                        + mSyncQueue.mOperationsMap.size());
+                }
+                Iterator<SyncOperation> operationIterator =
+                        mSyncQueue.mOperationsMap.values().iterator();
+                while (operationIterator.hasNext()) {
+                    final SyncOperation op = operationIterator.next();
 
                     // drop the sync if the account of this operation no longer exists
                     if (!ArrayUtils.contains(mAccounts, op.account)) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
                         continue;
                     }
 
-
-                    // drop this sync request if it isn't syncable, intializing the sync adapter
-                    // if the syncable state is set to "unknown"
-                    syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
+                    // drop this sync request if it isn't syncable
+                    int syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
                     if (syncableState == 0) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+                        continue;
+                    }
+
+                    // if the next run time is in the future, meaning there are no syncs ready
+                    // to run, return the time
+                    if (op.effectiveRunTime > now) {
+                        if (nextReadyToRunTime > op.effectiveRunTime) {
+                            nextReadyToRunTime = op.effectiveRunTime;
+                        }
                         continue;
                     }
 
@@ -1651,30 +1622,139 @@
                                 || !backgroundDataUsageAllowed
                                 || !mSyncStorageEngine.getSyncAutomatically(
                                        op.account, op.authority))) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
                         continue;
                     }
 
-                    // go ahead and try to sync this syncOperation
-                    break;
-                }
-
-                // We will do this sync. Run it outside of the synchronized block.
-                if (isLoggable) {
-                    Log.v(TAG, "runStateIdle: we are going to sync " + op);
+                    operations.add(op);
                 }
             }
 
-            // convert the op into an initialization sync if the syncable state is "unknown" and
-            // op isn't already an initialization sync. If it is marked syncable then convert
-            // this into a regular sync
-            final boolean initializeIsSet =
-                    op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
-            if (syncableState < 0 && !initializeIsSet) {
-                op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
-                op = new SyncOperation(op);
-            } else if (syncableState > 0 && initializeIsSet) {
-                op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
-                op = new SyncOperation(op);
+            // find the next operation to dispatch, if one is ready
+            // iterate from the top, keep issuing (while potentially cancelling existing syncs)
+            // until the quotas are filled.
+            // once the quotas are filled iterate once more to find when the next one would be
+            // (also considering pre-emption reasons).
+            if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size());
+            Collections.sort(operations);
+            if (isLoggable) Log.v(TAG, "dispatch all ready sync operations");
+            for (int i = 0, N = operations.size(); i < N; i++) {
+                final SyncOperation candidate = operations.get(i);
+                final boolean candidateIsInitialization = candidate.isInitialization();
+
+                int numInit = 0;
+                int numRegular = 0;
+                ActiveSyncContext conflict = null;
+                ActiveSyncContext longRunning = null;
+                ActiveSyncContext toReschedule = null;
+
+                for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+                    final SyncOperation activeOp = activeSyncContext.mSyncOperation;
+                    if (activeOp.isInitialization()) {
+                        numInit++;
+                    } else {
+                        numRegular++;
+                    }
+                    if (activeOp.account.type.equals(candidate.account.type)
+                            && activeOp.authority.equals(candidate.authority)) {
+                        conflict = activeSyncContext;
+                        // don't break out since we want to do a full count of the varieties
+                    } else {
+                        if (candidateIsInitialization == activeOp.isInitialization()
+                                && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
+                            longRunning = activeSyncContext;
+                            // don't break out since we want to do a full count of the varieties
+                        }
+                    }
+                }
+
+                if (isLoggable) {
+                    Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate);
+                    Log.v(TAG, "  numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
+                    Log.v(TAG, "  longRunning: " + longRunning);
+                    Log.v(TAG, "  conflict: " + conflict);
+                }
+
+                if (conflict != null) {
+                    if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
+                            && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
+                        toReschedule = conflict;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "canceling and rescheduling sync since an initialization "
+                                    + "takes higher priority, " + conflict);
+                        }
+                    } else if (candidate.expedited && !conflict.mSyncOperation.expedited
+                            && (candidateIsInitialization
+                                == conflict.mSyncOperation.isInitialization())) {
+                        toReschedule = conflict;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "canceling and rescheduling sync since an expedited "
+                                    + "takes higher priority, " + conflict);
+                        }
+                    } else {
+                        continue;
+                    }
+                } else {
+                    final boolean roomAvailable = candidateIsInitialization 
+                            ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS 
+                            : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
+                    if (roomAvailable) {
+                        // dispatch candidate
+                    } else if (longRunning != null
+                            && (candidateIsInitialization
+                                == longRunning.mSyncOperation.isInitialization())) {
+                        toReschedule = longRunning;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
+                                    + longRunning);
+                        }
+                    } else {
+                        continue;
+                    }
+                }
+
+                if (toReschedule != null) {
+                    runSyncFinishedOrCanceledLocked(null, toReschedule);
+                    scheduleSyncOperation(toReschedule.mSyncOperation);
+                }
+    
+                synchronized (mSyncQueue){
+                    mSyncQueue.remove(candidate);
+                }
+                dispatchSyncOperation(candidate);
+            }
+
+            return nextReadyToRunTime;
+     }
+
+        private boolean dispatchSyncOperation(SyncOperation op) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "maybeStartNextSync: we are going to sync " + op);
+                Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
+                for (ActiveSyncContext syncContext : mActiveSyncContexts) {
+                    Log.v(TAG, syncContext.toString());
+                }
+            }
+
+            // if this is an initialization sync and there is already a sync running with
+            // the same account type and authority cancel that sync before starting this one
+            // since otherwise the syncadapter will likely reject this request
+            if (op.isInitialization()) {
+                Iterator<ActiveSyncContext> iterator = mActiveSyncContexts.iterator();
+                while (iterator.hasNext()) {
+                    ActiveSyncContext syncContext = iterator.next();
+                    if (!syncContext.mSyncOperation.isInitialization()
+                            && syncContext.mSyncOperation.account.type.equals(op.account.type)
+                            && syncContext.mSyncOperation.authority.equals(op.authority)) {
+                        Log.d(TAG, "canceling and rescheduling " + syncContext.mSyncOperation
+                                + " since we are about to start a sync that used the "
+                                + "same sync adapter, " + op);
+                        iterator.remove();
+                        runSyncFinishedOrCanceledLocked(null, syncContext);
+                        scheduleSyncOperation(syncContext.mSyncOperation);
+                    }
+                }
             }
 
             // connect to the sync adapter
@@ -1685,61 +1765,70 @@
                 Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
                         + ", removing settings for it");
                 mSyncStorageEngine.removeAuthority(op.account, op.authority);
-                runStateIdle();
-                return;
+                return false;
             }
 
             ActiveSyncContext activeSyncContext =
-                    new ActiveSyncContext(op, insertStartSyncEvent(op));
-            mActiveSyncContext = activeSyncContext;
+                    new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
+            activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
+            mActiveSyncContexts.add(activeSyncContext);
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext);
+                Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
             }
-            mSyncStorageEngine.setActiveSync(mActiveSyncContext);
             if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
                 Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
-                mActiveSyncContext.close();
-                mActiveSyncContext = null;
-                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
-                mSyncWakeLock.setWorkSource(null);
-                runStateIdle();
-                return;
+                closeActiveSyncContext(activeSyncContext);
+                return false;
             }
 
-            mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid));
-            mSyncWakeLock.acquire();
-            // 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
-            // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
+            return true;
         }
 
-        private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
-            mActiveSyncContext.mSyncAdapter = syncAdapter;
-            final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
+        private void runBoundToSyncAdapter(ActiveSyncContext activeSyncContext,
+              ISyncAdapter syncAdapter) {
+            activeSyncContext.mSyncAdapter = syncAdapter;
+            final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
             try {
-                syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
+                syncAdapter.startSync(activeSyncContext, syncOperation.authority,
                         syncOperation.account, syncOperation.extras);
             } catch (RemoteException remoteExc) {
-                Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
-                mActiveSyncContext.close();
-                mActiveSyncContext = null;
-                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+                Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
+                closeActiveSyncContext(activeSyncContext);
                 increaseBackoffSetting(syncOperation);
                 scheduleSyncOperation(new SyncOperation(syncOperation));
             } catch (RuntimeException exc) {
-                mActiveSyncContext.close();
-                mActiveSyncContext = null;
-                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+                closeActiveSyncContext(activeSyncContext);
                 Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
             }
         }
 
-        private void runSyncFinishedOrCanceled(SyncResult syncResult) {
+        private void cancelActiveSyncLocked(Account account, String authority) {
+            ArrayList<ActiveSyncContext> activeSyncs =
+                    new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
+            for (ActiveSyncContext activeSyncContext : activeSyncs) {
+                if (activeSyncContext != null) {
+                    // if an authority was specified then only cancel the sync if it matches
+                    if (account != null) {
+                        if (!account.equals(activeSyncContext.mSyncOperation.account)) {
+                            return;
+                        }
+                    }
+                    // if an account was specified then only cancel the sync if it matches
+                    if (authority != null) {
+                        if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
+                            return;
+                        }
+                    }
+                    runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
+                            activeSyncContext);
+                }
+            }
+        }
+
+        private void runSyncFinishedOrCanceledLocked(SyncResult syncResult,
+                ActiveSyncContext activeSyncContext) {
             boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-            final ActiveSyncContext activeSyncContext = mActiveSyncContext;
-            mActiveSyncContext = null;
-            mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+            closeActiveSyncContext(activeSyncContext);
 
             final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
 
@@ -1759,16 +1848,6 @@
                     // TODO: set these correctly when the SyncResult is extended to include it
                     downstreamActivity = 0;
                     upstreamActivity = 0;
-                    clearBackoffSetting(syncOperation);
-                    // if this was an initialization sync and the sync adapter is now
-                    // marked syncable then reschedule the sync. The next time it runs it
-                    // will be made into a regular sync.
-                    if (syncOperation.extras.getBoolean(
-                                ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
-                            && mSyncStorageEngine.getIsSyncable(
-                                syncOperation.account, syncOperation.authority) > 0) {
-                        scheduleSyncOperation(new SyncOperation(syncOperation));
-                    }
                 } else {
                     Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
                     // the operation failed so increase the backoff time
@@ -1803,8 +1882,6 @@
             stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
                     upstreamActivity, downstreamActivity, elapsedTime);
 
-            activeSyncContext.close();
-
             if (syncResult != null && syncResult.tooManyDeletions) {
                 installHandleTooManyDeletesNotification(syncOperation.account,
                         syncOperation.authority, syncResult.stats.numDeletes);
@@ -1815,11 +1892,18 @@
 
             if (syncResult != null && syncResult.fullSyncRequested) {
                 scheduleSyncOperation(new SyncOperation(syncOperation.account,
-                        syncOperation.syncSource, syncOperation.authority, new Bundle(), 0));
+                        syncOperation.syncSource, syncOperation.authority, new Bundle(), 0,
+                        syncOperation.backoff, syncOperation.delayUntil));
             }
             // no need to schedule an alarm, as that will be done by our caller.
         }
 
+        private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
+            activeSyncContext.close();
+            mActiveSyncContexts.remove(activeSyncContext);
+            mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo);
+        }
+
         /**
          * Convert the error-containing SyncResult into the Sync.History error number. Since
          * the SyncResult may indicate multiple errors at once, this method just returns the
@@ -1849,11 +1933,11 @@
             throw new IllegalStateException("we are not in an error state, " + syncResult);
         }
 
-        private void manageSyncNotification() {
+        private void manageSyncNotificationLocked() {
             boolean shouldCancel;
             boolean shouldInstall;
 
-            if (mActiveSyncContext == null) {
+            if (mActiveSyncContexts.isEmpty()) {
                 mSyncNotificationInfo.startTime = null;
 
                 // we aren't syncing. if the notification is active then remember that we need
@@ -1862,34 +1946,38 @@
                 shouldInstall = false;
             } else {
                 // we are syncing
-                final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
-
                 final long now = SystemClock.elapsedRealtime();
                 if (mSyncNotificationInfo.startTime == null) {
                     mSyncNotificationInfo.startTime = now;
                 }
 
-                // cancel the notification if it is up and the authority or account is wrong
-                shouldCancel = mSyncNotificationInfo.isActive &&
-                        (!syncOperation.authority.equals(mSyncNotificationInfo.authority)
-                        || !syncOperation.account.equals(mSyncNotificationInfo.account));
-
-                // there are four cases:
-                // - the notification is up and there is no change: do nothing
-                // - the notification is up but we should cancel since it is stale:
-                //   need to install
+                // there are three cases:
+                // - the notification is up: do nothing
                 // - the notification is not up but it isn't time yet: don't install
                 // - the notification is not up and it is time: need to install
 
                 if (mSyncNotificationInfo.isActive) {
-                    shouldInstall = shouldCancel;
+                    shouldInstall = shouldCancel = false;
                 } else {
+                    // it isn't currently up, so there is nothing to cancel
+                    shouldCancel = false;
+
                     final boolean timeToShowNotification =
                             now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
-                    // show the notification immediately if this is a manual sync
-                    final boolean manualSync = syncOperation.extras
-                            .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
-                    shouldInstall = timeToShowNotification || manualSync;
+                    if (timeToShowNotification) {
+                        shouldInstall = true;
+                    } else {
+                        // show the notification immediately if this is a manual sync
+                        shouldInstall = false;
+                        for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+                            final boolean manualSync = activeSyncContext.mSyncOperation.extras
+                                    .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+                            if (manualSync) {
+                                shouldInstall = true;
+                                break;
+                            }
+                        }
+                    }
                 }
             }
 
@@ -1900,94 +1988,82 @@
             }
 
             if (shouldInstall) {
-                SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
                 mNeedSyncActiveNotification = true;
                 sendSyncStateIntent();
                 mSyncNotificationInfo.isActive = true;
-                mSyncNotificationInfo.account = syncOperation.account;
-                mSyncNotificationInfo.authority = syncOperation.authority;
             }
         }
 
-        /**
-         * Check if there were any long-lasting errors, if so install the error notification,
-         * otherwise cancel the error notification.
-         */
-        private void manageErrorNotification() {
-            //
-            long when = mSyncStorageEngine.getInitialSyncFailureTime();
-            if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) {
-                if (!mErrorNotificationInstalled) {
-                    mNeedSyncErrorNotification = true;
-                    sendSyncStateIntent();
-                }
-                mErrorNotificationInstalled = true;
-            } else {
-                if (mErrorNotificationInstalled) {
-                    mNeedSyncErrorNotification = false;
-                    sendSyncStateIntent();
-                }
-                mErrorNotificationInstalled = false;
-            }
-        }
-
-        private void manageSyncAlarm(Long earliestFuturePollElapsedTime) {
+        private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime,
+                long nextPendingEventElapsedTime) {
             // in each of these cases the sync loop will be kicked, which will cause this
             // method to be called again
             if (!mDataConnectionIsConnected) return;
             if (mStorageIsLow) return;
 
-            final long now = SystemClock.elapsedRealtime();
+            // When the status bar notification should be raised
+            final long notificationTime =
+                    (!mSyncHandler.mSyncNotificationInfo.isActive
+                            && mSyncHandler.mSyncNotificationInfo.startTime != null)
+                            ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY
+                            : Long.MAX_VALUE;
 
-            // Compute the alarm fire time:
-            // - not syncing: time of the next sync operation
-            // - syncing, no notification: time from sync start to notification create time
-            // - syncing, with notification: time till timeout of the active sync operation
-            Long alarmTime;
-            ActiveSyncContext activeSyncContext = mActiveSyncContext;
-            if (activeSyncContext == null) {
-                synchronized (mSyncQueue) {
-                    final Pair<SyncOperation, Long> candidate = mSyncQueue.nextOperation();
-                    if (earliestFuturePollElapsedTime == null && candidate == null) {
-                        alarmTime = null;
-                    } else if (earliestFuturePollElapsedTime == null) {
-                        alarmTime = candidate.second;
-                    } else if (candidate == null) {
-                        alarmTime = earliestFuturePollElapsedTime;
-                    } else {
-                        alarmTime = Math.min(earliestFuturePollElapsedTime, candidate.second);
-                    }
+            // When we should consider canceling an active sync
+            long earliestTimeoutTime = Long.MAX_VALUE;
+            for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+                final long currentSyncTimeoutTime =
+                        currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is "
+                            + currentSyncTimeoutTime);
                 }
-            } else {
-                final long notificationTime =
-                        mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
-                final long timeoutTime =
-                        mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
-                if (mSyncHandler.mSyncNotificationInfo.isActive) {
-                    alarmTime = timeoutTime;
-                } else {
-                    alarmTime = Math.min(notificationTime, timeoutTime);
+                if (earliestTimeoutTime > currentSyncTimeoutTime) {
+                    earliestTimeoutTime = currentSyncTimeoutTime;
                 }
             }
 
-            // adjust the alarmTime so that we will wake up when it is time to
-            // install the error notification
-            if (!mErrorNotificationInstalled) {
-                long when = mSyncStorageEngine.getInitialSyncFailureTime();
-                if (when > 0) {
-                    when += ERROR_NOTIFICATION_DELAY_MS;
-                    // convert when fron absolute time to elapsed run time
-                    long delay = when - System.currentTimeMillis();
-                    when = now + delay;
-                    alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime);
+            }
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime);
+            }
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is "
+                        + nextPeriodicEventElapsedTime);
+            }
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is "
+                        + nextPendingEventElapsedTime);
+            }
+
+            long alarmTime = Math.min(notificationTime, earliestTimeoutTime);
+            alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime);
+            alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime);
+
+            // Bound the alarm time.
+            final long now = SystemClock.elapsedRealtime();
+            if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, "
+                            + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
                 }
+                alarmTime = now + SYNC_ALARM_TIMEOUT_MIN;
+            } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, "
+                            + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
+                }
+                alarmTime = now + SYNC_ALARM_TIMEOUT_MAX;
             }
 
             // determine if we need to set or cancel the alarm
             boolean shouldSet = false;
             boolean shouldCancel = false;
             final boolean alarmIsActive = mAlarmScheduleTime != null;
-            final boolean needAlarm = alarmTime != null;
+            final boolean needAlarm = alarmTime != Long.MAX_VALUE;
             if (needAlarm) {
                 if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
                     shouldSet = true;
@@ -1999,6 +2075,11 @@
             // set or cancel the alarm as directed
             ensureAlarmService();
             if (shouldSet) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time "
+                            + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000)
+                            + " secs from now");
+                }
                 mAlarmScheduleTime = alarmTime;
                 mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
                         mSyncAlarmIntent);
@@ -2012,7 +2093,7 @@
             Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
             syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
-            syncStateIntent.putExtra("failing", mNeedSyncErrorNotification);
+            syncStateIntent.putExtra("failing", false);
             mContext.sendBroadcast(syncStateIntent);
         }
 
@@ -2101,4 +2182,13 @@
                     resultMessage, downstreamActivity, upstreamActivity);
         }
     }
+
+    private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
+        for (ActiveSyncContext sync : mActiveSyncContexts) {
+            if (sync == activeSyncContext) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index b0160885..3d7f3fbf 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -33,9 +33,12 @@
     public long earliestRunTime;
     public boolean expedited;
     public SyncStorageEngine.PendingOperation pendingOperation;
+    public Long backoff;
+    public long delayUntil;
+    public long effectiveRunTime;
 
     public SyncOperation(Account account, int source, String authority, Bundle extras,
-            long delayInMs) {
+            long delayInMs, long backoff, long delayUntil) {
         this.account = account;
         this.syncSource = source;
         this.authority = authority;
@@ -48,6 +51,8 @@
         removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
         removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
         removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+        this.delayUntil = delayUntil;
+        this.backoff = backoff;
         final long now = SystemClock.elapsedRealtime();
         if (delayInMs < 0) {
             this.expedited = true;
@@ -56,6 +61,7 @@
             this.expedited = false;
             this.earliestRunTime = now + delayInMs;
         }
+        updateEffectiveRunTime();
         this.key = toKey();
     }
 
@@ -72,49 +78,78 @@
         this.extras = new Bundle(other.extras);
         this.expedited = other.expedited;
         this.earliestRunTime = SystemClock.elapsedRealtime();
+        this.backoff = other.backoff;
+        this.delayUntil = other.delayUntil;
+        this.updateEffectiveRunTime();
         this.key = toKey();
     }
 
     public String toString() {
+        return dump(true);
+    }
+
+    public String dump(boolean useOneLine) {
         StringBuilder sb = new StringBuilder();
-        sb.append("authority: ").append(authority);
-        sb.append(" account: ").append(account);
-        sb.append(" extras: ");
-        extrasToStringBuilder(extras, sb, false /* asKey */);
-        sb.append(" syncSource: ").append(syncSource);
-        sb.append(" when: ").append(earliestRunTime);
-        sb.append(" expedited: ").append(expedited);
+        sb.append(account.name);
+        sb.append(" (" + account.type + ")");
+        sb.append(", " + authority);
+        sb.append(", ");
+        sb.append(SyncStorageEngine.SOURCES[syncSource]);
+        sb.append(", earliestRunTime " + earliestRunTime);
+        if (expedited) {
+            sb.append(", EXPEDITED");
+        }
+        if (!useOneLine && !extras.keySet().isEmpty()) {
+            sb.append("\n    ");
+            extrasToStringBuilder(extras, sb);
+        }
         return sb.toString();
     }
 
+    public boolean isInitialization() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+    }
+
+    public boolean ignoreBackoff() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
+    }
+
     private String toKey() {
         StringBuilder sb = new StringBuilder();
         sb.append("authority: ").append(authority);
-	sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
+    	sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
         sb.append(" extras: ");
-        extrasToStringBuilder(extras, sb, true /* asKey */);
+        extrasToStringBuilder(extras, sb);
         return sb.toString();
     }
 
-    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb, boolean asKey) {
+    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
         sb.append("[");
         for (String key : bundle.keySet()) {
-            // if we are writing this as a key don't consider whether this
-            // is an initialization sync or not when computing the key since
-            // we set this flag appropriately when dispatching the sync request.
-            if (asKey && ContentResolver.SYNC_EXTRAS_INITIALIZE.equals(key)) {
-                continue;
-            }
             sb.append(key).append("=").append(bundle.get(key)).append(" ");
         }
         sb.append("]");
     }
 
+    public void updateEffectiveRunTime() {
+        effectiveRunTime = ignoreBackoff()
+                ? earliestRunTime
+                : Math.max(
+                    Math.max(earliestRunTime, delayUntil),
+                    backoff);
+    }
+
     public int compareTo(Object o) {
         SyncOperation other = (SyncOperation)o;
-        if (earliestRunTime == other.earliestRunTime) {
+
+        if (expedited != other.expedited) {
+            return expedited ? -1 : 1;
+        }
+
+        if (effectiveRunTime == other.effectiveRunTime) {
             return 0;
         }
-        return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+
+        return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
     }
 }
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index 28baa0d..f826147 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -16,17 +16,18 @@
 
 package android.content;
 
-import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
+import android.os.SystemClock;
+import android.text.format.DateUtils;
 import android.util.Pair;
 import android.util.Log;
 import android.accounts.Account;
 
-import java.util.HashMap;
 import java.util.ArrayList;
-import java.util.Map;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 
 /**
  *
@@ -38,7 +39,7 @@
 
     // A Map of SyncOperations operationKey -> SyncOperation that is designed for
     // quick lookup of an enqueued SyncOperation.
-    private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
+    public final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
 
     public SyncQueue(SyncStorageEngine syncStorageEngine) {
         mSyncStorageEngine = syncStorageEngine;
@@ -47,8 +48,11 @@
         final int N = ops.size();
         for (int i=0; i<N; i++) {
             SyncStorageEngine.PendingOperation op = ops.get(i);
+            final Pair<Long, Long> backoff = syncStorageEngine.getBackoff(op.account, op.authority);
             SyncOperation syncOperation = new SyncOperation(
-                    op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
+                    op.account, op.syncSource, op.authority, op.extras, 0 /* delay */,
+                    backoff != null ? backoff.first : 0,
+                    syncStorageEngine.getDelayUntilTime(op.account, op.authority));
             syncOperation.expedited = op.expedited;
             syncOperation.pendingOperation = op;
             add(syncOperation, op);
@@ -119,65 +123,26 @@
         }
     }
 
-    /**
-     * Find the operation that should run next. Operations are sorted by their earliestRunTime,
-     * prioritizing first those with a syncable state of "unknown" that aren't retries then
-     * expedited operations.
-     * The earliestRunTime is adjusted by the sync adapter's backoff and delayUntil times, if any.
-     * @return the operation that should run next and when it should run. The time may be in
-     * the future. It is expressed in milliseconds since boot.
-     */
-    public Pair<SyncOperation, Long> nextOperation() {
-        SyncOperation best = null;
-        long bestRunTime = 0;
-        boolean bestSyncableIsUnknownAndNotARetry = false;
+    public void onBackoffChanged(Account account, String providerName, long backoff) {
+        // for each op that matches the account and provider update its
+        // backoff and effectiveStartTime
         for (SyncOperation op : mOperationsMap.values()) {
-            long opRunTime = op.earliestRunTime;
-            if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
-                Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
-                long delayUntil = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
-                opRunTime = Math.max(
-                        Math.max(opRunTime, delayUntil),
-                        backoff != null ? backoff.first : 0);
-            }
-            // we know a sync is a retry if the intialization flag is set, since that will only
-            // be set by the sync dispatching code, thus if it is set it must have already been
-            // dispatched
-            final boolean syncableIsUnknownAndNotARetry =
-                    !op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
-                    && mSyncStorageEngine.getIsSyncable(op.account, op.authority) < 0;
-            // if the unsyncable state differs, make the current the best if it is unsyncable
-            // else, if the expedited state differs, make the current the best if it is expedited
-            // else, make the current the best if it is earlier than the best
-            if (best == null
-                    || ((bestSyncableIsUnknownAndNotARetry == syncableIsUnknownAndNotARetry)
-                        ? (best.expedited == op.expedited
-                           ? opRunTime < bestRunTime
-                           : op.expedited)
-                        : syncableIsUnknownAndNotARetry)) {
-                best = op;
-                bestSyncableIsUnknownAndNotARetry = syncableIsUnknownAndNotARetry;
-                bestRunTime = opRunTime;
+            if (op.account.equals(account) && op.authority.equals(providerName)) {
+                op.backoff = backoff;
+                op.updateEffectiveRunTime();
             }
         }
-        if (best == null) {
-            return null;
-        }
-        return Pair.create(best, bestRunTime);
     }
 
-    /**
-     * Find and return the SyncOperation that should be run next and is ready to run.
-     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
-     * decide if the sync operation is ready to run
-     * @return the SyncOperation that should be run next and is ready to run.
-     */
-    public Pair<SyncOperation, Long> nextReadyToRun(long now) {
-        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
-        if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
-            return null;
+    public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
+        // for each op that matches the account and provider update its
+        // delayUntilTime and effectiveStartTime
+        for (SyncOperation op : mOperationsMap.values()) {
+            if (op.account.equals(account) && op.authority.equals(providerName)) {
+                op.delayUntil = delayUntil;
+                op.updateEffectiveRunTime();
+            }
         }
-        return nextOpAndRunTime;
     }
 
     public void remove(Account account, String authority) {
@@ -200,9 +165,17 @@
     }
 
     public void dump(StringBuilder sb) {
+        final long now = SystemClock.elapsedRealtime();
         sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
         for (SyncOperation operation : mOperationsMap.values()) {
-            sb.append(operation).append("\n");
+            sb.append("  ");
+            if (operation.effectiveRunTime <= now) {
+                sb.append("READY");
+            } else {
+                sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
+            }
+            sb.append(" - ");
+            sb.append(operation.dump(false)).append("\n");
         }
     }
 }
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 6413cec..487f6ce 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -229,7 +229,7 @@
     private final ArrayList<PendingOperation> mPendingOperations =
             new ArrayList<PendingOperation>();
 
-    private SyncInfo mCurrentSync;
+    private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>();
 
     private final SparseArray<SyncStatusInfo> mSyncStatus =
             new SparseArray<SyncStatusInfo>();
@@ -690,23 +690,12 @@
 
     /**
      * Returns true if there is currently a sync operation for the given
-     * account or authority in the pending list, or actively being processed.
+     * account or authority actively being processed.
      */
     public boolean isSyncActive(Account account, String authority) {
         synchronized (mAuthorities) {
-            int i = mPendingOperations.size();
-            while (i > 0) {
-                i--;
-                // TODO(fredq): this probably shouldn't be considering
-                // pending operations.
-                PendingOperation op = mPendingOperations.get(i);
-                if (op.account.equals(account) && op.authority.equals(authority)) {
-                    return true;
-                }
-            }
-
-            if (mCurrentSync != null) {
-                AuthorityInfo ainfo = getAuthority(mCurrentSync.authorityId);
+            for (SyncInfo syncInfo : mCurrentSyncs) {
+                AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
                 if (ainfo != null && ainfo.account.equals(account)
                         && ainfo.authority.equals(authority)) {
                     return true;
@@ -887,40 +876,47 @@
     }
 
     /**
-     * Called when the currently active sync is changing (there can only be
-     * one at a time).  Either supply a valid ActiveSyncContext with information
-     * about the sync, or null to stop the currently active sync.
+     * Called when a sync is starting. Supply a valid ActiveSyncContext with information
+     * about the sync.
      */
-    public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+    public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+        final SyncInfo syncInfo;
         synchronized (mAuthorities) {
-            if (activeSyncContext != null) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "setActiveSync: account="
-                        + activeSyncContext.mSyncOperation.account
-                        + " auth=" + activeSyncContext.mSyncOperation.authority
-                        + " src=" + activeSyncContext.mSyncOperation.syncSource
-                        + " extras=" + activeSyncContext.mSyncOperation.extras);
-                }
-                if (mCurrentSync != null) {
-                    Log.w(TAG, "setActiveSync called with existing active sync!");
-                }
-                AuthorityInfo authority = getAuthorityLocked(
-                        activeSyncContext.mSyncOperation.account,
-                        activeSyncContext.mSyncOperation.authority,
-                        "setActiveSync");
-                if (authority == null) {
-                    return;
-                }
-                mCurrentSync = new SyncInfo(authority.ident,
-                        authority.account, authority.authority,
-                        activeSyncContext.mStartTime);
-            } else {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null");
-                mCurrentSync = null;
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "setActiveSync: account="
+                    + activeSyncContext.mSyncOperation.account
+                    + " auth=" + activeSyncContext.mSyncOperation.authority
+                    + " src=" + activeSyncContext.mSyncOperation.syncSource
+                    + " extras=" + activeSyncContext.mSyncOperation.extras);
             }
+            AuthorityInfo authority = getOrCreateAuthorityLocked(
+                    activeSyncContext.mSyncOperation.account,
+                    activeSyncContext.mSyncOperation.authority,
+                    -1 /* assign a new identifier if creating a new authority */,
+                    true /* write to storage if this results in a change */);
+            syncInfo = new SyncInfo(authority.ident,
+                    authority.account, authority.authority,
+                    activeSyncContext.mStartTime);
+            mCurrentSyncs.add(syncInfo);
         }
 
-        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
+        reportActiveChange();
+        return syncInfo;
+    }
+
+    /**
+     * Called to indicate that a previously active sync is no longer active.
+     */
+    public void removeActiveSync(SyncInfo syncInfo) {
+        synchronized (mAuthorities) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "removeActiveSync: account="
+                        + syncInfo.account + " auth=" + syncInfo.authority);
+            }
+            mCurrentSyncs.remove(syncInfo);
+        }
+
+        reportActiveChange();
     }
 
     /**
@@ -1095,10 +1091,26 @@
      * Return the currently active sync information, or null if there is no
      * active sync.  Note that the returned object is the real, live active
      * sync object, so be careful what you do with it.
+     * <p>
+     * Since multiple concurrent syncs are now supported you should use
+     * {@link #getCurrentSyncs()} to get the accurate list of current syncs.
+     * This method returns the first item from the list of current syncs
+     * or null if there are none.
+     * @deprecated use {@link #getCurrentSyncs()}
      */
     public SyncInfo getCurrentSync() {
         synchronized (mAuthorities) {
-            return mCurrentSync;
+            return !mCurrentSyncs.isEmpty() ? mCurrentSyncs.get(0) : null;
+        }
+    }
+
+    /**
+     * Return a list of the currently active syncs. Note that the returned items are the
+     * real, live active sync objects, so be careful what you do with it.
+     */
+    public List<SyncInfo> getCurrentSyncs() {
+        synchronized (mAuthorities) {
+            return new ArrayList<SyncInfo>(mCurrentSyncs);
         }
     }
 
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 e26f160..e688c86 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -149,6 +149,27 @@
      * {@link android.R.attr#finishOnCloseSystemDialogs} attribute.
      */
     public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 0x0100;
+    /** 
+     * Bit in {@link #flags} corresponding to an immersive activity
+     * that wishes not to be interrupted by notifications.
+     * Applications that hide the system notification bar with
+     * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN}
+     * may still be interrupted by high-priority notifications; for example, an
+     * incoming phone call may use
+     * {@link android.app.Notification#fullScreenIntent fullScreenIntent}
+     * to present a full-screen in-call activity to the user, pausing the
+     * current activity as a side-effect. An activity with
+     * {@link #FLAG_IMMERSIVE} set, however, will not be interrupted; the
+     * notification may be shown in some other way (such as a small floating
+     * "toast" window).
+     * {@see android.app.Notification#FLAG_HIGH_PRIORITY}
+     */
+    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.
@@ -159,6 +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_HARDWARE_ACCELERATED}
      */
     public int flags;
 
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b1ef0db..ba74d9b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -269,7 +269,6 @@
      * increased in size for extra large screens.  Corresponds to
      * {@link android.R.styleable#AndroidManifestSupportsScreens_xlargeScreens
      * android:xlargeScreens}.
-     * @hide
      */
     public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 1<<19;
     
@@ -312,7 +311,7 @@
      * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP},
      * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS},
      * {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
-     * {@link #FLAG_SUPPORTS_LARGE_SCREENS},
+     * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_SUPPORTS_XLARGE_SCREENS},
      * {@link #FLAG_RESIZEABLE_FOR_SCREENS},
      * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE}
      */
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 89839ce..6cbc9b5 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.BufferedInputStream;
 import java.io.File;
@@ -49,7 +46,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;
 
@@ -1254,7 +1250,8 @@
                 "<permission-group>", sa,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_name,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_label,
-                com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon, 0)) {
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon,
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup_logo)) {
             sa.recycle();
             mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
             return null;
@@ -1289,7 +1286,8 @@
                 "<permission>", sa,
                 com.android.internal.R.styleable.AndroidManifestPermission_name,
                 com.android.internal.R.styleable.AndroidManifestPermission_label,
-                com.android.internal.R.styleable.AndroidManifestPermission_icon, 0)) {
+                com.android.internal.R.styleable.AndroidManifestPermission_icon,
+                com.android.internal.R.styleable.AndroidManifestPermission_logo)) {
             sa.recycle();
             mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
             return null;
@@ -1342,7 +1340,8 @@
                 "<permission-tree>", sa,
                 com.android.internal.R.styleable.AndroidManifestPermissionTree_name,
                 com.android.internal.R.styleable.AndroidManifestPermissionTree_label,
-                com.android.internal.R.styleable.AndroidManifestPermissionTree_icon, 0)) {
+                com.android.internal.R.styleable.AndroidManifestPermissionTree_icon,
+                com.android.internal.R.styleable.AndroidManifestPermissionTree_logo)) {
             sa.recycle();
             mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
             return null;
@@ -1386,7 +1385,8 @@
             mParseInstrumentationArgs = new ParsePackageItemArgs(owner, outError,
                     com.android.internal.R.styleable.AndroidManifestInstrumentation_name,
                     com.android.internal.R.styleable.AndroidManifestInstrumentation_label,
-                    com.android.internal.R.styleable.AndroidManifestInstrumentation_icon, 0);
+                    com.android.internal.R.styleable.AndroidManifestInstrumentation_icon,
+                    com.android.internal.R.styleable.AndroidManifestInstrumentation_logo);
             mParseInstrumentationArgs.tag = "<instrumentation>";
         }
         
@@ -1496,10 +1496,18 @@
             ai.nonLocalizedLabel = v.coerceToString();
         }
 
+        int defaultTheme = 0;
+        if (owner.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+            // As of honeycomb, the default application theme is holographic.
+            defaultTheme = android.R.style.Theme_Holo;
+        }
+
         ai.icon = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
+        ai.logo = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestApplication_logo, 0);
         ai.theme = sa.getResourceId(
-                com.android.internal.R.styleable.AndroidManifestApplication_theme, 0);
+                com.android.internal.R.styleable.AndroidManifestApplication_theme, defaultTheme);
         ai.descriptionRes = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestApplication_description, 0);
 
@@ -1531,6 +1539,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)) {
@@ -1630,7 +1642,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;
@@ -1639,7 +1652,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;
@@ -1762,6 +1775,11 @@
             outInfo.nonLocalizedLabel = null;
         }
         
+        int logoVal = sa.getResourceId(logoRes, 0);
+        if (logoVal != 0) {
+            outInfo.logo = logoVal;
+        }
+
         TypedValue v = sa.peekValue(labelRes);
         if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
             outInfo.nonLocalizedLabel = v.coerceToString();
@@ -1774,7 +1792,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);
 
@@ -1782,7 +1801,8 @@
             mParseActivityArgs = new ParseComponentArgs(owner, outError,
                     com.android.internal.R.styleable.AndroidManifestActivity_name,
                     com.android.internal.R.styleable.AndroidManifestActivity_label,
-                    com.android.internal.R.styleable.AndroidManifestActivity_icon, 0,
+                    com.android.internal.R.styleable.AndroidManifestActivity_icon,
+                    com.android.internal.R.styleable.AndroidManifestActivity_logo,
                     mSeparateProcesses,
                     com.android.internal.R.styleable.AndroidManifestActivity_process,
                     com.android.internal.R.styleable.AndroidManifestActivity_description,
@@ -1878,7 +1898,19 @@
             a.info.flags |= ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
         }
 
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestActivity_immersive,
+                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);
@@ -1992,7 +2024,8 @@
             mParseActivityAliasArgs = new ParseComponentArgs(owner, outError,
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_name,
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_label,
-                    com.android.internal.R.styleable.AndroidManifestActivityAlias_icon, 0,
+                    com.android.internal.R.styleable.AndroidManifestActivityAlias_icon,
+                    com.android.internal.R.styleable.AndroidManifestActivityAlias_logo,
                     mSeparateProcesses,
                     0,
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_description,
@@ -2120,7 +2153,8 @@
             mParseProviderArgs = new ParseComponentArgs(owner, outError,
                     com.android.internal.R.styleable.AndroidManifestProvider_name,
                     com.android.internal.R.styleable.AndroidManifestProvider_label,
-                    com.android.internal.R.styleable.AndroidManifestProvider_icon, 0,
+                    com.android.internal.R.styleable.AndroidManifestProvider_icon,
+                    com.android.internal.R.styleable.AndroidManifestProvider_logo,
                     mSeparateProcesses,
                     com.android.internal.R.styleable.AndroidManifestProvider_process,
                     com.android.internal.R.styleable.AndroidManifestProvider_description,
@@ -2392,7 +2426,8 @@
             mParseServiceArgs = new ParseComponentArgs(owner, outError,
                     com.android.internal.R.styleable.AndroidManifestService_name,
                     com.android.internal.R.styleable.AndroidManifestService_label,
-                    com.android.internal.R.styleable.AndroidManifestService_icon, 0,
+                    com.android.internal.R.styleable.AndroidManifestService_icon,
+                    com.android.internal.R.styleable.AndroidManifestService_logo,
                     mSeparateProcesses,
                     com.android.internal.R.styleable.AndroidManifestService_process,
                     com.android.internal.R.styleable.AndroidManifestService_description,
@@ -2606,6 +2641,9 @@
         outInfo.icon = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);
         
+        outInfo.logo = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestIntentFilter_logo, 0);
+
         sa.recycle();
 
         int outerDepth = parser.getDepth();
@@ -2873,6 +2911,11 @@
                 outInfo.nonLocalizedLabel = null;
             }
             
+            int logoVal = args.sa.getResourceId(args.logoRes, 0);
+            if (logoVal != 0) {
+                outInfo.logo = logoVal;
+            }
+
             TypedValue v = args.sa.peekValue(args.labelRes);
             if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
                 outInfo.nonLocalizedLabel = v.coerceToString();
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index a37e4e8..01ae1da 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -25,8 +25,6 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.nio.channels.FileChannel;
 
 /**
  * File descriptor of an entry in the AssetManager.  This provides your own
@@ -51,7 +49,7 @@
      * @param startOffset The location within the file that the asset starts.
      * This must be 0 if length is UNKNOWN_LENGTH.
      * @param length The number of bytes of the asset, or
-     * {@link #UNKNOWN_LENGTH if it extends to the end of the file.
+     * {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
      */
     public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
             long length) {
@@ -125,13 +123,6 @@
     public void close() throws IOException {
         mFd.close();
     }
-    
-    /**
-     * Checks whether this file descriptor is for a memory file.
-     */
-    private boolean isMemoryFile() throws IOException {
-        return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
-    }
 
     /**
      * Create and return a new auto-close input stream for this asset.  This
@@ -142,12 +133,6 @@
      * should only call this once for a particular asset.
      */
     public FileInputStream createInputStream() throws IOException {
-        if (isMemoryFile()) {
-            if (mLength > Integer.MAX_VALUE) {
-                throw new IOException("File length too large for a memory file: " + mLength);
-            }
-            return new AutoCloseMemoryFileInputStream(mFd, (int)mLength);
-        }
         if (mLength < 0) {
             return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
         }
@@ -276,66 +261,6 @@
             super.reset();
         }
     }
-    
-    /**
-     * An input stream that reads from a MemoryFile and closes it when the stream is closed.
-     * This extends FileInputStream just because {@link #createInputStream} returns
-     * a FileInputStream. All the FileInputStream methods are
-     * overridden to use the MemoryFile instead.
-     */
-    private static class AutoCloseMemoryFileInputStream extends FileInputStream {
-        private ParcelFileDescriptor mParcelFd;
-        private MemoryFile mFile;
-        private InputStream mStream;
-
-        public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length)
-                throws IOException {
-            super(fd.getFileDescriptor());
-            mParcelFd = fd;
-            mFile = new MemoryFile(fd.getFileDescriptor(), length, "r");
-            mStream = mFile.getInputStream();
-        }
-
-        @Override
-        public int available() throws IOException {
-            return mStream.available();
-        }
-
-        @Override
-        public void close() throws IOException {
-            mParcelFd.close();  // must close ParcelFileDescriptor, not just the file descriptor,
-                                // since it could be a subclass of ParcelFileDescriptor.
-                                // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases
-                                // a content provider
-            mFile.close();      // to unmap the memory file from the address space.
-            mStream.close();    // doesn't actually do anything
-        }
-
-        @Override
-        public FileChannel getChannel() {
-            return null;
-        }
-
-        @Override
-        public int read() throws IOException {
-            return mStream.read();
-        }
-
-        @Override
-        public int read(byte[] buffer, int offset, int count) throws IOException {
-            return mStream.read(buffer, offset, count);
-        }
-
-        @Override
-        public int read(byte[] buffer) throws IOException {
-            return mStream.read(buffer);
-        }
-
-        @Override
-        public long skip(long count) throws IOException {
-            return mStream.skip(count);
-        }
-    }
 
     /**
      * An OutputStream you can create on a ParcelFileDescriptor, which will
@@ -422,15 +347,4 @@
         }
     };
 
-    /**
-     * Creates an AssetFileDescriptor from a memory file.
-     *
-     * @hide
-     */
-    public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile)
-            throws IOException {
-        ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor();
-        return new AssetFileDescriptor(fd, 0, memoryFile.length());
-    }
-
 }
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index d0ba590..406b091 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -18,6 +18,7 @@
 
 import android.content.pm.ApplicationInfo;
 import android.graphics.Canvas;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.util.DisplayMetrics;
@@ -363,6 +364,17 @@
         }
 
         /**
+         * Translate a Point in screen coordinates into the app window's coordinates.
+         */
+        public void translatePointInScreenToAppWindow(PointF point) {
+            final float scale = applicationInvertedScale;
+            if (scale != 1.0f) {
+                point.x *= scale;
+                point.y *= scale;
+            }
+        }
+
+        /**
          * Translate the location of the sub window.
          * @param params
          */
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 5a3dd41..2f110f0 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -62,7 +62,6 @@
     public static final int SCREENLAYOUT_SIZE_SMALL = 0x01;
     public static final int SCREENLAYOUT_SIZE_NORMAL = 0x02;
     public static final int SCREENLAYOUT_SIZE_LARGE = 0x03;
-    /** @hide */
     public static final int SCREENLAYOUT_SIZE_XLARGE = 0x04;
     
     public static final int SCREENLAYOUT_LONG_MASK = 0x30;
@@ -84,7 +83,7 @@
      * <p>The {@link #SCREENLAYOUT_SIZE_MASK} bits define the overall size
      * of the screen.  They may be one of
      * {@link #SCREENLAYOUT_SIZE_SMALL}, {@link #SCREENLAYOUT_SIZE_NORMAL},
-     * {@link #SCREENLAYOUT_SIZE_LARGE}.
+     * {@link #SCREENLAYOUT_SIZE_LARGE}, or {@link #SCREENLAYOUT_SIZE_XLARGE}.
      * 
      * <p>The {@link #SCREENLAYOUT_LONG_MASK} defines whether the screen
      * is wider/taller than normal.  They may be one of
@@ -267,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 a6513aa..9b23c1e 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);
             }
         }
     }
@@ -1519,7 +1559,7 @@
     /**
      * Parse a series of {@link android.R.styleable#Extra &lt;extra&gt;} tags from
      * an XML file.  You call this when you are at the parent tag of the
-     * extra tags, and it return once all of the child tags have been parsed.
+     * extra tags, and it will return once all of the child tags have been parsed.
      * This will call {@link #parseBundleExtra} for each extra tag encountered.
      * 
      * @param parser The parser from which to retrieve the extras.
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index a5e5e46..bfaeb82 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) {
@@ -522,6 +374,10 @@
         }
     }
 
+    public Uri getNotificationUri() {
+        return mNotifyUri;
+    }
+
     public boolean getWantsAllOnMoveCalls() {
         return false;
     }
@@ -535,36 +391,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 +453,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 +465,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 95fc1fd..70a7fb6 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -31,6 +31,7 @@
 import android.database.sqlite.SQLiteProgram;
 import android.database.sqlite.SQLiteStatement;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
 import android.util.Config;
 import android.util.Log;
@@ -50,6 +51,29 @@
     private static final boolean DEBUG = false;
     private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
 
+    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_PRAGMA = 7;
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_DDL = 8;
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_UNPREPARED = 9;
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_OTHER = 99;
+
     /**
      * Special function for writing an exception result at the header of
      * a parcel, to be used when returning an exception from a transaction.
@@ -191,6 +215,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.
@@ -656,14 +711,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();
     }
 
     /**
@@ -684,14 +733,36 @@
      * 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]);
-            }
+        prog.bindAllArgsAsStrings(selectionArgs);
+        return prog.simpleQueryForString();
+    }
+
+    /**
+     * Utility method to run the query on the db and return the blob value in the
+     * first column of the first row.
+     *
+     * @return A read-only file descriptor for a copy of the blob value.
+     */
+    public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteDatabase db,
+            String query, String[] selectionArgs) {
+        SQLiteStatement prog = db.compileStatement(query);
+        try {
+            return blobFileDescriptorForQuery(prog, selectionArgs);
+        } finally {
+            prog.close();
         }
-        String value = prog.simpleQueryForString();
-        return value;
+    }
+
+    /**
+     * Utility method to run the pre-compiled query and return the blob value in the
+     * first column of the first row.
+     *
+     * @return A read-only file descriptor for a copy of the blob value.
+     */
+    public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteStatement prog,
+            String[] selectionArgs) {
+        prog.bindAllArgsAsStrings(selectionArgs);
+        return prog.simpleQueryForBlobFileDescriptor();
     }
 
     /**
@@ -704,8 +775,8 @@
      */
     public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values,
             String column) {
-        final int index = cursor.getColumnIndexOrThrow(column);
-        if (!cursor.isNull(index)) {
+        final int index = cursor.getColumnIndex(column);
+        if (index != -1 && !cursor.isNull(index)) {
             values.put(column, cursor.getString(index));
         }
     }
@@ -720,8 +791,8 @@
      */
     public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values,
             String column) {
-        final int index = cursor.getColumnIndexOrThrow(column);
-        if (!cursor.isNull(index)) {
+        final int index = cursor.getColumnIndex(column);
+        if (index != -1 && !cursor.isNull(index)) {
             values.put(column, cursor.getLong(index));
         }
     }
@@ -736,8 +807,8 @@
      */
     public static void cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values,
             String column) {
-        final int index = cursor.getColumnIndexOrThrow(column);
-        if (!cursor.isNull(index)) {
+        final int index = cursor.getColumnIndex(column);
+        if (index != -1 && !cursor.isNull(index)) {
             values.put(column, cursor.getShort(index));
         }
     }
@@ -752,8 +823,8 @@
      */
     public static void cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values,
             String column) {
-        final int index = cursor.getColumnIndexOrThrow(column);
-        if (!cursor.isNull(index)) {
+        final int index = cursor.getColumnIndex(column);
+        if (index != -1 && !cursor.isNull(index)) {
             values.put(column, cursor.getInt(index));
         }
     }
@@ -768,8 +839,8 @@
      */
     public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values,
             String column) {
-        final int index = cursor.getColumnIndexOrThrow(column);
-        if (!cursor.isNull(index)) {
+        final int index = cursor.getColumnIndex(column);
+        if (index != -1 && !cursor.isNull(index)) {
             values.put(column, cursor.getFloat(index));
         }
     }
@@ -784,8 +855,8 @@
      */
     public static void cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values,
             String column) {
-        final int index = cursor.getColumnIndexOrThrow(column);
-        if (!cursor.isNull(index)) {
+        final int index = cursor.getColumnIndex(column);
+        if (index != -1 && !cursor.isNull(index)) {
             values.put(column, cursor.getDouble(index));
         }
     }
@@ -1152,4 +1223,66 @@
         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;
+        } else if (prefixSql.equals("PRA")) {
+            return STATEMENT_PRAGMA;
+        } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") ||
+                prefixSql.equals("ALT")) {
+            return STATEMENT_DDL;
+        } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) {
+            return STATEMENT_UNPREPARED;
+        }
+        return STATEMENT_OTHER;
+    }
+
+    /**
+     * Appends one set of selection args to another. This is useful when adding a selection
+     * argument to a user provided set.
+     */
+    public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
+        if (originalValues == null || originalValues.length == 0) {
+            return newValues;
+        }
+        String[] result = new String[originalValues.length + newValues.length ];
+        System.arraycopy(originalValues, 0, result, 0, originalValues.length);
+        System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
+        return result;
+    }
 }
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..39a9d23
--- /dev/null
+++ b/core/java/android/database/sqlite/DatabaseConnectionPool.java
@@ -0,0 +1,348 @@
+/*
+ * 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.content.res.Resources;
+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. */
+    private volatile int mMaxPoolSize =
+        Resources.getSystem().getInteger(com.android.internal.R.integer.db_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 */ synchronized void close() {
+        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 */ synchronized SQLiteDatabase get(String sql) {
+        SQLiteDatabase db = null;
+        PoolObj poolObj = null;
+        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.isInStatementCache(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.isInStatementCache(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 */ synchronized void release(SQLiteDatabase db) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            assert db.mConnectionNum > 0;
+            doAsserts();
+            assert mPool.get(db.mConnectionNum - 1).mDb == db;
+        }
+
+        PoolObj 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 */ synchronized ArrayList<SQLiteDatabase> getConnectionList() {
+        ArrayList<SQLiteDatabase> list = new ArrayList<SQLiteDatabase>();
+        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);
+        }
+    }
+
+    /** only used for testing purposes. */
+    /* package */ synchronized void setMaxPoolSize(int size) {
+        mMaxPoolSize = size;
+    }
+
+    /** only used for testing purposes. */
+    /* package */ synchronized int getMaxPoolSize() {
+        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/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 1830f6c..96e6f22 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -23,13 +23,14 @@
  */
 public abstract class SQLiteClosable {
     private int mReferenceCount = 1;
-    private Object mLock = new Object();
+    private Object mLock = new Object(); // STOPSHIP remove this line
 
     protected abstract void onAllReferencesReleased();
     protected void onAllReferencesReleasedFromContainer() {}
 
     public void acquireReference() {
-        synchronized(mLock) {
+        synchronized(mLock) { // STOPSHIP change 'mLock' to 'this'
+            checkRefCount();
             if (mReferenceCount <= 0) {
                 throw new IllegalStateException(
                         "attempt to re-open an already-closed object: " + getObjInfo());
@@ -39,7 +40,8 @@
     }
 
     public void releaseReference() {
-        synchronized(mLock) {
+        synchronized(mLock) { // STOPSHIP change 'mLock' to 'this'
+            checkRefCount();
             mReferenceCount--;
             if (mReferenceCount == 0) {
                 onAllReferencesReleased();
@@ -48,7 +50,8 @@
     }
 
     public void releaseReferenceFromContainer() {
-        synchronized(mLock) {
+        synchronized(mLock) { // STOPSHIP change 'mLock' to 'this'
+            checkRefCount();
             mReferenceCount--;
             if (mReferenceCount == 0) {
                 onAllReferencesReleasedFromContainer();
@@ -63,8 +66,7 @@
         if (this instanceof SQLiteDatabase) {
             buff.append("database = ");
             buff.append(((SQLiteDatabase)this).getPath());
-        } else if (this instanceof SQLiteProgram || this instanceof SQLiteStatement ||
-                this instanceof SQLiteQuery) {
+        } else if (this instanceof SQLiteProgram) {
             buff.append("mSql = ");
             buff.append(((SQLiteProgram)this).mSql);
         } else if (this instanceof CursorWindow) {
@@ -74,4 +76,12 @@
         buff.append(") ");
         return buff.toString();
     }
+
+    // STOPSHIP remove this method before shipping
+    private void checkRefCount() {
+        if (mReferenceCount > 1000) {
+            throw new IllegalStateException("bad refcount: " + mReferenceCount +
+                    ". file bug against frameworks->database" + getObjInfo());
+        }
+    }
 }
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index 003d0f2..a7ad757 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -32,12 +32,12 @@
     private static final String TAG = "SQLiteCompiledSql";
 
     /** The database this program is compiled against. */
-    /* package */ SQLiteDatabase mDatabase;
+    /* package */ final SQLiteDatabase mDatabase;
 
     /**
      * Native linkage, do not modify. This comes from the database.
      */
-    /* package */ int nHandle = 0;
+    /* package */ final int nHandle;
 
     /**
      * Native linkage, do not modify. When non-0 this holds a reference to a valid
@@ -55,61 +55,21 @@
     private boolean mInUse = false;
 
     /* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
-        if (!db.isOpen()) {
-            throw new IllegalStateException("database " + db.getPath() + " already closed");
-        }
+        db.verifyDbIsOpen();
+        db.verifyLockOwner();
         mDatabase = db;
         mSqlStmt = sql;
         mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
-        this.nHandle = db.mNativeHandle;
-        compile(sql, true);
-    }
-
-    /**
-     * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
-     * this method has been called previously without a call to close and forCompilation is set
-     * to false the previous compilation will be used. Setting forceCompilation to true will
-     * always re-compile the program and should be done if you pass differing SQL strings to this
-     * method.
-     *
-     * <P>Note: this method acquires the database lock.</P>
-     *
-     * @param sql the SQL string to compile
-     * @param forceCompilation forces the SQL to be recompiled in the event that there is an
-     *  existing compiled SQL program already around
-     */
-    private void compile(String sql, boolean forceCompilation) {
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        // 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();
-            }
-        }
+        nHandle = db.mNativeHandle;
+        native_compile(sql);
     }
 
     /* package */ void releaseSqlStatement() {
         // Note that native_finalize() checks to make sure that nStatement is
         // non-null before destroying it.
         if (nStatement != 0) {
-            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;
         }
     }
 
@@ -118,23 +78,24 @@
      */
     /* package */ synchronized boolean acquire() {
         if (mInUse) {
-            // someone already has acquired it.
+            // it is already in use.
             return false;
         }
         mInUse = true;
-        if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
-            Log.v(TAG, "Acquired DbObj (id#" + nStatement + ") from DB cache");
-        }
         return true;
     }
 
     /* package */ synchronized void release() {
-        if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
-            Log.v(TAG, "Released DbObj (id#" + nStatement + ") back to DB cache");
-        }
         mInUse = false;
     }
 
+    /* package */ synchronized void releaseIfNotInUse() {
+        // if it is not in use, release its memory from the database
+        if (!mInUse) {
+            releaseSqlStatement();
+        }
+    }
+
     /**
      * Make sure that the native resource is cleaned up.
      */
@@ -143,10 +104,10 @@
         try {
             if (nStatement == 0) return;
             // finalizer should NEVER get called
-            if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
-                Log.v(TAG, "** warning ** Finalized DbObj (id#" + nStatement + ")");
-            }
-            if (StrictMode.vmSqliteObjectLeaksEnabled()) {
+            // but if the database itself is not closed and is GC'ed, then
+            // all sub-objects attached to the database could end up getting GC'ed too.
+            // in that case, don't print any warning.
+            if (mInUse && StrictMode.vmSqliteObjectLeaksEnabled()) {
                 int len = mSqlStmt.length();
                 StrictMode.onSqliteObjectLeaked(
                     "Releasing statement in a finalizer. Please ensure " +
@@ -160,6 +121,24 @@
         }
     }
 
+    @Override public String toString() {
+        synchronized(this) {
+            StringBuilder buff = new StringBuilder();
+            buff.append(" nStatement=");
+            buff.append(nStatement);
+            buff.append(", mInUse=");
+            buff.append(mInUse);
+            buff.append(", db=");
+            buff.append(mDatabase.getPath());
+            buff.append(", db_connectionNum=");
+            buff.append(mDatabase.mConnectionNum);
+            buff.append(", sql=");
+            int len = mSqlStmt.length();
+            buff.append(mSqlStmt.substring(0, (len > 100) ? 100 : len));
+            return buff.toString();
+        }
+    }
+
     /**
      * Compiles SQL into a SQLite program.
      *
@@ -167,5 +146,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/SQLiteContentHelper.java b/core/java/android/database/sqlite/SQLiteContentHelper.java
deleted file mode 100644
index 2800d86..0000000
--- a/core/java/android/database/sqlite/SQLiteContentHelper.java
+++ /dev/null
@@ -1,92 +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.database.sqlite;
-
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.os.MemoryFile;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-/**
- * Some helper functions for using SQLite database to implement content providers.
- *
- * @hide
- */
-public class SQLiteContentHelper {
-
-    /**
-     * Runs an SQLite query and returns an AssetFileDescriptor for the
-     * blob in column 0 of the first row. If the first column does
-     * not contain a blob, an unspecified exception is thrown.
-     *
-     * @param db Handle to a readable database.
-     * @param sql SQL query, possibly with query arguments.
-     * @param selectionArgs Query argument values, or {@code null} for no argument.
-     * @return If no exception is thrown, a non-null AssetFileDescriptor is returned.
-     * @throws FileNotFoundException If the query returns no results or the
-     *         value of column 0 is NULL, or if there is an error creating the
-     *         asset file descriptor.
-     */
-    public static AssetFileDescriptor getBlobColumnAsAssetFile(SQLiteDatabase db, String sql,
-            String[] selectionArgs) throws FileNotFoundException {
-        try {
-            MemoryFile file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs);
-            if (file == null) {
-                throw new FileNotFoundException("No results.");
-            }
-            return AssetFileDescriptor.fromMemoryFile(file);
-        } catch (IOException ex) {
-            throw new FileNotFoundException(ex.toString());
-        }
-    }
-
-    /**
-     * Runs an SQLite query and returns a MemoryFile for the
-     * blob in column 0 of the first row. If the first column does
-     * not contain a blob, an unspecified exception is thrown.
-     *
-     * @return A memory file, or {@code null} if the query returns no results
-     *         or the value column 0 is NULL.
-     * @throws IOException If there is an error creating the memory file.
-     */
-    // TODO: make this native and use the SQLite blob API to reduce copying
-    private static MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql,
-            String[] selectionArgs) throws IOException {
-        Cursor cursor = db.rawQuery(sql, selectionArgs);
-        if (cursor == null) {
-            return null;
-        }
-        try {
-            if (!cursor.moveToFirst()) {
-                return null;
-            }
-            byte[] bytes = cursor.getBlob(0);
-            if (bytes == null) {
-                return null;
-            }
-            MemoryFile file = new MemoryFile(null, bytes.length);
-            file.writeBytes(bytes, 0, 0, bytes.length);
-            file.deactivate();
-            return file;
-        } finally {
-            cursor.close();
-        }
-    }
-
-}
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index d8dcaf7..89e8ab7 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -16,21 +16,20 @@
 
 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.os.StrictMode;
-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;
 
@@ -42,32 +41,29 @@
  * threads should perform its own synchronization when using the SQLiteCursor.
  */
 public class SQLiteCursor extends AbstractWindowedCursor {
-    static final String TAG = "Cursor";
+    static final String TAG = "SQLiteCursor";
     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;
+    private volatile int mCount = NO_COUNT;
 
     /** A mapping of column names to column indices, to speed up lookups */
     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 
@@ -78,6 +74,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
@@ -137,14 +138,22 @@
                     break;
                 }
                 try {
-                    int count = mQuery.fillWindow(cw, mMaxRead, mCount);
-                    // return -1 means not finished
+                    int count = getQuery().fillWindow(cw, mMaxRead, mCount);
+                    // return -1 means there is still more data to be retrieved from the resultset
                     if (count != 0) {
                         if (count == NO_COUNT){
                             mCount += mMaxRead;
+                            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                                Log.d(TAG, "received -1 from native_fill_window. read " +
+                                        mCount + " rows so far");
+                            }
                             sendMessage();
                         } else {                                
-                            mCount = count;
+                            mCount += count;
+                            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                                Log.d(TAG, "received all data from native_fill_window. read " +
+                                        mCount + " rows.");
+                            }
                             sendMessage();
                             break;
                         }
@@ -202,24 +211,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();
@@ -240,7 +271,7 @@
                 }
             }
         } finally {
-            db.unlock();
+            query.mDatabase.unlock();
         }
     }
 
@@ -248,7 +279,9 @@
      * @return the SQLiteDatabase that this cursor is associated with.
      */
     public SQLiteDatabase getDatabase() {
-        return mDatabase;
+        synchronized (this) {
+            return mQuery.mDatabase;
+        }
     }
 
     @Override
@@ -284,13 +317,27 @@
                 }
         }
         mWindow.setStartPosition(startPos);
-        mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
-        // return -1 means not finished
-        if (mCount == NO_COUNT){
+        int count = getQuery().fillWindow(mWindow, mInitialRead, 0);
+        // return -1 means there is still more data to be retrieved from the resultset
+        if (count == NO_COUNT){
             mCount = startPos + mInitialRead;
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "received -1 from native_fill_window. read " + mCount + " rows so far");
+            }
             Thread t = new Thread(new QueryThread(mCursorState), "query thread");
             t.start();
-        } 
+        } else if (startPos == 0) { // native_fill_window returns count(*) only for startPos = 0
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "received count(*) from native_fill_window: " + count);
+            }
+            mCount = count;
+        } else if (mCount <= 0) {
+            throw new IllegalStateException("count should never be non-zero negative number");
+        }
+    }
+
+    private synchronized SQLiteQuery getQuery() {
+        return mQuery;
     }
 
     @Override
@@ -322,166 +369,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;
@@ -502,9 +394,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
@@ -512,20 +427,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;
@@ -536,8 +461,6 @@
             } finally {
                 queryThreadUnlock();
             }
-        } finally {
-            mDatabase.unlock();
         }
 
         if (Config.LOGV) {
@@ -587,7 +510,7 @@
                     int len = mQuery.mSql.length();
                     StrictMode.onSqliteObjectLeaked(
                         "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);
                 }
@@ -595,7 +518,7 @@
                 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);
                 }
             }
@@ -603,4 +526,11 @@
             super.finalize();
         }
     }
+
+    /**
+     * this is only for testing purposes.
+     */
+    /* package */ int getMCount() {
+        return mCount;
+    }
 }
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..a98a305 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,13 @@
 
 import java.io.File;
 import java.lang.ref.WeakReference;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
-import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Pattern;
@@ -67,7 +65,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 +190,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,94 +236,138 @@
     // 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;
+    // guarded by itself
+    /* 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.
+                eldest.getValue().releaseIfNotInUse();
+                // 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
-    private int mCacheFullWarnings;
-    private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1;
+    public static final int MAX_SQL_CACHE_SIZE = 100;
+    private boolean mCacheFullWarning;
 
-    /** maintain stats about number of cache hits and misses */
+    /** Number of cache hits on this database connection. guarded by {@link #mCompiledQueries}. */
     private int mNumCacheHits;
+    /** Number of cache misses on this database connection. guarded by {@link #mCompiledQueries}. */
     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:";
+
+    /** set to true if the database has attached databases */
+    private volatile boolean mHasAttachedDbs = false;
+
+    /** 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 +397,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,7 +410,16 @@
      * @see #unlock()
      */
     /* package */ void lock() {
-        if (!mLockingEnabled) return;
+        lock(false);
+    }
+    private void lock(boolean forced) {
+        // make sure this method is NOT being called from a 'synchronized' method
+        if (Thread.holdsLock(this)) {
+            // STOPSHIP change the following line to Log.w()
+            throw new IllegalStateException("don't lock() while in a synchronized method");
+        }
+        verifyDbIsOpen();
+        if (!forced && !mLockingEnabled) return;
         mLock.lock();
         if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
             if (mLock.getHoldCount() == 1) {
@@ -384,7 +429,6 @@
             }
         }
     }
-
     /**
      * Locks the database for exclusive access. The database lock must be held when
      * touch the native sqlite3* object since it is single threaded and uses
@@ -394,14 +438,7 @@
      * @see #unlockForced()
      */
     private void lockForced() {
-        mLock.lock();
-        if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
-            if (mLock.getHoldCount() == 1) {
-                // Use elapsed real-time since the CPU may sleep when waiting for IO
-                mLockAcquiredWallTime = SystemClock.elapsedRealtime();
-                mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
-            }
-        }
+        lock(true);
     }
 
     /**
@@ -460,11 +497,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 +518,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 +565,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 +624,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 +655,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 +680,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 +725,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 +740,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 +887,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);
     }
 
     /**
@@ -791,8 +901,7 @@
     public interface CursorFactory {
         /**
          * See
-         * {@link SQLiteCursor#SQLiteCursor(SQLiteDatabase, SQLiteCursorDriver,
-         * String, SQLiteQuery)}.
+         * {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}.
          */
         public Cursor newCursor(SQLiteDatabase db,
                 SQLiteCursorDriver masterQuery, String editTable,
@@ -814,30 +923,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 +1014,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 +1045,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);
     }
 
     /**
@@ -875,20 +1053,38 @@
      */
     public void close() {
         if (!isOpen()) {
-            return; // already closed
+            return;
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.i(TAG, "closing db: " + mPath + " (connection # " + mConnectionNum);
         }
         lock();
         try {
+            // some other thread could have closed this database while I was waiting for lock.
+            // check the database state
+            if (!isOpen()) {
+                return;
+            }
             closeClosable();
+            // finalize ALL statements queued up so far
+            closePendingStatements();
+            releaseCustomFunctions();
             // close this database instance - regardless of its reference count value
-            onAllReferencesReleased();
+            closeDatabase();
+            if (mConnectionPool != null) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    assert mConnectionPool != null;
+                    Log.i(TAG, mConnectionPool.toString());
+                }
+                mConnectionPool.close();
+            }
         } finally {
-            unlock();
+            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
@@ -908,29 +1104,106 @@
     }
 
     /**
+     * package level access for testing purposes
+     */
+    /* package */ void closeDatabase() throws SQLiteException {
+        try {
+            dbclose();
+        } catch (SQLiteUnfinalizedObjectsException e)  {
+            String msg = e.getMessage();
+            String[] tokens = msg.split(",", 2);
+            int stmtId = Integer.parseInt(tokens[0]);
+            // get extra info about this statement, if it is still to be released by closeClosable()
+            Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator();
+            boolean found = false;
+            while (iter.hasNext()) {
+                Map.Entry<SQLiteClosable, Object> entry = iter.next();
+                SQLiteClosable program = entry.getKey();
+                if (program != null && program instanceof SQLiteProgram) {
+                        SQLiteCompiledSql compiledSql = ((SQLiteProgram)program).mCompiledSql;
+                        if (compiledSql.nStatement == stmtId) {
+                            msg = compiledSql.toString();
+                            found = true;
+                        }
+                }
+            }
+            if (!found) {
+                // the statement is already released by closeClosable(). is it waiting to be
+                // finalized?
+                if (mClosedStatementIds.contains(stmtId)) {
+                    Log.w(TAG, "this shouldn't happen. finalizing the statement now: ");
+                    closePendingStatements();
+                    // try to close the database again
+                    closeDatabase();
+                }
+            } else {
+                // the statement is not yet closed. most probably programming error in the app.
+                Log.w(TAG, "dbclose failed due to un-close()d SQL statements: " + msg);
+                throw e;
+            }
+        }
+    }
+
+    /**
      * Native call to close the database.
      */
     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 +1221,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 +1233,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 +1250,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 +1271,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 +1287,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 +1322,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 +1331,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 +1409,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 +1520,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 +1528,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 +1555,7 @@
                                   : "<null>")  + ", count is " + count);
                 }
             }
+            releaseDbConnection(db);
         }
         return cursor;
     }
@@ -1501,84 +1682,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 +1731,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 +1770,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 +1781,71 @@
         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);
 
+        if (stmtType == DatabaseUtils.STATEMENT_ATTACH) {
+            mHasAttachedDbs = true;
+        }
         // 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 +1853,100 @@
     }
 
     /**
-     * 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 int executeSql(String sql, Object[] bindArgs) throws SQLException {
         long timeStart = SystemClock.uptimeMillis();
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-        SQLiteStatement statement = null;
+        int n;
+        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();
+            n = statement.executeUpdateDelete();
         } catch (SQLiteDatabaseCorruptException e) {
             onCorruption();
             throw e;
         } finally {
-            if (statement != null) {
-                statement.close();
-            }
-            unlock();
+            statement.close();
         }
         logTimeStat(sql, timeStart);
+        return n;
     }
 
     @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 +1955,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 +2016,7 @@
         }
         if (durationMillis >= sQueryLogTimeInMillis) {
             samplePercent = 100;
-        } else {;
+        } else {
             samplePercent = (int) (100 * durationMillis / sQueryLogTimeInMillis) + 1;
             if (mRandom.nextInt(100) >= samplePercent) return;
         }
@@ -1970,66 +2085,55 @@
         }
     }
 
-    /*
-     * ============================================================================
-     *
-     *       The following methods deal with compiled-sql cache
-     * ============================================================================
-     */
+    /* 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!");
+        }
+    }
+
     /**
-     * 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
-            compiledSql = mCompiledQueries.get(sql);
-            if (compiledSql != null) {
+            if (mCompiledQueries.containsKey(sql)) {
                 return;
             }
-            // add this <sql, compiledStatement> to the cache
-            if (mCompiledQueries.size() == mMaxSqlCacheSize) {
+
+            if (!mCacheFullWarning && 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");
-                }
-                // 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);
-                }
-            }
+                Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
+                        getPath() + ". Use setMaxSqlCacheSize() to increase cachesize. ");
+                mCacheFullWarning = true;
+            } 
+            /* 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);
         }
-        return;
     }
 
-
-    private void deallocCachedSqlStatements() {
+    /** package-level access for testing purposes */
+    /* package */ void deallocCachedSqlStatements() {
         synchronized (mCompiledQueries) {
             for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) {
                 compiledSql.releaseSqlStatement();
@@ -2039,199 +2143,464 @@
     }
 
     /**
-     * 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());
-                }
+        synchronized (mCompiledQueries) {
+            SQLiteCompiledSql compiledStatement = mCompiledQueries.get(sql);
+            if (compiledStatement == null) {
+                mNumCacheMisses++;
                 return null;
             }
-            cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null;
-        }
-        if (cacheHit) {
             mNumCacheHits++;
-        } else {
-            mNumCacheMisses++;
+            return compiledStatement;
         }
-
-        if (SQLiteDebug.DEBUG_SQL_CACHE) {
-            Log.v(TAG, "|cache_stats|" +
-                    getPath() + "|" + mCompiledQueries.size() +
-                    "|" + mNumCacheHits + "|" + mNumCacheMisses +
-                    "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql);
-        }
-        return compiledStatement;
     }
 
     /**
-     * returns true if the given sql is cached in compiled-sql cache.
-     * @hide
+     * 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.
+     *
+     * @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 boolean isInCompiledSqlCache(String sql) {
+    public void setMaxSqlCacheSize(int cacheSize) {
         synchronized(mCompiledQueries) {
+            if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
+                throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE);
+            } else if (cacheSize < mMaxSqlCacheSize) {
+                throw new IllegalStateException("cannot set cacheSize to a value less than the value " +
+                        "set with previous setMaxSqlCacheSize() call.");
+            }
+            mMaxSqlCacheSize = cacheSize;
+        }
+    }
+
+    /* package */ boolean isInStatementCache(String sql) {
+        synchronized (mCompiledQueries) {
             return mCompiledQueries.containsKey(sql);
         }
     }
 
-    /**
-     * purges the given sql from the compiled-sql cache.
-     * @hide
-     */
-    public void purgeFromCompiledSqlCache(String sql) {
+    /* package */ void releaseCompiledSqlObj(SQLiteCompiledSql compiledSql) {
+        synchronized (mCompiledQueries) {
+            if (mCompiledQueries.containsValue(compiledSql)) {
+                // it is in cache - reset its inUse flag
+                compiledSql.release();
+            } else {
+                // it is NOT in cache. finalize it.
+                compiledSql.releaseSqlStatement();
+            }
+        }
+    }
+
+    private int getCacheHitNum() {
         synchronized(mCompiledQueries) {
-            mCompiledQueries.remove(sql);
+            return mNumCacheHits;
         }
     }
 
-    /**
-     * remove everything from the compiled sql cache
-     * @hide
-     */
-    public void resetCompiledSqlCache() {
+    private int getCacheMissNum() {
         synchronized(mCompiledQueries) {
-            mCompiledQueries.clear();
+            return mNumCacheMisses;
+        }
+    }
+
+    private int getCachesize() {
+        synchronized(mCompiledQueries) {
+            return mCompiledQueries.size();
+        }
+    }
+
+    /* 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));
         }
     }
 
     /**
-     * return the current maxCacheSqlCacheSize
-     * @hide
+     * for testing only
      */
-    public synchronized int getMaxSqlCacheSize() {
-        return mMaxSqlCacheSize;
+    /* package */ ArrayList<Integer> getQueuedUpStmtList() {
+        return mClosedStatementIds;
     }
 
     /**
-     * set the max size of the compiled sql cache for this database after purging the cache.
-     * (size of the cache = number of compiled-sql-statements stored in the cache).
+     * 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>
      *
-     * 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
+     * @return true if write-ahead-logging is set. false otherwise
      */
-    public synchronized void setMaxSqlCacheSize(int cacheSize) {
-        if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
-            throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE);
-        } else if (cacheSize < mMaxSqlCacheSize) {
-            throw new IllegalStateException("cannot set cacheSize to a value less than the value " +
-                    "set with previous setMaxSqlCacheSize() call.");
+    public boolean enableWriteAheadLogging() {
+        // acquire lock - no that no other thread is enabling WAL at the same time
+        lock();
+        try {
+            if (mConnectionPool != null) {
+                // already enabled
+                return true;
+            }
+            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 (mHasAttachedDbs) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG,
+                            "this database: " + mPath + " has attached databases. can't  enable WAL.");
+                }
+                return false;
+            }
+            mConnectionPool = new DatabaseConnectionPool(this);
+            setJournalMode(mPath, "WAL");
+            return true;
+        } finally {
+            unlock();
         }
-        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;}
+    /**
+     * This method disables the features enabled by {@link #enableWriteAheadLogging()}.
+     * @hide
+     */
+    public void disableWriteAheadLogging() {
+        // grab database lock so that writeAheadLogging is not disabled from 2 different threads
+        // at the same time
+        lock();
+        try {
+            if (mConnectionPool == null) {
+                return; // already disabled
+            }
+            mConnectionPool.close();
+            setJournalMode(mPath, "TRUNCATE");
+            mConnectionPool = null;
+        } finally {
+            unlock();
+        }
+    }
+
+    /* 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);
+        }
+    }
+
+    /* 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.
+        // the only time when it is called with pooled database connection handle is
+        // corruption occurs while trying to open a pooled database connection handle.
+        // in that case, simply return 'this' handle
+        if (isPooledConnection()) {
+            return this;
+        }
+
+        // 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;
+        synchronized(mActiveDatabases) {
+            tempList = (ArrayList<WeakReference<SQLiteDatabase>>)mActiveDatabases.clone();
+        }
+        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);
+            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;
+                }
+                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.getCacheHitNum(), db.getCacheMissNum(),
+                                db.getCachesize()));
                     }
                 }
-                if (pageCount > 0) {
-                    dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
-                            lookasideUsed));
+                // if there are pooled connections, return the cache stats for them also.
+                // while we are trying to query the pooled connections for stats, some other thread
+                // could be disabling conneciton pool. so, grab a reference to the connection pool.
+                DatabaseConnectionPool connPool = db.mConnectionPool;
+                if (connPool != null) {
+                    for (SQLiteDatabase pDb : connPool.getConnectionList()) {
+                        dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
+                                + lastnode, 0, 0, 0, pDb.getCacheHitNum(),
+                                pDb.getCacheMissNum(), pDb.getCachesize()));
+                    }
                 }
+            } catch (SQLiteException e) {
+                // ignore. we don't care about exceptions when we are taking adb
+                // bugreport!
             }
         }
         return dbStatsList;
     }
 
     /**
-     * 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)));
+        if (!mHasAttachedDbs) {
+            // No attached databases.
+            // There is a small window where attached databases exist but this flag is not set yet.
+            // This can occur when this thread is in a race condition with another thread
+            // that is executing the SQL statement: "attach database <blah> as <foo>"
+            // If this thread is NOT ok with such a race condition (and thus possibly not receive
+            // the entire list of attached databases), then the caller should ensure that no thread
+            // is executing any SQL statements while a thread is calling this method.
+            // Typically, this method is called when 'adb bugreport' is done or the caller wants to
+            // collect stats on the database and all its attached databases.
+            attachedDbs.add(new Pair<String, String>("main", mPath));
+            return attachedDbs;
         }
-        c.close();
+        // has attached databases. query sqlite to get the list of attached databases.
+        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();
+            }
+        }
         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 = null;
+        try {
+            attachedDbs = getAttachedDbs();
+            if (attachedDbs == null) {
+                throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " +
+                        "be retrieved. probably because the database is closed");
+            }
+        } catch (SQLiteException e) {
+            // can't get attachedDb list. do integrity check on the main database
+            attachedDbs = new ArrayList<Pair<String, String>>();
+            attachedDbs.add(new Pair<String, String>("main", this.mPath));
+        }
+        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
+                    Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt);
+                    return false;
+                }
+            } finally {
+                if (prog != null) prog.close();
+            }
+        }
+        return true;
+    }
+
+    /**
      * Native call to open the database.
      *
      * @param path The full path to the database
@@ -2239,51 +2608,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 +2643,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 b2a166b..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 / 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/SQLiteMisuseException.java b/core/java/android/database/sqlite/SQLiteMisuseException.java
index 685f3ea..546ec08 100644
--- a/core/java/android/database/sqlite/SQLiteMisuseException.java
+++ b/core/java/android/database/sqlite/SQLiteMisuseException.java
@@ -16,6 +16,18 @@
 
 package android.database.sqlite;
 
+/**
+ * This error can occur if the application creates a SQLiteStatement object and allows multiple
+ * threads in the application use it at the same time.
+ * Sqlite returns this error if bind and execute methods on this object occur at the same time
+ * from multiple threads, like so:
+ *     thread # 1: in execute() method of the SQLiteStatement object
+ *     while thread # 2: is in bind..() on the same object.
+ *</p>
+ * FIX this by NEVER sharing the same SQLiteStatement object between threads.
+ * Create a local instance of the SQLiteStatement whenever it is needed, use it and close it ASAP.
+ * NEVER make it globally available.
+ */
 public class SQLiteMisuseException extends SQLiteException {
     public SQLiteMisuseException() {}
 
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index b4615eb..ccf8d68 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;
     }
 
     /**
@@ -93,8 +121,13 @@
      * @return a read/write database object valid until {@link #close} is called
      */
     public synchronized SQLiteDatabase getWritableDatabase() {
-        if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
-            return mDatabase;  // The database is already open for business
+        if (mDatabase != null) {
+            if (!mDatabase.isOpen()) {
+                // darn! the user closed the database by calling mDatabase.close()
+                mDatabase = null;
+            } else if (!mDatabase.isReadOnly()) {
+                return mDatabase;  // The database is already open for business
+            }
         }
 
         if (mIsInitializing) {
@@ -115,10 +148,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 {
@@ -175,8 +212,13 @@
      *     or {@link #close} is called.
      */
     public synchronized SQLiteDatabase getReadableDatabase() {
-        if (mDatabase != null && mDatabase.isOpen()) {
-            return mDatabase;  // The database is already open for business
+        if (mDatabase != null) {
+            if (!mDatabase.isOpen()) {
+                // darn! the user closed the database by calling mDatabase.close()
+                mDatabase = null;
+            } else {
+                return mDatabase;  // The database is already open for business
+            }
         }
 
         if (mIsInitializing) {
@@ -194,7 +236,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..83621f2 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -16,12 +16,16 @@
 
 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.
- *
- * SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple
+ *<p>
+ * SQLiteProgram is NOT internally synchronized so code using a SQLiteProgram from multiple
  * threads should perform its own synchronization when using the SQLiteProgram.
  */
 public abstract class SQLiteProgram extends SQLiteClosable {
@@ -43,12 +47,12 @@
      * @deprecated do not use this
      */
     @Deprecated
-    protected int nHandle = 0;
+    protected int nHandle;
 
     /**
      * the SQLiteCompiledSql object for the given sql statement.
      */
-    private SQLiteCompiledSql mCompiledSql;
+    /* package */ SQLiteCompiledSql mCompiledSql;
 
     /**
      * SQLiteCompiledSql statement id is populated with the corresponding object from the above
@@ -56,41 +60,97 @@
      * @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 */ static final int STATEMENT_CACHEABLE = 16;
+    /* package */ static final int STATEMENT_DONT_PREPARE = 32;
+    /* package */ static final int STATEMENT_USE_POOLED_CONN = 64;
+    /* package */ static final int STATEMENT_TYPE_MASK = 0x0f;
 
     /* 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();
+        int n = DatabaseUtils.getSqlStatementType(mSql);
+        switch (n) {
+            case DatabaseUtils.STATEMENT_UPDATE:
+                mStatementType = n | STATEMENT_CACHEABLE;
+                break;
+            case DatabaseUtils.STATEMENT_SELECT:
+                mStatementType = n | STATEMENT_CACHEABLE | STATEMENT_USE_POOLED_CONN;
+                break;
+            case DatabaseUtils.STATEMENT_ATTACH:
+            case DatabaseUtils.STATEMENT_BEGIN:
+            case DatabaseUtils.STATEMENT_COMMIT:
+            case DatabaseUtils.STATEMENT_ABORT:
+            case DatabaseUtils.STATEMENT_DDL:
+            case DatabaseUtils.STATEMENT_UNPREPARED:
+                mStatementType = n | STATEMENT_DONT_PREPARE;
+                break;
+            default:
+                mStatementType = n;
+        }
         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 & STATEMENT_CACHEABLE) == 0) {
+            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);
-            if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
-                Log.v(TAG, "Created DbObj (id#" + mCompiledSql.nStatement +
-                        ") for sql: " + sql);
-            }
+            mDatabase.addToCompiledQueries(mSql, mCompiledSql);
         } else {
             // it is already in compiled-sql cache.
             // try to acquire the object.
@@ -100,13 +160,7 @@
                 // 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);
-                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);
-                }
+                mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
                 // since it is not in the cache, no need to acquire() it.
             }
         }
@@ -115,42 +169,45 @@
 
     @Override
     protected void onAllReferencesReleased() {
-        releaseCompiledSqlIfNotInCache();
-        mDatabase.releaseReference();
+        release();
         mDatabase.removeSQLiteClosable(this);
+        mDatabase.releaseReference();
     }
 
     @Override
     protected void onAllReferencesReleasedFromContainer() {
-        releaseCompiledSqlIfNotInCache();
+        release();
         mDatabase.releaseReference();
     }
 
-    private void releaseCompiledSqlIfNotInCache() {
+    /* package */ void release() {
         if (mCompiledSql == null) {
             return;
         }
-        synchronized(mDatabase.mCompiledQueries) {
-            if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) {
-                // 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();
-            }
-        } 
+        mDatabase.releaseCompiledSqlObj(mCompiledSql);
+        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 +226,37 @@
         // TODO is there a need for this?
     }
 
+    private void bind(int type, int index, Object value) {
+        mDatabase.verifyDbIsOpen();
+        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,24 +314,18 @@
         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");
+        mBindArgs = null;
+        if (this.nStatement == 0) {
+            return;
         }
+        mDatabase.verifyDbIsOpen();
         acquireReference();
         try {
             native_clear_bindings();
@@ -288,15 +338,79 @@
      * Release this program's resources, making it invalid.
      */
     public void close() {
-        if (!mDatabase.isOpen()) {
+        mBindArgs = null;
+        if (nHandle == 0 || !mDatabase.isOpen()) {
             return;
         }
-        mDatabase.lock();
-        try {
-            releaseReference();
-        } finally {
-            mDatabase.unlock();
+        releaseReference();
+    }
+
+    private void addToBindArgs(int index, Object value) {
+        if (mBindArgs == null) {
+            mBindArgs = new HashMap<Integer, Object>();
         }
+        mBindArgs.put(index, value);
+    }
+
+    /* package */ void compileAndbindAllArgs() {
+        if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
+            // no need to prepare this SQL statement
+            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+                if (mBindArgs != null) {
+                    throw new IllegalArgumentException("no need to pass bindargs for this sql :" +
+                            mSql);
+                }
+            }
+            return;
+        }
+        if (nStatement == 0) {
+            // SQL statement is not compiled yet. compile it now.
+            compileSql();
+        }
+        if (mBindArgs == null) {
+            return;
+        }
+        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;
+        for (int i = 0; i < size; i++) {
+            bindString(i + 1, bindArgs[i]);
+        }
+    }
+
+    /* package */ synchronized final void setNativeHandle(int nHandle) {
+        this.nHandle = nHandle;
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 905b66b..e9e0172 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -28,28 +28,38 @@
  * 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;
+        this.mOffsetIndex = query.mOffsetIndex;
     }
 
     /**
@@ -70,13 +80,8 @@
                 // if the start pos is not equal to 0, then most likely window is
                 // too small for the data set, loading by another thread
                 // 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);
-                }
+                int numRows = native_fill_window(window, window.getStartPosition(),
+                        mOffsetIndex, maxRead, lastPos);
                 mDatabase.logTimeStat(mSql, timeStart);
                 return numRows;
             } catch (IllegalStateException e){
@@ -85,6 +90,9 @@
             } catch (SQLiteDatabaseCorruptException e) {
                 mDatabase.onCorruption();
                 throw e;
+            } catch (SQLiteException e) {
+                Log.e(TAG, "exception: " + e.getMessage() + "; query: " + mSql);
+                throw e;
             } finally {
                 window.releaseReference();
             }
@@ -141,51 +149,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..5e96928 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -16,7 +16,12 @@
 
 package android.database.sqlite;
 
+import android.database.DatabaseUtils;
+import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.IOException;
 
 import dalvik.system.BlockGuard;
 
@@ -25,44 +30,70 @@
  * The statement cannot return multiple rows, but 1x1 result sets are allowed.
  * Don't use SQLiteStatement constructor directly, please use
  * {@link SQLiteDatabase#compileStatement(String)}
- *
- * SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple
+ *<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 String TAG = "SQLiteStatement";
+
+    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();
+    /**
+     * Execute this SQL statement, if the the number of rows affected by execution 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() {
         try {
-            native_execute();
+            long timeStart = acquireAndLock(WRITE);
+            int numChanges = 0;
+            if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
+                // since the statement doesn't have to be prepared,
+                // call the following native method which will not prepare
+                // the query plan
+                native_executeSql(mSql);
+            } else {
+                numChanges = native_execute();
+            }
             mDatabase.logTimeStat(mSql, timeStart);
+            return numChanges;
         } finally {
-            releaseReference();
-            mDatabase.unlock();
+            releaseAndUnlock();
         }
     }
 
@@ -76,21 +107,13 @@
      *         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();
+            long timeStart = acquireAndLock(WRITE);
+            long lastInsertedRowId = native_executeInsert();
             mDatabase.logTimeStat(mSql, timeStart);
-            return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1;
+            return lastInsertedRowId;
         } finally {
-            releaseReference();
-            mDatabase.unlock();
+            releaseAndUnlock();
         }
     }
 
@@ -103,21 +126,13 @@
      * @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 timeStart = acquireAndLock(READ);
             long retValue = native_1x1_long();
             mDatabase.logTimeStat(mSql, timeStart);
             return retValue;
         } finally {
-            releaseReference();
-            mDatabase.unlock();
+            releaseAndUnlock();
         }
     }
 
@@ -130,25 +145,136 @@
      * @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 {
+            long timeStart = acquireAndLock(READ);
             String retValue = native_1x1_string();
             mDatabase.logTimeStat(mSql, timeStart);
             return retValue;
         } finally {
-            releaseReference();
-            mDatabase.unlock();
+            releaseAndUnlock();
         }
     }
 
-    private final native void native_execute();
+    /**
+     * Executes a statement that returns a 1 by 1 table with a blob value.
+     *
+     * @return A read-only file descriptor for a copy of the blob value, or {@code null}
+     *         if the value is null or could not be read for some reason.
+     *
+     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
+     */
+    public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() {
+        try {
+            long timeStart = acquireAndLock(READ);
+            ParcelFileDescriptor retValue = native_1x1_blob_ashmem();
+            mDatabase.logTimeStat(mSql, timeStart);
+            return retValue;
+        } catch (IOException ex) {
+            Log.e(TAG, "simpleQueryForBlobFileDescriptor() failed", ex);
+            return null;
+        } finally {
+            releaseAndUnlock();
+        }
+    }
+
+    /**
+     * 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 & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0)
+                ? mDatabase.getDbConnection(mSql) : mDatabase;
+        // use the database connection obtained above
+        mOrigDb = mDatabase;
+        mDatabase = db;
+        setNativeHandle(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 & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) {
+            if (!mDatabase.isDbLockedByCurrentThread()) {
+                // transaction is  NOT started by calling beginTransaction() methods in
+                // SQLiteDatabase
+                mDatabase.setTransactionUsingExecSqlFlag();
+            }
+        } else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
+                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 & SQLiteProgram.STATEMENT_TYPE_MASK) ==
+                DatabaseUtils.STATEMENT_COMMIT ||
+                (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
+                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.
+        release();
+        // restore the database connection handle to the original value
+        mDatabase = mOrigDb;
+        setNativeHandle(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();
+    private final native ParcelFileDescriptor native_1x1_blob_ashmem() throws IOException;
+    private final native void native_executeSql(String sql);
 }
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/database/sqlite/SQLiteUnfinalizedObjectsException.java b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java
new file mode 100644
index 0000000..bcf95e2
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 can't be closed because of some un-closed
+ * Cursor or SQLiteStatement objects. Could happen when a thread is trying to close
+ * the database while another thread still hasn't closed a Cursor on that database.
+ * @hide
+ */
+public class SQLiteUnfinalizedObjectsException extends SQLiteException {
+    public SQLiteUnfinalizedObjectsException() {}
+
+    public SQLiteUnfinalizedObjectsException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/hardware/Usb.java b/core/java/android/hardware/Usb.java
index 57271d4..ebb8296 100644
--- a/core/java/android/hardware/Usb.java
+++ b/core/java/android/hardware/Usb.java
@@ -17,6 +17,10 @@
 
 package android.hardware;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
 /**
  * Class for accessing USB state information.
  * @hide
@@ -55,6 +59,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.
@@ -96,4 +118,30 @@
      * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast
      */
     public static final String USB_FUNCTION_DISABLED = "disabled";
+
+    private static File getFunctionEnableFile(String function) {
+        return new File("/sys/class/usb_composite/" + function + "/enable");
+    }
+
+    /**
+     * Returns true if the specified USB function is supported by the kernel.
+     * Note that a USB function maybe supported but disabled.
+     */
+    public static boolean isFunctionSupported(String function) {
+        return getFunctionEnableFile(function).exists();
+    }
+
+    /**
+     * Returns true if the specified USB function is currently enabled.
+     */
+    public static boolean isFunctionEnabled(String function) {
+        try {
+            FileInputStream stream = new FileInputStream(getFunctionEnableFile(function));
+            boolean enabled = (stream.read() == '1');
+            stream.close();
+            return enabled;
+        } catch (IOException e) {
+            return false;
+        }
+    }
 }
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 3619653..27af013 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -177,6 +177,7 @@
      * Implement this to handle {@link android.os.Binder#dump Binder.dump()}
      * calls on your input method.
      */
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
     }
 
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 8a52e40..22968b0 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.ContextMenu;
 import android.view.inputmethod.ExtractedText;
 import android.widget.EditText;
 
@@ -29,7 +28,6 @@
 public class ExtractEditText extends EditText {
     private InputMethodService mIME;
     private int mSettingExtractedText;
-    private boolean mContextMenuShouldBeHandledBySuper = false;
     
     public ExtractEditText(Context context) {
         super(context, null);
@@ -99,19 +97,12 @@
         return false;
     }
     
-    @Override
-    protected void onCreateContextMenu(ContextMenu menu) {
-        super.onCreateContextMenu(menu);
-        mContextMenuShouldBeHandledBySuper = true;
-    }
-
     @Override public boolean onTextContextMenuItem(int id) {
-        if (mIME != null && !mContextMenuShouldBeHandledBySuper) {
+        if (mIME != null) {
             if (mIME.onExtractTextContextMenuItem(id)) {
                 return true;
             }
         }
-        mContextMenuShouldBeHandledBySuper = false;
         return super.onTextContextMenuItem(id);
     }
     
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 35fd46f..24ea7d2 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -36,6 +36,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodSession;
+import android.view.inputmethod.InputMethodSubtype;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -64,6 +65,7 @@
     private static final int DO_REVOKE_SESSION = 50;
     private static final int DO_SHOW_SOFT_INPUT = 60;
     private static final int DO_HIDE_SOFT_INPUT = 70;
+    private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
    
     final WeakReference<AbstractInputMethodService> mTarget;
     final HandlerCaller mCaller;
@@ -178,6 +180,9 @@
             case DO_HIDE_SOFT_INPUT:
                 inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
                 return;
+            case DO_CHANGE_INPUTMETHOD_SUBTYPE:
+                inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
+                return;
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
@@ -267,4 +272,9 @@
         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
                 flags, resultReceiver));
     }
+
+    public void changeInputMethodSubtype(InputMethodSubtype subtype) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
+                subtype));
+    }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 1a261d3..6089013 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -52,6 +52,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.EditorInfo;
 import android.widget.Button;
 import android.widget.FrameLayout;
@@ -270,7 +271,7 @@
 
     final Insets mTmpInsets = new Insets();
     final int[] mTmpLocation = new int[2];
-    
+
     final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
             new ViewTreeObserver.OnComputeInternalInsetsListener() {
         public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
@@ -394,8 +395,12 @@
                                 : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
             }
         }
+
+        public void changeInputMethodSubtype(InputMethodSubtype subtype) {
+            onCurrentInputMethodSubtypeChanged(subtype);
+        }
     }
-    
+
     /**
      * Concrete implementation of
      * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
@@ -541,6 +546,7 @@
      * will typically call it in your constructor with the resource ID
      * of your custom theme.
      */
+    @Override
     public void setTheme(int theme) {
         if (mWindow != null) {
             throw new IllegalStateException("Must be called before onCreate()");
@@ -558,7 +564,7 @@
         initViews();
         mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
     }
-    
+
     /**
      * This is a hook that subclasses can use to perform initialization of
      * their interface.  It is called for you prior to any of your UI objects
@@ -567,14 +573,14 @@
      */
     public void onInitializeInterface() {
     }
-    
+
     void initialize() {
         if (!mInitialized) {
             mInitialized = true;
             onInitializeInterface();
         }
     }
-    
+
     void initViews() {
         mInitialized = false;
         mWindowCreated = false;
@@ -610,7 +616,7 @@
         mCandidatesFrame.setVisibility(mCandidatesVisibility);
         mInputFrame.setVisibility(View.GONE);
     }
-    
+
     @Override public void onDestroy() {
         super.onDestroy();
         mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
@@ -677,6 +683,7 @@
      * Implement to return our standard {@link InputMethodImpl}.  Subclasses
      * can override to provide their own customized version.
      */
+    @Override
     public AbstractInputMethodImpl onCreateInputMethodInterface() {
         return new InputMethodImpl();
     }
@@ -685,6 +692,7 @@
      * Implement to return our standard {@link InputMethodSessionImpl}.  Subclasses
      * can override to provide their own customized version.
      */
+    @Override
     public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() {
         return new InputMethodSessionImpl();
     }
@@ -1656,6 +1664,7 @@
         return doMovementKey(keyCode, event, MOVEMENT_UP);
     }
 
+    @Override
     public boolean onTrackballEvent(MotionEvent event) {
         return false;
     }
@@ -1694,7 +1703,7 @@
                 dy = count;
                 break;
         }
-        onExtractedCursorMovement(dx, dy);       
+        onExtractedCursorMovement(dx, dy);
     }
     
     boolean doMovementKey(int keyCode, KeyEvent event, int count) {
@@ -2064,7 +2073,24 @@
             }
         }
     }
-    
+
+    // TODO: Handle the subtype change event
+    /**
+     * Called when the subtype was changed.
+     * @param newSubtype the subtype which is being changed to.
+     */
+    protected void onCurrentInputMethodSubtypeChanged(InputMethodSubtype newSubtype) {
+        if (DEBUG) {
+            int nameResId = newSubtype.getNameResId();
+            int modeResId = newSubtype.getModeResId();
+            String output = "changeInputMethodSubtype:"
+                + (nameResId == 0 ? "<none>" : getString(nameResId)) + ","
+                + (modeResId == 0 ? "<none>" : getString(modeResId)) + ","
+                + newSubtype.getLocale() + "," + newSubtype.getExtraValue();
+            Log.v(TAG, "--- " + output);
+        }
+    }
+
     /**
      * Performs a dump of the InputMethodService's internal state.  Override
      * to add your own information to the dump.
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index 269e1c9..75c945b 100644
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -504,7 +504,30 @@
     public Keyboard(Context context, int xmlLayoutResId) {
         this(context, xmlLayoutResId, 0);
     }
-    
+
+    /**
+     * Creates a keyboard from the given xml key layout file. Weeds out rows
+     * that have a keyboard mode defined but don't match the specified mode.
+     * @param context the application or service context
+     * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+     * @param modeId keyboard mode identifier
+     * @param width sets width of keyboard
+     * @param height sets height of keyboard
+     */
+    public Keyboard(Context context, int xmlLayoutResId, int modeId, int width, int height) {
+        mDisplayWidth = width;
+        mDisplayHeight = height;
+
+        mDefaultHorizontalGap = 0;
+        mDefaultWidth = mDisplayWidth / 10;
+        mDefaultVerticalGap = 0;
+        mDefaultHeight = mDefaultWidth;
+        mKeys = new ArrayList<Key>();
+        mModifierKeys = new ArrayList<Key>();
+        mKeyboardMode = modeId;
+        loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
+    }
+
     /**
      * Creates a keyboard from the given xml key layout file. Weeds out rows
      * that have a keyboard mode defined but don't match the specified mode. 
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 1cfe419b7..dd9c8f0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -264,6 +264,24 @@
         }
     }
 
+    /** @hide */
+    public LinkProperties getActiveLinkProperties() {
+        try {
+            return mService.getActiveLinkProperties();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /** @hide */
+    public LinkProperties getLinkProperties(int networkType) {
+        try {
+            return mService.getLinkProperties(networkType);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
     /** {@hide} */
     public boolean setRadios(boolean turnOn) {
         try {
@@ -367,14 +385,14 @@
      * <p>
      * All applications that have background services that use the network
      * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
-     * 
+     *
      * @return Whether background data usage is allowed.
      */
     public boolean getBackgroundDataSetting() {
         try {
             return mService.getBackgroundDataSetting();
         } catch (RemoteException e) {
-            // Err on the side of safety 
+            // Err on the side of safety
             return false;
         }
     }
@@ -532,6 +550,17 @@
         }
     }
 
+    /**
+     * {@hide}
+     */
+    public String[] getTetherableBluetoothRegexs() {
+        try {
+            return mService.getTetherableBluetoothRegexs();
+        } catch (RemoteException e) {
+            return new String[0];
+        }
+    }
+
     /** {@hide} */
     public static final int TETHER_ERROR_NO_ERROR           = 0;
     /** {@hide} */
@@ -570,6 +599,21 @@
     }
 
     /**
+     * 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;
+        }
+    }
+
+    /**
      * @param networkType The type of network you want to report on
      * @param percentage The quality of the connection 0 is bad, 100 is good
      * {@hide}
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
index 1178bec..9c81c19 100644
--- a/core/java/android/net/DhcpInfo.java
+++ b/core/java/android/net/DhcpInfo.java
@@ -37,6 +37,19 @@
         super();
     }
 
+    /** copy constructor {@hide} */
+    public DhcpInfo(DhcpInfo source) {
+        if (source != null) {
+            ipAddress = source.ipAddress;
+            gateway = source.gateway;
+            netmask = source.netmask;
+            dns1 = source.dns1;
+            dns2 = source.dns2;
+            serverAddress = source.serverAddress;
+            leaseDuration = source.leaseDuration;
+        }
+    }
+
     public String toString() {
         StringBuffer str = new StringBuffer();
 
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 afccdd9..35054d6 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -1,21 +1,22 @@
 /**
  * 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 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this 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.
  */
 
 package android.net;
 
+import android.net.LinkProperties;
 import android.net.NetworkInfo;
 import android.os.IBinder;
 
@@ -36,6 +37,10 @@
 
     NetworkInfo[] getAllNetworkInfo();
 
+    LinkProperties getActiveLinkProperties();
+
+    LinkProperties getLinkProperties(int networkType);
+
     boolean setRadios(boolean onOff);
 
     boolean setRadio(int networkType, boolean turnOn);
@@ -75,5 +80,9 @@
 
     String[] getTetherableWifiRegexs();
 
+    String[] getTetherableBluetoothRegexs();
+
+    void requestNetworkTransitionWakelock(in String forWhom);
+
     void reportInetCondition(int networkType, int percentage);
 }
diff --git a/core/java/android/net/LinkAddress.aidl b/core/java/android/net/LinkAddress.aidl
new file mode 100644
index 0000000..e7d8646
--- /dev/null
+++ b/core/java/android/net/LinkAddress.aidl
@@ -0,0 +1,21 @@
+/**
+ *
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 LinkAddress;
+
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
new file mode 100644
index 0000000..cb302da
--- /dev/null
+++ b/core/java/android/net/LinkAddress.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.InterfaceAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Identifies an address of a network link
+ * @hide
+ */
+public class LinkAddress implements Parcelable {
+    /**
+     * IPv4 or IPv6 address.
+     */
+    private final InetAddress address;
+
+    /**
+     * Network prefix
+     */
+    private final int prefix;
+
+    public LinkAddress(InetAddress address, InetAddress mask) {
+        this.address = address;
+        this.prefix = computeprefix(mask);
+    }
+
+    public LinkAddress(InetAddress address, int prefix) {
+        this.address = address;
+        this.prefix = prefix;
+    }
+
+    public LinkAddress(InterfaceAddress interfaceAddress) {
+        this.address = interfaceAddress.getAddress();
+        this.prefix = interfaceAddress.getNetworkPrefixLength();
+    }
+
+    private static int computeprefix(InetAddress mask) {
+        int count = 0;
+        for (byte b : mask.getAddress()) {
+            for (int i = 0; i < 8; ++i) {
+                if ((b & (1 << i)) != 0) {
+                    ++count;
+                }
+            }
+        }
+        return count;
+    }
+
+    @Override
+    public String toString() {
+        return (address == null ? "" : (address.getHostAddress() + "/" + prefix));
+    }
+
+    /**
+     * Compares this {@code LinkAddress} instance against the specified address
+     * in {@code obj}. Two addresses are equal if their InetAddress and prefix
+     * are equal
+     *
+     * @param obj the object to be tested for equality.
+     * @return {@code true} if both objects are equal, {@code false} otherwise.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LinkAddress)) {
+            return false;
+        }
+        LinkAddress linkAddress = (LinkAddress) obj;
+        return this.address.equals(linkAddress.address) &&
+            this.prefix == linkAddress.prefix;
+    }
+
+    /**
+     * Returns the InetAddress for this address.
+     */
+    public InetAddress getAddress() {
+        return address;
+    }
+
+    /**
+     * Get network prefix length
+     */
+    public int getNetworkPrefix() {
+        return prefix;
+    }
+
+    /**
+     * Implement the Parcelable interface
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        if (address != null) {
+            dest.writeByte((byte)1);
+            dest.writeByteArray(address.getAddress());
+            dest.writeInt(prefix);
+        } else {
+            dest.writeByte((byte)0);
+        }
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public static final Creator<LinkAddress> CREATOR =
+        new Creator<LinkAddress>() {
+            public LinkAddress createFromParcel(Parcel in) {
+                InetAddress address = null;
+                int prefix = 0;
+                if (in.readByte() == 1) {
+                    try {
+                        address = InetAddress.getByAddress(in.createByteArray());
+                        prefix = in.readInt();
+                    } catch (UnknownHostException e) { }
+                }
+                return new LinkAddress(address, prefix);
+            }
+
+            public LinkAddress[] newArray(int size) {
+                return new LinkAddress[size];
+            }
+        };
+}
diff --git a/core/java/android/net/LinkCapabilities.aidl b/core/java/android/net/LinkCapabilities.aidl
new file mode 100644
index 0000000..df72599
--- /dev/null
+++ b/core/java/android/net/LinkCapabilities.aidl
@@ -0,0 +1,21 @@
+/*
+**
+** Copyright (C) 2010 The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES 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 LinkCapabilities;
+
diff --git a/core/java/android/net/LinkCapabilities.java b/core/java/android/net/LinkCapabilities.java
new file mode 100644
index 0000000..eb9166f
--- /dev/null
+++ b/core/java/android/net/LinkCapabilities.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * A class representing the capabilities of a link
+ *
+ * @hide
+ */
+public class LinkCapabilities implements Parcelable {
+    private static final String TAG = "LinkCapabilities";
+    private static final boolean DBG = false;
+
+    /** The Map of Keys to Values */
+    private HashMap<Integer, String> mCapabilities;
+
+
+    /**
+     * The set of keys defined for a links capabilities.
+     *
+     * Keys starting with RW are read + write, i.e. the application
+     * can request for a certain requirement corresponding to that key.
+     * Keys starting with RO are read only, i.e. the the application
+     * can read the value of that key from the socket but cannot request
+     * a corresponding requirement.
+     *
+     * TODO: Provide a documentation technique for concisely and precisely
+     * define the syntax for each value string associated with a key.
+     */
+    public static final class Key {
+        /** No constructor */
+        private Key() {}
+
+        /**
+         * An integer representing the network type.
+         * @see ConnectivityManager
+         */
+        public final static int RO_NETWORK_TYPE = 1;
+
+        /**
+         * Desired minimum forward link (download) bandwidth for the
+         * in kilobits per second (kbps). Values should be strings such
+         * "50", "100", "1500", etc.
+         */
+        public final static int RW_DESIRED_FWD_BW = 2;
+
+        /**
+         * Required minimum forward link (download) bandwidth, in
+         * per second (kbps), below which the socket cannot function.
+         * Values should be strings such as "50", "100", "1500", etc.
+         */
+        public final static int RW_REQUIRED_FWD_BW = 3;
+
+        /**
+         * Available forward link (download) bandwidth for the socket.
+         * This value is in kilobits per second (kbps).
+         * Values will be strings such as "50", "100", "1500", etc.
+         */
+        public final static int RO_AVAILABLE_FWD_BW = 4;
+
+        /**
+         * Desired minimum reverse link (upload) bandwidth for the socket
+         * in kilobits per second (kbps).
+         * Values should be strings such as "50", "100", "1500", etc.
+         * <p>
+         * This key is set via the needs map.
+         */
+        public final static int RW_DESIRED_REV_BW = 5;
+
+        /**
+         * Required minimum reverse link (upload) bandwidth, in kilobits
+         * per second (kbps), below which the socket cannot function.
+         * If a rate is not specified, the default rate of kbps will be
+         * Values should be strings such as "50", "100", "1500", etc.
+         */
+        public final static int RW_REQUIRED_REV_BW = 6;
+
+        /**
+         * Available reverse link (upload) bandwidth for the socket.
+         * This value is in kilobits per second (kbps).
+         * Values will be strings such as "50", "100", "1500", etc.
+         */
+        public final static int RO_AVAILABLE_REV_BW = 7;
+
+        /**
+         * Maximum latency for the socket, in milliseconds, above which
+         * socket cannot function.
+         * Values should be strings such as "50", "300", "500", etc.
+         */
+        public final static int RW_MAX_ALLOWED_LATENCY = 8;
+
+        /**
+         * Interface that the socket is bound to. This can be a virtual
+         * interface (e.g. VPN or Mobile IP) or a physical interface
+         * (e.g. wlan0 or rmnet0).
+         * Values will be strings such as "wlan0", "rmnet0"
+         */
+        public final static int RO_BOUND_INTERFACE = 9;
+
+        /**
+         * Physical interface that the socket is routed on.
+         * This can be different from BOUND_INTERFACE in cases such as
+         * VPN or Mobile IP. The physical interface may change over time
+         * if seamless mobility is supported.
+         * Values will be strings such as "wlan0", "rmnet0"
+         */
+        public final static int RO_PHYSICAL_INTERFACE = 10;
+    }
+
+    /**
+     * Role informs the LinkSocket about the data usage patterns of your
+     * application.
+     * <P>
+     * {@code Role.DEFAULT} is the default role, and is used whenever
+     * a role isn't set.
+     */
+    public static final class Role {
+        /** No constructor */
+        private Role() {}
+
+        // examples only, discuss which roles should be defined, and then
+        // code these to match
+
+        /** Default Role */
+        public static final String DEFAULT = "default";
+        /** Bulk down load */
+        public static final String BULK_DOWNLOAD = "bulk.download";
+        /** Bulk upload */
+        public static final String BULK_UPLOAD = "bulk.upload";
+
+        /** VoIP Application at 24kbps */
+        public static final String VOIP_24KBPS = "voip.24k";
+        /** VoIP Application at 32kbps */
+        public static final String VOIP_32KBPS = "voip.32k";
+
+        /** Video Streaming at 480p */
+        public static final String VIDEO_STREAMING_480P = "video.streaming.480p";
+        /** Video Streaming at 720p */
+        public static final String VIDEO_STREAMING_720I = "video.streaming.720i";
+
+        /** Video Chat Application at 360p */
+        public static final String VIDEO_CHAT_360P = "video.chat.360p";
+        /** Video Chat Application at 480p */
+        public static final String VIDEO_CHAT_480P = "video.chat.480i";
+    }
+
+    /**
+     * Constructor
+     */
+    public LinkCapabilities() {
+        mCapabilities = new HashMap<Integer, String>();
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param source
+     */
+    public LinkCapabilities(LinkCapabilities source) {
+        if (source != null) {
+            mCapabilities = new HashMap<Integer, String>(source.mCapabilities);
+        } else {
+            mCapabilities = new HashMap<Integer, String>();
+        }
+    }
+
+    /**
+     * Create the {@code LinkCapabilities} with values depending on role type.
+     * @param applicationRole a {@code LinkSocket.Role}
+     * @return the {@code LinkCapabilities} associated with the applicationRole, empty if none
+     */
+    public static LinkCapabilities createNeedsMap(String applicationRole) {
+        if (DBG) log("createNeededCapabilities(applicationRole) EX");
+        return new LinkCapabilities();
+    }
+
+    /**
+     * Remove all capabilities
+     */
+    public void clear() {
+        mCapabilities.clear();
+    }
+
+    /**
+     * Returns whether this map is empty.
+     */
+    public boolean isEmpty() {
+        return mCapabilities.isEmpty();
+    }
+
+    /**
+     * Returns the number of elements in this map.
+     *
+     * @return the number of elements in this map.
+     */
+    public int size() {
+        return mCapabilities.size();
+    }
+
+    /**
+     * Given the key return the capability string
+     *
+     * @param key
+     * @return the capability string
+     */
+    public String get(int key) {
+        return mCapabilities.get(key);
+    }
+
+    /**
+     * Store the key/value capability pair
+     *
+     * @param key
+     * @param value
+     */
+    public void put(int key, String value) {
+        mCapabilities.put(key, value);
+    }
+
+    /**
+     * Returns whether this map contains the specified key.
+     *
+     * @param key to search for.
+     * @return {@code true} if this map contains the specified key,
+     *         {@code false} otherwise.
+     */
+    public boolean containsKey(int key) {
+        return mCapabilities.containsKey(key);
+    }
+
+    /**
+     * Returns whether this map contains the specified value.
+     *
+     * @param value to search for.
+     * @return {@code true} if this map contains the specified value,
+     *         {@code false} otherwise.
+     */
+    public boolean containsValue(String value) {
+        return mCapabilities.containsValue(value);
+    }
+
+    /**
+     * Returns a set containing all of the mappings in this map. Each mapping is
+     * an instance of {@link Map.Entry}. As the set is backed by this map,
+     * changes in one will be reflected in the other.
+     *
+     * @return a set of the mappings.
+     */
+    public Set<Entry<Integer, String>> entrySet() {
+        return mCapabilities.entrySet();
+    }
+
+    /**
+     * @return the set of the keys.
+     */
+    public Set<Integer> keySet() {
+        return mCapabilities.keySet();
+    }
+
+    /**
+     * @return the set of values
+     */
+    public Collection<String> values() {
+        return mCapabilities.values();
+    }
+
+    /**
+     * Implement the Parcelable interface
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Convert to string for debugging
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("{");
+        boolean firstTime = true;
+        for (Entry<Integer, String> entry : mCapabilities.entrySet()) {
+            if (firstTime) {
+                firstTime = false;
+            } else {
+                sb.append(",");
+            }
+            sb.append(entry.getKey());
+            sb.append(":\"");
+            sb.append(entry.getValue());
+            sb.append("\"");
+            return mCapabilities.toString();
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mCapabilities.size());
+        for (Entry<Integer, String> entry : mCapabilities.entrySet()) {
+            dest.writeInt(entry.getKey().intValue());
+            dest.writeString(entry.getValue());
+        }
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public static final Creator<LinkCapabilities> CREATOR =
+        new Creator<LinkCapabilities>() {
+            public LinkCapabilities createFromParcel(Parcel in) {
+                LinkCapabilities capabilities = new LinkCapabilities();
+                int size = in.readInt();
+                while (size-- != 0) {
+                    int key = in.readInt();
+                    String value = in.readString();
+                    capabilities.mCapabilities.put(key, value);
+                }
+                return capabilities;
+            }
+
+            public LinkCapabilities[] newArray(int size) {
+                return new LinkCapabilities[size];
+            }
+        };
+
+    /**
+     * Debug logging
+     */
+    protected static void log(String s) {
+        Log.d(TAG, s);
+    }
+}
diff --git a/core/java/android/net/LinkProperties.aidl b/core/java/android/net/LinkProperties.aidl
new file mode 100644
index 0000000..9cd06d5
--- /dev/null
+++ b/core/java/android/net/LinkProperties.aidl
@@ -0,0 +1,21 @@
+/*
+**
+** Copyright (C) 2010 The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES 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 LinkProperties;
+
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
new file mode 100644
index 0000000..f1545ea
--- /dev/null
+++ b/core/java/android/net/LinkProperties.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.net.ProxyProperties;
+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;
+import java.util.Collections;
+
+/**
+ * Describes the properties of a network link.
+ * TODO - consider adding optional fields like Apn and ApnType
+ * @hide
+ */
+public class LinkProperties implements Parcelable {
+
+    String mIfaceName;
+    private Collection<LinkAddress> mLinkAddresses;
+    private Collection<InetAddress> mDnses;
+    private InetAddress mGateway;
+    private ProxyProperties mHttpProxy;
+
+    public LinkProperties() {
+        clear();
+    }
+
+    // copy constructor instead of clone
+    public LinkProperties(LinkProperties source) {
+        if (source != null) {
+            mIfaceName = source.getInterfaceName();
+            mLinkAddresses = source.getLinkAddresses();
+            mDnses = source.getDnses();
+            mGateway = source.getGateway();
+            mHttpProxy = new ProxyProperties(source.getHttpProxy());
+        }
+    }
+
+    public void setInterfaceName(String iface) {
+        mIfaceName = iface;
+    }
+
+    public String getInterfaceName() {
+        return mIfaceName;
+    }
+
+    public Collection<InetAddress> getAddresses() {
+        Collection<InetAddress> addresses = new ArrayList<InetAddress>();
+        for (LinkAddress linkAddress : mLinkAddresses) {
+            addresses.add(linkAddress.getAddress());
+        }
+        return Collections.unmodifiableCollection(addresses);
+    }
+
+    public void addLinkAddress(LinkAddress address) {
+        mLinkAddresses.add(address);
+    }
+
+    public Collection<LinkAddress> getLinkAddresses() {
+        return Collections.unmodifiableCollection(mLinkAddresses);
+    }
+
+    public void addDns(InetAddress dns) {
+        mDnses.add(dns);
+    }
+
+    public Collection<InetAddress> getDnses() {
+        return Collections.unmodifiableCollection(mDnses);
+    }
+
+    public void setGateway(InetAddress gateway) {
+        mGateway = gateway;
+    }
+    public InetAddress getGateway() {
+        return mGateway;
+    }
+
+    public void setHttpProxy(ProxyProperties proxy) {
+        mHttpProxy = proxy;
+    }
+    public ProxyProperties getHttpProxy() {
+        return mHttpProxy;
+    }
+
+    public void clear() {
+        mIfaceName = null;
+        mLinkAddresses = new ArrayList<LinkAddress>();
+        mDnses = new ArrayList<InetAddress>();
+        mGateway = null;
+        mHttpProxy = null;
+    }
+
+    /**
+     * Implement the Parcelable interface
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
+
+        String linkAddresses = "LinkAddresses: [";
+        for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString();
+        linkAddresses += "] ";
+
+        String dns = "DnsAddresses: [";
+        for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
+        dns += "] ";
+
+        String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
+        String gateway = (mGateway == null ? "" : "Gateway: " + mGateway.getHostAddress() + " ");
+
+        return ifaceName + linkAddresses + gateway + dns + proxy;
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(getInterfaceName());
+        dest.writeInt(mLinkAddresses.size());
+        for(LinkAddress linkAddress : mLinkAddresses) {
+            dest.writeParcelable(linkAddress, flags);
+        }
+
+        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<LinkProperties> CREATOR =
+        new Creator<LinkProperties>() {
+            public LinkProperties createFromParcel(Parcel in) {
+                LinkProperties netProp = new LinkProperties();
+                String iface = in.readString();
+                if (iface != null) {
+                    try {
+                        netProp.setInterfaceName(iface);
+                    } catch (Exception e) {
+                        return null;
+                    }
+                }
+                int addressCount = in.readInt();
+                for (int i=0; i<addressCount; i++) {
+                    netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
+                }
+                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 LinkProperties[] newArray(int size) {
+                return new LinkProperties[size];
+            }
+        };
+}
diff --git a/core/java/android/net/LinkSocket.java b/core/java/android/net/LinkSocket.java
new file mode 100644
index 0000000..5aa6451
--- /dev/null
+++ b/core/java/android/net/LinkSocket.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.net.LinkSocketNotifier;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Set;
+
+/** @hide */
+public class LinkSocket extends Socket {
+    private final static String TAG = "LinkSocket";
+    private final static boolean DBG = true;
+
+    /**
+     * Default constructor
+     */
+    public LinkSocket() {
+        if (DBG) log("LinkSocket() EX");
+    }
+
+    /**
+     * Creates a new unconnected socket.
+     * @param notifier a reference to a class that implements {@code LinkSocketNotifier}
+     */
+    public LinkSocket(LinkSocketNotifier notifier) {
+        if (DBG) log("LinkSocket(notifier) EX");
+    }
+
+    /**
+     * Creates a new unconnected socket usign the given proxy type.
+     * @param notifier a reference to a class that implements {@code LinkSocketNotifier}
+     * @param proxy the specified proxy for this socket
+     * @throws IllegalArgumentException if the argument proxy is null or of an invalid type.
+     * @throws SecurityException if a security manager exists and it denies the permission
+     *                           to connect to the given proxy.
+     */
+    public LinkSocket(LinkSocketNotifier notifier, Proxy proxy) {
+        if (DBG) log("LinkSocket(notifier, proxy) EX");
+    }
+
+    /**
+     * @return the {@code LinkProperties} for the socket
+     */
+    public LinkProperties getLinkProperties() {
+        if (DBG) log("LinkProperties() EX");
+        return new LinkProperties();
+    }
+
+    /**
+     * Set the {@code LinkCapabilies} needed for this socket.  If the socket is already connected
+     * or is a duplicate socket the request is ignored and {@code false} will
+     * be returned. A needs map can be created via the {@code createNeedsMap} static
+     * method.
+     * @param needs the needs of the socket
+     * @return {@code true} if needs are successfully set, {@code false} otherwise
+     */
+    public boolean setNeededCapabilities(LinkCapabilities needs) {
+        if (DBG) log("setNeeds() EX");
+        return false;
+    }
+
+    /**
+     * @return the LinkCapabilites set by setNeededCapabilities, empty if none has been set
+     */
+    public LinkCapabilities getNeededCapabilities() {
+        if (DBG) log("getNeeds() EX");
+        return null;
+    }
+
+    /**
+     * @return all of the {@code LinkCapabilities} of the link used by this socket
+     */
+    public LinkCapabilities getCapabilities() {
+        if (DBG) log("getCapabilities() EX");
+        return null;
+    }
+
+    /**
+     * Returns this LinkSockets set of capabilities, filtered according to
+     * the given {@code Set}.  Capabilities in the Set but not available from
+     * the link will not be reported in the results.  Capabilities of the link
+     * but not listed in the Set will also not be reported in the results.
+     * @param capabilities {@code Set} of capabilities requested
+     * @return the filtered {@code LinkCapabilities} of this LinkSocket, may be empty
+     */
+    public LinkCapabilities getCapabilities(Set<Integer> capabilities) {
+        if (DBG) log("getCapabilities(capabilities) EX");
+        return new LinkCapabilities();
+    }
+
+    /**
+     * Provide the set of capabilities the application is interested in tracking
+     * for this LinkSocket.
+     * @param capabilities a {@code Set} of capabilities to track
+     */
+    public void setTrackedCapabilities(Set<Integer> capabilities) {
+        if (DBG) log("setTrackedCapabilities(capabilities) EX");
+    }
+
+    /**
+     * @return the {@code LinkCapabilities} that are tracked, empty if none has been set.
+     */
+    public Set<Integer> getTrackedCapabilities() {
+        if (DBG) log("getTrackedCapabilities(capabilities) EX");
+        return new HashSet<Integer>();
+    }
+
+    /**
+     * Connects this socket to the given remote host address and port specified
+     * by dstName and dstPort.
+     * @param dstName the address of the remote host to connect to
+     * @param dstPort the port to connect to on the remote host
+     * @param timeout the timeout value in milliseconds or 0 for infinite timeout
+     * @throws UnknownHostException if the given dstName is invalid
+     * @throws IOException if the socket is already connected or an error occurs
+     *                     while connecting
+     * @throws SocketTimeoutException if the timeout fires
+     */
+    public void connect(String dstName, int dstPort, int timeout)
+            throws UnknownHostException, IOException, SocketTimeoutException {
+        if (DBG) log("connect(dstName, dstPort, timeout) EX");
+    }
+
+    /**
+     * Connects this socket to the given remote host address and port specified
+     * by dstName and dstPort.
+     * @param dstName the address of the remote host to connect to
+     * @param dstPort the port to connect to on the remote host
+     * @throws UnknownHostException if the given dstName is invalid
+     * @throws IOException if the socket is already connected or an error occurs
+     *                     while connecting
+     */
+    public void connect(String dstName, int dstPort)
+            throws UnknownHostException, IOException {
+        if (DBG) log("connect(dstName, dstPort, timeout) EX");
+    }
+
+    /**
+     * Connects this socket to the given remote host address and port specified
+     * by the SocketAddress with the specified timeout.
+     * @deprecated Use {@code connect(String dstName, int dstPort, int timeout)}
+     *             instead.  Using this method may result in reduced functionality.
+     * @param remoteAddr the address and port of the remote host to connect to
+     * @throws IllegalArgumentException if the given SocketAddress is invalid
+     * @throws IOException if the socket is already connected or an error occurs
+     *                     while connecting
+     * @throws SocketTimeoutException if the timeout expires
+     */
+    @Override
+    @Deprecated
+    public void connect(SocketAddress remoteAddr, int timeout)
+            throws IOException, SocketTimeoutException {
+        if (DBG) log("connect(remoteAddr, timeout) EX DEPRECATED");
+    }
+
+    /**
+     * Connects this socket to the given remote host address and port specified
+     * by the SocketAddress.
+     * TODO add comment on all these that the network selection happens during connect
+     * and may take 30 seconds
+     * @deprecated Use {@code connect(String dstName, int dstPort)}
+     *             Using this method may result in reduced functionality.
+     * @param remoteAddr the address and port of the remote host to connect to.
+     * @throws IllegalArgumentException if the SocketAddress is invalid or not supported.
+     * @throws IOException if the socket is already connected or an error occurs
+     *                     while connecting
+     */
+    @Override
+    @Deprecated
+    public void connect(SocketAddress remoteAddr) throws IOException {
+        if (DBG) log("connect(remoteAddr) EX DEPRECATED");
+    }
+
+    /**
+     * Connect a duplicate socket socket to the same remote host address and port
+     * as the original with a timeout parameter.
+     * @param timeout the timeout value in milliseconds or 0 for infinite timeout
+     * @throws IOException if the socket is already connected or an error occurs
+     *                     while connecting
+     */
+    public void connect(int timeout) throws IOException {
+        if (DBG) log("connect(timeout) EX");
+    }
+
+    /**
+     * Connect a duplicate socket socket to the same remote host address and port
+     * as the original.
+     * @throws IOException if the socket is already connected or an error occurs
+     *                     while connecting
+     */
+    public void connect() throws IOException {
+        if (DBG) log("connect() EX");
+    }
+
+    /**
+     * Closes the socket.  It is not possible to reconnect or rebind to this
+     * socket thereafter which means a new socket instance has to be created.
+     * @throws IOException if an error occurs while closing the socket
+     */
+    @Override
+    public synchronized void close() throws IOException {
+        if (DBG) log("close() EX");
+    }
+
+    /**
+     * Request that a new LinkSocket be created using a different radio
+     * (such as WiFi or 3G) than the current LinkSocket.  If a different
+     * radio is available a call back will be made via {@code onBetterLinkAvail}.
+     * If unable to find a better radio, application will be notified via
+     * {@code onNewLinkUnavailable}
+     * @see LinkSocketNotifier#onBetterLinkAvailable(LinkSocket, LinkSocket)
+     * @param linkRequestReason reason for requesting a new link.
+     */
+    public void requestNewLink(LinkRequestReason linkRequestReason) {
+        if (DBG) log("requestNewLink(linkRequestReason) EX");
+    }
+
+    /**
+     * @deprecated LinkSocket will automatically pick the optimum interface
+     *             to bind to
+     * @param localAddr the specific address and port on the local machine
+     *                  to bind to
+     * @throws IOException always as this method is deprecated for LinkSocket
+     */
+    @Override
+    @Deprecated
+    public void bind(SocketAddress localAddr) throws UnsupportedOperationException {
+        if (DBG) log("bind(localAddr) EX throws IOException");
+        throw new UnsupportedOperationException("bind is deprecated for LinkSocket");
+    }
+
+    /**
+     * Reason codes an application can specify when requesting for a new link.
+     * TODO: need better documentation
+     */
+    public static final class LinkRequestReason {
+        /** No constructor */
+        private LinkRequestReason() {}
+
+        /** This link is working properly */
+        public static final int LINK_PROBLEM_NONE = 0;
+        /** This link has an unknown issue */
+        public static final int LINK_PROBLEM_UNKNOWN = 1;
+    }
+
+    /**
+     * Debug logging
+     */
+    protected static void log(String s) {
+        Log.d(TAG, s);
+    }
+}
diff --git a/core/java/android/net/LinkSocketNotifier.java b/core/java/android/net/LinkSocketNotifier.java
new file mode 100644
index 0000000..28e2834
--- /dev/null
+++ b/core/java/android/net/LinkSocketNotifier.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 java.util.Map;
+
+/**
+ * Interface used to get feedback about a {@link android.net.LinkSocket}.  Instance is optionally
+ * passed when a LinkSocket is constructed.  Multiple LinkSockets may use the same notifier.
+ * @hide
+ */
+public interface LinkSocketNotifier {
+    /**
+     * This callback function will be called if a better link
+     * becomes available.
+     * TODO - this shouldn't be checked for all cases - what's the conditional
+     *        flag?
+     * If the duplicate socket is accepted, the original will be marked invalid
+     * and additional use will throw exceptions.
+     * @param original the original LinkSocket
+     * @param duplicate the new LinkSocket that better meets the application
+     *                  requirements
+     * @return {@code true} if the application intends to use this link
+     *
+     * REM
+     * TODO - how agressive should we be?
+     * At a minimum CS tracks which LS have this turned on and tracks the requirements
+     * When a new link becomes available, automatically check if any of the LinkSockets
+     *   will care.
+     * If found, grab a refcount on the link so it doesn't go away and send notification
+     * Optionally, periodically setup connection on available networks to check for better links
+     * Maybe pass this info into the LinkFactories so condition changes can be acted on more quickly
+     */
+    public boolean onBetterLinkAvailable(LinkSocket original, LinkSocket duplicate);
+
+    /**
+     * This callback function will be called when a LinkSocket no longer has
+     * an active link.
+     * @param socket the LinkSocket that lost its link
+     *
+     * REM
+     * NetworkStateTracker tells us it is disconnected
+     * CS checks the table for LS on that link
+     * CS calls each callback (need to work out p2p cross process callback)
+     */
+    public void onLinkLost(LinkSocket socket);
+
+    /**
+     * This callback function will be called when an application calls
+     * requestNewLink on a LinkSocket but the LinkSocket is unable to find
+     * a suitable new link.
+     * @param socket the LinkSocket for which a new link was not found
+     * TODO - why the diff between initial request (sync) and requestNewLink?
+     *
+     * REM
+     * CS process of trying to find a new link must track the LS that started it
+     * on failure, call callback
+     */
+    public void onNewLinkUnavailable(LinkSocket socket);
+
+    /**
+     * This callback function will be called when any of the notification-marked
+     * capabilities of the LinkSocket (e.g. upstream bandwidth) have changed.
+     * @param socket the linkSocet for which capabilities have changed
+     * @param changedCapabilities the set of capabilities that the application
+     *                            is interested in and have changed (with new values)
+     *
+     * REM
+     * Maybe pass the interesting capabilities into the Links.
+     * Get notified of every capability change
+     * check for LinkSockets on that Link that are interested in that Capability - call them
+     */
+    public void onCapabilitiesChanged(LinkSocket socket, LinkCapabilities changedCapabilities);
+}
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 8d18eba..3df8ec0 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -16,19 +16,21 @@
 
 package android.net;
 
-import java.net.InetAddress;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 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 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.LinkProperties;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.text.TextUtils;
@@ -40,19 +42,25 @@
  *
  * {@hide}
  */
-public class MobileDataStateTracker extends NetworkStateTracker {
+public class MobileDataStateTracker implements NetworkStateTracker {
 
     private static final String TAG = "MobileDataStateTracker";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
 
     private Phone.DataState mMobileDataState;
     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 LinkProperties mLinkProperties;
+    private LinkCapabilities mLinkCapabilities;
+    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
@@ -60,35 +68,22 @@
 
     /**
      * Create a new MobileDataStateTracker
-     * @param context the application context of the caller
-     * @param target a message handler for getting callbacks about state changes
      * @param netType the ConnectivityManager network type
-     * @param apnType the Phone apnType
      * @param tag the name of this network
      */
-    public MobileDataStateTracker(Context context, Handler target, int netType, String tag) {
-        super(context, target, netType,
+    public MobileDataStateTracker(int netType, String tag) {
+        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",
@@ -99,165 +94,168 @@
                 "net.gprs.dns2",
                 "net.ppp0.dns1",
                 "net.ppp0.dns2"};
-
     }
 
     /**
-     * Begin monitoring mobile data connectivity.
+     * Begin monitoring data connectivity.
+     *
+     * @param context is the current Android context
+     * @param target is the Hander to which to return the events.
      */
-    public void startMonitoring() {
-        IntentFilter filter =
-                new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+    public void startMonitoring(Context context, Handler target) {
+        mTarget = target;
+        mContext = context;
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
         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);
-            }
-        }
-        return Phone.DataState.DISCONNECTED;
+    /**
+     * 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;
     }
 
-    private boolean isApnTypeIncluded(String typeList) {
-        /* comma seperated list - split and check */
-        if (typeList == null)
-            return false;
+    public boolean isPrivateDnsRouteSet() {
+        return mPrivateDnsRouteSet;
+    }
 
-        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;
-            }
-        }
-        return false;
+    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() {
     }
 
     private class MobileDataStateReceiver extends BroadcastReceiver {
-        ConnectivityManager mConnectivityManager;
+        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);
+                if (!TextUtils.equals(apnType, mApnType)) {
+                    return;
+                }
+                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(!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 (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " +
+                        mMobileDataState + ", reason= " +
+                        (reason == null ? "(unspecified)" : reason));
 
-                    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);
+                if (mMobileDataState != state) {
+                    mMobileDataState = state;
+                    switch (state) {
+                        case DISCONNECTED:
+                            if(isTeardownRequested()) {
+                                setTeardownRequested(false);
                             }
-                            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);
+                            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(
+                                            Context.CONNECTIVITY_SERVICE);
+                                    mConnectivityManager = IConnectivityManager.Stub.asInterface(b);
                                 }
-
-                                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) {
-                                        mConnectivityManager =
-                                                (ConnectivityManager)context.getSystemService(
-                                                Context.CONNECTIVITY_SERVICE);
-                                    }
+                                try {
                                     if (mConnectivityManager != null) {
                                         NetworkInfo info = mConnectivityManager.getNetworkInfo(
-                                                    typeToCheck);
-                                        if (info != null && info.isConnected() == true) {
+                                                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 && 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.");
-                                }
-                                mDefaultGatewayAddr = intent.getIntExtra(Phone.DATA_GATEWAY_KEY, 0);
-                                setDetailedState(DetailedState.CONNECTED, reason, apnName);
-                                break;
-                        }
+                            }
+                            if (doReset && mLinkProperties != null) {
+                                String iface = mLinkProperties.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:
+                            mLinkProperties = intent.getParcelableExtra(
+                                    Phone.DATA_LINK_PROPERTIES_KEY);
+                            if (mLinkProperties == null) {
+                                Log.d(TAG, "CONNECTED event did not supply link properties.");
+                                mLinkProperties = new LinkProperties();
+                            }
+                            mLinkCapabilities = intent.getParcelableExtra(
+                                    Phone.DATA_LINK_CAPABILITIES_KEY);
+                            if (mLinkCapabilities == null) {
+                                Log.d(TAG, "CONNECTED event did not supply link capabilities.");
+                                mLinkCapabilities = new LinkCapabilities();
+                            }
+                            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);
                 }
-                TelephonyManager tm = TelephonyManager.getDefault();
-                setRoamingStatus(tm.isNetworkRoaming());
-                setSubtype(tm.getNetworkType(), tm.getNetworkTypeName());
+            } 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);
             }
         }
     }
@@ -272,33 +270,7 @@
      * Report whether data connectivity is possible.
      */
     public boolean isAvailable() {
-        getPhoneService(false);
-
-        /*
-         * If the phone process has crashed in the past, we'll get a
-         * RemoteException and need to re-reference the service.
-         */
-        for (int retry = 0; retry < 2; retry++) {
-            if (mPhoneService == null) break;
-
-            try {
-                return mPhoneService.isDataConnectivityPossible();
-            } catch (RemoteException e) {
-                // First-time failed, get the phone service again
-                if (retry == 0) getPhoneService(true);
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * {@inheritDoc}
-     * The mobile data network subtype indicates what generation network technology is in effect,
-     * e.g., GPRS, EDGE, UMTS, etc.
-     */
-    public int getNetworkSubtype() {
-        return TelephonyManager.getDefault().getNetworkType();
+        return mNetworkInfo.isAvailable();
     }
 
     /**
@@ -350,70 +322,74 @@
     /**
      * 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}
+     * @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();
+        }
+    }
+
+    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;
     }
 
     /**
@@ -486,25 +462,6 @@
         return -1;
     }
 
-    /**
-     * 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,
-     * @return {@code true} on success, {@code false} on failure
-     */
-    @Override
-    public boolean requestRouteToHost(InetAddress hostAddress) {
-        if (DBG) {
-            Log.d(TAG, "Requested host route to " + hostAddress.getHostAddress() +
-                    " for " + mApnType + "(" + mInterfaceName + ")");
-        }
-        if (mInterfaceName != null) {
-            return NetworkUtils.addHostRoute(mInterfaceName, hostAddress, null);
-        } else {
-            return false;
-        }
-    }
-
     @Override
     public String toString() {
         StringBuffer sb = new StringBuffer("Mobile data state: ");
@@ -566,4 +523,18 @@
                 return null;
         }
     }
+
+    /**
+     * @see android.net.NetworkStateTracker#getLinkProperties()
+     */
+    public LinkProperties getLinkProperties() {
+        return new LinkProperties(mLinkProperties);
+    }
+
+    /**
+     * @see android.net.NetworkStateTracker#getLinkCapabilities()
+     */
+    public LinkCapabilities getLinkCapabilities() {
+        return new LinkCapabilities(mLinkCapabilities);
+    }
 }
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/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index c161f3a..8afdcee 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -16,367 +16,129 @@
 
 package android.net;
 
-import java.io.FileWriter;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemProperties;
 import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-
+import android.os.Handler;
 
 /**
- * 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 provides the {@link com.android.server.ConnectivityService}
+ * with three services. Events to the ConnectivityService when
+ * changes occur, an API for controlling the network and storage
+ * for network specific information.
+ *
+ * The Connectivity will call startMonitoring before any other
+ * method is called.
  *
  * {@hide}
  */
-public abstract class NetworkStateTracker extends Handler {
+public interface NetworkStateTracker {
 
-    protected NetworkInfo mNetworkInfo;
-    protected Context mContext;
-    protected Handler mTarget;
-    protected String mInterfaceName;
-    protected String[] mDnsPropNames;
-    private boolean mPrivateDnsRouteSet;
-    protected int mDefaultGatewayAddr;
-    private boolean mTeardownRequested;
-
-    private static boolean DBG = false;
-    private static final String TAG = "NetworkStateTracker";
+    /**
+     * -------------------------------------------------------------
+     * Event Interface back to ConnectivityService.
+     *
+     * The events that are to be sent back to the Handler passed
+     * to startMonitoring when the particular event occurs.
+     * -------------------------------------------------------------
+     */
 
     // Share the event space with ConnectivityService (which we can't see, but
     // must send events to).  If you change these, change ConnectivityService
     // too.
-    private static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1;
-    private static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100;
+    static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1;
+    static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100;
 
-    public static final int EVENT_STATE_CHANGED = 1;
-    public static final int EVENT_SCAN_RESULTS_AVAILABLE = 2;
     /**
-     * arg1: 1 to show, 0 to hide
-     * arg2: ID of the notification
-     * obj: Notification (if showing)
+     * The network state has changed and the NetworkInfo object
+     * contains the new state.
+     *
+     * msg.what = EVENT_STATE_CHANGED
+     * msg.obj = NetworkInfo object
      */
-    public static final int EVENT_NOTIFICATION_CHANGED = 3;
-    public static final int EVENT_CONFIGURATION_CHANGED = 4;
-    public static final int EVENT_ROAMING_CHANGED = 5;
-    public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 6;
+    public static final int EVENT_STATE_CHANGED = 1;
 
-    public NetworkStateTracker(Context context,
-            Handler target,
-            int networkType,
-            int subType,
-            String typeName,
-            String subtypeName) {
-        super();
-        mContext = context;
-        mTarget = target;
-        mTeardownRequested = false;
+    /**
+     * msg.what = EVENT_CONFIGURATION_CHANGED
+     * msg.obj = NetworkInfo object
+     */
+    public static final int EVENT_CONFIGURATION_CHANGED = 3;
 
-        this.mNetworkInfo = new NetworkInfo(networkType, subType, typeName, subtypeName);
-    }
+    /**
+     * msg.what = EVENT_RESTORE_DEFAULT_NETWORK
+     * msg.obj = FeatureUser object
+     */
+    public static final int EVENT_RESTORE_DEFAULT_NETWORK = 6;
 
-    public NetworkInfo getNetworkInfo() {
-        return mNetworkInfo;
-    }
+    /**
+     * -------------------------------------------------------------
+     * Control Interface
+     * -------------------------------------------------------------
+     */
+    /**
+     * Begin monitoring data connectivity.
+     *
+     * This is the first method called when this interface is used.
+     *
+     * @param context is the current Android context
+     * @param target is the Hander to which to return the events.
+     */
+    public void startMonitoring(Context context, Handler target);
+
+    /**
+     * Fetch NetworkInfo for the network
+     */
+    public NetworkInfo getNetworkInfo();
+
+    /**
+     * Return the LinkProperties for the connection.
+     *
+     * @return a copy of the LinkProperties, is never null.
+     */
+    public LinkProperties getLinkProperties();
+
+    /**
+     * A capability is an Integer/String pair, the capabilities
+     * are defined in the class LinkSocket#Key.
+     *
+     * @return a copy of this connections capabilities, may be empty but never null.
+     */
+    public LinkCapabilities getLinkCapabilities();
 
     /**
      * Return the system properties name associated with the tcp buffer sizes
      * for this network.
      */
-    public abstract 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.
-     */
-    public String[] getNameServers() {
-        return getNameServerList(mDnsPropNames);
-    }
-
-    /**
-     * 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.
-     */
-    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()) {
-                if (addrString != null) {
-                    try {
-                        InetAddress inetAddress = InetAddress.getByName(addrString);
-                        if (DBG) Log.d(TAG, "  adding " + addrString);
-                        if (NetworkUtils.addHostRoute(mInterfaceName, inetAddress, null)) {
-                            mPrivateDnsRouteSet = true;
-                        }
-                    } catch (UnknownHostException e) {
-                        if (DBG) Log.d(TAG, " DNS address " + addrString + " : Exception " + e);
-                    }
-                }
-            }
-        }
-    }
-
-    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)) {
-            if (DBG) {
-                Log.d(TAG, "addDefaultRoute for " + mNetworkInfo.getTypeName() +
-                        " (" + mInterfaceName + "), GatewayAddr=" + mDefaultGatewayAddr);
-            }
-            InetAddress inetAddress = NetworkUtils.intToInetAddress(mDefaultGatewayAddr);
-            if (inetAddress == null) {
-                if (DBG) Log.d(TAG, " Unable to add default route. mDefaultGatewayAddr Error");
-            } else {
-                if (!NetworkUtils.addDefaultRoute(mInterfaceName, inetAddress) && DBG) {
-                    Log.d(TAG, "  Unable to add default route.");
-                }
-            }
-        }
-    }
-
-    public void removeDefaultRoute() {
-        if (mInterfaceName != null) {
-            if (DBG) {
-                Log.d(TAG, "removeDefaultRoute for " + mNetworkInfo.getTypeName() + " (" +
-                        mInterfaceName + ")");
-            }
-            NetworkUtils.removeDefaultRoute(mInterfaceName);
-        }
-    }
-
-    /**
-     * 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() {
-        String key = getTcpBufferSizesPropName();
-        String bufferSizes = SystemProperties.get(key);
-
-        if (bufferSizes.length() == 0) {
-            Log.w(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);
-        }
-    }
-
-    /**
-     * Release the wakelock, if any, that may be held while handling a
-     * disconnect operation.
-     */
-    public void releaseWakeLock() {
-    }
-
-    /**
-     * 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 {
-                Log.w(TAG, "Invalid buffersize string: " + bufferSizes);
-            }
-        } catch (IOException e) {
-            Log.w(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();
-        }
-    }
-
-    /**
-     * 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}
-     */
-    public 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
-     */
-    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 String getTcpBufferSizesPropName();
 
     /**
      * 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();
+
+    /**
+     * Fetch default gateway address for the network
+     */
+    public int getDefaultGatewayAddr();
 
     /**
      * Tells the underlying networking system that the caller wants to
@@ -390,7 +152,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
@@ -404,23 +166,43 @@
      * implementation+feature combination, except that the value {@code -1}
      * always indicates failure.
      */
-    public abstract int stopUsingNetworkFeature(String feature, int callingPid, int callingUid);
+    public 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
+     * -------------------------------------------------------------
+     * Storage API used by ConnectivityService for saving
+     * Network specific information.
+     * -------------------------------------------------------------
      */
-    public boolean requestRouteToHost(InetAddress hostAddress) {
-        return false;
-    }
 
     /**
-     * Interprets scan results. This will be called at a safe time for
-     * processing, and from a safe thread.
+     * Check if private DNS route is set for the network
      */
-    public void interpretScanResultsAvailable() {
-    }
+    public boolean isPrivateDnsRouteSet();
+
+    /**
+     * Set a flag indicating private DNS route is set
+     */
+    public void privateDnsRouteSet(boolean enabled);
+
+    /**
+     * Check if default route is set
+     */
+    public boolean isDefaultRouteSet();
+
+    /**
+     * Set a flag indicating default route is set for the network
+     */
+    public void defaultRouteSet(boolean enabled);
+
+    /**
+     * Check if tear down was requested
+     */
+    public boolean isTeardownRequested();
+
+    /**
+     * Indicate tear down requested from connectivity
+     */
+    public void setTeardownRequested(boolean isRequested);
 
 }
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 5d4b099..01004c2 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -52,7 +52,11 @@
           int prefixLength, String gw);
 
     /** 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);
+        return intToInetAddress(addr);
+    }
+    private native static int getDefaultRouteNative(String interfaceName);
 
     /** Remove host routes that uses the named interface. */
     public native static int removeHostRoutes(String interfaceName);
@@ -194,19 +198,4 @@
         }
         return addRoute(interfaceName, dstStr, prefixLength, gwStr) == 0;
     }
-
-    public static int v4StringToInt(String str) {
-        int result = 0;
-        String[] array = str.split("\\.");
-        if (array.length != 4) return 0;
-        try {
-            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;
-        }
-        return result;
-    }
 }
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index 22c30a5..830ff06 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -16,32 +16,173 @@
 
 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.text.TextUtils;
 import android.provider.Settings;
 import android.util.Log;
 
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+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.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import junit.framework.Assert;
 
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.routing.HttpRoutePlanner;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
+import org.apache.http.protocol.HttpContext;
+
 /**
  * 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 =
+    // Used to notify an app that's caching the default connection proxy
+    // that either the default connection or its proxy has changed
+    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;
+
+    private static ConnectivityManager sConnectivityManager = 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);
+    }
+
+    // useful because it holds the processed exclusion list - don't want to reparse it each time
+    private static class ProxySpec {
+        String[] exclusionList;
+        InetSocketAddress address = null;
+        public ProxySpec() {
+            exclusionList = new String[0];
+        };
+    }
+
+    private static boolean isURLInExclusionList(String url, String[] exclusionList) {
+        if (url == 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 (isURLInExclusionList(url, sGlobalProxySpec.exclusionList)) {
+                    // Return no proxy
+                    retval = java.net.Proxy.NO_PROXY;
+                } else {
+                    retval =
+                        new java.net.Proxy(java.net.Proxy.Type.HTTP, sGlobalProxySpec.address);
+                }
+            } else {
+                retval = getDefaultProxy(ctx, url);
+            }
+        } finally {
+            sProxyInfoLock.readLock().unlock();
+        }
+        if ((retval != java.net.Proxy.NO_PROXY) && (isLocalHost(url))) {
+            retval = java.net.Proxy.NO_PROXY;
+        }
+        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,83 +190,82 @@
      *         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;
-            }
-            return host.substring(0, i);
+    public static final String getHost(Context ctx) {
+        java.net.Proxy proxy = getProxy(ctx, null);
+        if (proxy == java.net.Proxy.NO_PROXY) return null;
+        try {
+            return ((InetSocketAddress)(proxy.address())).getHostName();
+        } catch (Exception e) {
+            return null;
         }
-        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;
-            }
-            if (DEBUG) {
-                Assert.assertTrue(i < host.length());
-            }
-            return Integer.parseInt(host.substring(i+1));
+    public static final int getPort(Context ctx) {
+        java.net.Proxy proxy = getProxy(ctx, null);
+        if (proxy == java.net.Proxy.NO_PROXY) return -1;
+        try {
+            return ((InetSocketAddress)(proxy.address())).getPort();
+        } catch (Exception e) {
+            return -1;
         }
-        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() {
-        String host = SystemProperties.get("net.gprs.http-proxy");
-        if (host != null) {
-            Uri u = Uri.parse(host);
-            host = u.getHost();
-            return host;
-        } else {
-            return null;
-        }
+    public static final String getDefaultHost() {
+        return null;
     }
 
+    // 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() {
-        String host = SystemProperties.get("net.gprs.http-proxy");
-        if (host != null) {
-            Uri u = Uri.parse(host);
-            return u.getPort();
-        } else {
-            return -1;
-        }
+    public static final int getDefaultPort() {
+        return -1;
     }
 
+    // TODO - cache the details for each network so we don't have to fetch and parse
+    // on each request
+    private static final java.net.Proxy getDefaultProxy(Context context, String url) {
+        if (sConnectivityManager == null) {
+            sConnectivityManager = (ConnectivityManager)context.getSystemService(
+                    Context.CONNECTIVITY_SERVICE);
+        }
+        if (sConnectivityManager == null) return java.net.Proxy.NO_PROXY;
+
+        LinkProperties linkProperties = sConnectivityManager.getActiveLinkProperties();
+
+        if (linkProperties != null) {
+            ProxyProperties proxyProperties = linkProperties.getHttpProxy();
+
+            if (proxyProperties != null) {
+                String exclusionList = proxyProperties.getExclusionList();
+                SocketAddress socketAddr = proxyProperties.getSocketAddress();
+                if (socketAddr != null) {
+                    String[] parsedExclusionArray =
+                            parsedExclusionArray = parseExclusionList(exclusionList);
+                    if (!isURLInExclusionList(url, parsedExclusionArray)) {
+                        return new java.net.Proxy(java.net.Proxy.Type.HTTP, socketAddr);
+                    }
+                }
+            }
+        }
+        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 +278,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,25 +311,170 @@
         } catch (IllegalArgumentException iex) {
             // Ignore (URI.create)
         }
-
         return false;
     }
 
-    static final private boolean isNetworkWifi(Context context) {
-        if (context == null) {
-            return false;
+    private static class SettingsObserver extends ContentObserver {
+
+        private Context mContext;
+
+        SettingsObserver(Context ctx) {
+            super(new Handler(ctx.getMainLooper()));
+            mContext = ctx;
         }
 
-        final ConnectivityManager connectivity = (ConnectivityManager)
-            context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        if (connectivity != null) {
-            final NetworkInfo info = connectivity.getActiveNetworkInfo();
-            if (info != null &&
-                    info.getType() == ConnectivityManager.TYPE_WIFI) {
-                return true;
+        @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();
+        try {
+            sGlobalProxyChangedObserver = new SettingsObserver(ctx);
+        } finally {
+            // 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 (TextUtils.isEmpty(proxyHost)) {
+            // 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);
+        ProxySpec tmpProxySpec = null;
+        if (proxyHost != null) {
+            tmpProxySpec = new ProxySpec();
+            tmpProxySpec.address = new InetSocketAddress(host, port);
+            tmpProxySpec.exclusionList = parseExclusionList(exclusionListSpec);
+        }
+        sProxyInfoLock.readLock().unlock();
+        sProxyInfoLock.writeLock().lock();
+        sGlobalProxySpec = tmpProxySpec;
+        sProxyInfoLock.readLock().lock();
+        sProxyInfoLock.writeLock().unlock();
+    }
+
+    private static String[] parseExclusionList(String exclusionList) {
+        String[] processedArray = new String[0];
+        if (!TextUtils.isEmpty(exclusionList)) {
+            String[] exclusionListArray = exclusionList.toLowerCase().split(",");
+            processedArray = new String[exclusionListArray.length];
+            for (int i = 0; i < exclusionListArray.length; i++) {
+                String entry = exclusionListArray[i].trim();
+                if (entry.startsWith(".")) {
+                    entry = entry.substring(1);
+                }
+                processedArray[i] = entry;
             }
         }
-
-        return false;
+        return processedArray;
     }
-};
+
+    /**
+     * 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();
+            }
+        }
+    }
+
+    static class AndroidProxySelectorRoutePlanner
+            extends org.apache.http.impl.conn.ProxySelectorRoutePlanner {
+
+        private Context mContext;
+
+        public AndroidProxySelectorRoutePlanner(SchemeRegistry schreg, ProxySelector prosel,
+                Context context) {
+            super(schreg, prosel);
+            mContext = context;
+        }
+
+        @Override
+        protected java.net.Proxy chooseProxy(List<java.net.Proxy> proxies, HttpHost target,
+                HttpRequest request, HttpContext context) {
+            return getProxy(mContext, target.getHostName());
+        }
+
+        @Override
+        protected HttpHost determineProxy(HttpHost target, HttpRequest request,
+                HttpContext context) {
+            return getPreferredHttpHost(mContext, target.getHostName());
+        }
+
+        @Override
+        public HttpRoute determineRoute(HttpHost target, HttpRequest request,
+                HttpContext context) {
+            HttpHost proxy = getPreferredHttpHost(mContext, target.getHostName());
+            if (proxy == null) {
+                return new HttpRoute(target);
+            } else {
+                return new HttpRoute(target, null, proxy, false);
+            }
+        }
+    }
+
+    /** @hide */
+    public static final HttpRoutePlanner getAndroidProxySelectorRoutePlanner(Context context) {
+        AndroidProxySelectorRoutePlanner ret = new AndroidProxySelectorRoutePlanner(
+                new SchemeRegistry(), ProxySelector.getDefault(), context);
+        return ret;
+    }
+}
diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java
new file mode 100644
index 0000000..5fd0d89
--- /dev/null
+++ b/core/java/android/net/ProxyProperties.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 android.net;
+
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+
+/**
+ * A container class for the http proxy info
+ * @hide
+ */
+public class ProxyProperties implements Parcelable {
+
+    private InetSocketAddress mProxy;
+    private String mExclusionList;
+
+    public ProxyProperties() {
+    }
+
+    // copy constructor instead of clone
+    public ProxyProperties(ProxyProperties source) {
+        if (source != null) {
+            mProxy = source.getSocketAddress();
+            String exclusionList = source.getExclusionList();
+            if (exclusionList != null) {
+                mExclusionList = new String(exclusionList);
+            }
+        }
+    }
+
+    public InetSocketAddress getSocketAddress() {
+        return mProxy;
+    }
+
+    public void setSocketAddress(InetSocketAddress proxy) {
+        mProxy = proxy;
+    }
+
+    public String getExclusionList() {
+        return mExclusionList;
+    }
+
+    public void setExclusionList(String exclusionList) {
+        mExclusionList = exclusionList;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        if (mProxy != null) {
+            sb.append(mProxy.toString());
+            if (mExclusionList != null) {
+                    sb.append(" xl=").append(mExclusionList);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Implement the Parcelable interface
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        String host = null;
+        if (mProxy != null) {
+            try {
+                InetAddress addr = mProxy.getAddress();
+                if (addr != null) {
+                    host = addr.getHostAddress();
+                } else {
+                    /* Does not resolve when addr is null */
+                    host = mProxy.getHostName();
+                }
+            } catch (Exception e) { }
+        }
+
+        if (host != null) {
+            dest.writeByte((byte)1);
+            dest.writeString(host);
+            dest.writeInt(mProxy.getPort());
+        } else {
+            dest.writeByte((byte)0);
+        }
+        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 {
+                        String host = in.readString();
+                        int port = in.readInt();
+                        proxyProperties.setSocketAddress(InetSocketAddress.createUnresolved(
+                                host, port));
+                    } catch (IllegalArgumentException e) { }
+                }
+                proxyProperties.setExclusionList(in.readString());
+                return proxyProperties;
+            }
+
+            public ProxyProperties[] newArray(int size) {
+                return new ProxyProperties[size];
+            }
+        };
+}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 47faaba..3b21590 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -28,8 +28,10 @@
 import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.RandomAccess;
+import java.util.Set;
 
 /**
  * Immutable URI reference. A URI reference includes a URI and a fragment, the
@@ -47,7 +49,7 @@
     /*
 
     This class aims to do as little up front work as possible. To accomplish
-    that, we vary the implementation dependending on what the user passes in.
+    that, we vary the implementation depending on what the user passes in.
     For example, we have one implementation if the user passes in a
     URI string (StringUri) and another if the user passes in the
     individual components (OpaqueUri).
@@ -1253,14 +1255,16 @@
      * concurrent use.
      *
      * <p>An absolute hierarchical URI reference follows the pattern:
-     * {@code &lt;scheme&gt;://&lt;authority&gt;&lt;absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
+     * {@code <scheme>://<authority><absolute path>?<query>#<fragment>}
      *
      * <p>Relative URI references (which are always hierarchical) follow one
-     * of two patterns: {@code &lt;relative or absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
-     * or {@code //&lt;authority&gt;&lt;absolute path&gt;?&lt;query&gt;#&lt;fragment&gt;}
+     * of two patterns: {@code <relative or absolute path>?<query>#<fragment>}
+     * or {@code //<authority><absolute path>?<query>#<fragment>}
      *
      * <p>An opaque URI follows this pattern:
-     * {@code &lt;scheme&gt;:&lt;opaque part&gt;#&lt;fragment&gt;}
+     * {@code <scheme>:<opaque part>#<fragment>}
+     * 
+     * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
      */
     public static final class Builder {
 
@@ -1447,6 +1451,13 @@
         }
 
         /**
+         * Clears the the previously set query.
+         */
+        public Builder clearQuery() {
+          return query((Part) null);
+        }
+
+        /**
          * Constructs a Uri with the current attributes.
          *
          * @throws UnsupportedOperationException if the URI is opaque and the
@@ -1491,19 +1502,60 @@
     }
 
     /**
+     * Returns a set of the unique names of all query parameters. Iterating
+     * over the set will return the names in order of their first occurrence.
+     *
+     * @throws UnsupportedOperationException if this isn't a hierarchical URI
+     *
+     * @return a set of decoded names
+     */
+    public Set<String> getQueryParameterNames() {
+        if (isOpaque()) {
+            throw new UnsupportedOperationException(NOT_HIERARCHICAL);
+        }
+
+        String query = getEncodedQuery();
+        if (query == null) {
+            return Collections.emptySet();
+        }
+
+        Set<String> names = new LinkedHashSet<String>();
+        int start = 0;
+        do {
+            int next = query.indexOf('&', start);
+            int end = (next == -1) ? query.length() : next;
+
+            int separator = query.indexOf('=', start);
+            if (separator > end || separator == -1) {
+                separator = end;
+            }
+
+            String name = query.substring(start, separator);
+            names.add(decode(name));
+
+            // Move start to end of name.
+            start = end + 1;
+        } while (start < query.length());
+
+        return Collections.unmodifiableSet(names);
+    }
+
+    /**
      * Searches the query string for parameter values with the given key.
      *
      * @param key which will be encoded
      *
      * @throws UnsupportedOperationException if this isn't a hierarchical URI
      * @throws NullPointerException if key is null
-     *
      * @return a list of decoded values
      */
     public List<String> getQueryParameters(String key) {
         if (isOpaque()) {
             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
         }
+        if (key == null) {
+          throw new NullPointerException("key");
+        }
 
         String query = getEncodedQuery();
         if (query == null) {
@@ -1517,39 +1569,34 @@
             throw new AssertionError(e);
         }
 
-        // Prepend query with "&" making the first parameter the same as the
-        // rest.
-        query = "&" + query;
-
-        // Parameter prefix.
-        String prefix = "&" + encodedKey + "=";
-
         ArrayList<String> values = new ArrayList<String>();
 
         int start = 0;
-        int length = query.length();
-        while (start < length) {
-            start = query.indexOf(prefix, start);
+        do {
+            int nextAmpersand = query.indexOf('&', start);
+            int end = nextAmpersand != -1 ? nextAmpersand : query.length();
 
-            if (start == -1) {
-                // No more values.
+            int separator = query.indexOf('=', start);
+            if (separator > end || separator == -1) {
+                separator = end;
+            }
+
+            if (separator - start == encodedKey.length()
+                    && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
+                if (separator == end) {
+                  values.add("");
+                } else {
+                  values.add(decode(query.substring(separator + 1, end)));
+                }
+            }
+
+            // Move start to end of name.
+            if (nextAmpersand != -1) {
+                start = nextAmpersand + 1;
+            } else {
                 break;
             }
-
-            // Move start to start of value.
-            start += prefix.length();
-
-            // Find end of value.
-            int end = query.indexOf('&', start);
-            if (end == -1) {
-                end = query.length();
-            }
-
-            String value = query.substring(start, end);
-            values.add(decode(value));
-
-            start = end;
-        }
+        } while (true);
 
         return Collections.unmodifiableList(values);
     }
@@ -1560,7 +1607,6 @@
      * @param key which will be encoded
      * @throws UnsupportedOperationException if this isn't a hierarchical URI
      * @throws NullPointerException if key is null
-     *
      * @return the decoded value or null if no parameter is found
      */
     public String getQueryParameter(String key) {
@@ -1568,7 +1614,7 @@
             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
         }
         if (key == null) {
-          throw new NullPointerException("key");
+            throw new NullPointerException("key");
         }
 
         final String query = getEncodedQuery();
@@ -1577,37 +1623,54 @@
         }
 
         final String encodedKey = encode(key, null);
-        final int encodedKeyLength = encodedKey.length();
+        final int length = query.length();
+        int start = 0;
+        do {
+            int nextAmpersand = query.indexOf('&', start);
+            int end = nextAmpersand != -1 ? nextAmpersand : length;
 
-        int encodedKeySearchIndex = 0;
-        final int encodedKeySearchEnd = query.length() - (encodedKeyLength + 1);
+            int separator = query.indexOf('=', start);
+            if (separator > end || separator == -1) {
+                separator = end;
+            }
 
-        while (encodedKeySearchIndex <= encodedKeySearchEnd) {
-            int keyIndex = query.indexOf(encodedKey, encodedKeySearchIndex);
-            if (keyIndex == -1) {
-                break;
-            }
-            final int equalsIndex = keyIndex + encodedKeyLength;
-            if (equalsIndex >= query.length()) {
-                break;
-            }
-            if (query.charAt(equalsIndex) != '=') {
-                encodedKeySearchIndex = equalsIndex + 1;
-                continue;
-            }
-            if (keyIndex == 0 || query.charAt(keyIndex - 1) == '&') {
-                int end = query.indexOf('&', equalsIndex);
-                if (end == -1) {
-                    end = query.length();
+            if (separator - start == encodedKey.length()
+                    && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
+                if (separator == end) {
+                  return "";
+                } else {
+                  return decode(query.substring(separator + 1, end));
                 }
-                return decode(query.substring(equalsIndex + 1, end));
-            } else {
-                encodedKeySearchIndex = equalsIndex + 1;
             }
-        }
+
+            // Move start to end of name.
+            if (nextAmpersand != -1) {
+                start = nextAmpersand + 1;
+            } else {
+                break;
+            }
+        } while (true);
         return null;
     }
 
+    /**
+     * Searches the query string for the first value with the given key and interprets it
+     * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
+     * else is interpreted as <code>true</code>.
+     *
+     * @param key which will be decoded
+     * @param defaultValue the default value to return if there is no query parameter for key
+     * @return the boolean interpretation of the query parameter key
+     */
+    public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
+        String flag = getQueryParameter(key);
+        if (flag == null) {
+            return defaultValue;
+        }
+        flag = flag.toLowerCase();
+        return (!"false".equals(flag) && !"0".equals(flag));
+    }
+
     /** Identifies a null parcelled Uri. */
     private static final int NULL_TYPE_ID = 0;
 
diff --git a/core/java/android/net/WebAddress.java b/core/java/android/net/WebAddress.java
index 4101ab4..9c4d6e8 100644
--- a/core/java/android/net/WebAddress.java
+++ b/core/java/android/net/WebAddress.java
@@ -56,7 +56,7 @@
     static Pattern sAddressPattern = Pattern.compile(
             /* scheme    */ "(?:(http|https|file)\\:\\/\\/)?" +
             /* authority */ "(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?:\\:[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" +
-            /* host      */ "([-" + GOOD_IRI_CHAR + "%_]+(?:\\.[-" + GOOD_IRI_CHAR + "%_]+)*|\\[[0-9a-fA-F:\\.]+\\])?" +
+            /* host      */ "([" + GOOD_IRI_CHAR + "%_-][" + GOOD_IRI_CHAR + "%_\\.-]*|\\[[0-9a-fA-F:\\.]+\\])?" +
             /* port      */ "(?:\\:([0-9]*))?" +
             /* path      */ "(\\/?[^#]*)?" +
             /* anchor    */ ".*", Pattern.CASE_INSENSITIVE);
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 4ca5903..503c470 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 5fb1d7c..f0309d6 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
@@ -123,6 +123,16 @@
  *     <li>The task can be executed only once (an exception will be thrown if
  *     a second execution is attempted.)</li>
  * </ul>
+ *
+ * <h2>Memory observability</h2>
+ * <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following
+ * operations are safe without explicit synchronizations.</p>
+ * <ul>
+ *     <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them
+ *     in {@link #doInBackground}.
+ *     <li>Set member fields in {@link #doInBackground}, and refer to them in
+ *     {@link #onProgressUpdate} and {@link #onPostExecute}.
+ * </ul>
  */
 public abstract class AsyncTask<Params, Progress, Result> {
     private static final String LOG_TAG = "AsyncTask";
@@ -175,6 +185,11 @@
         FINISHED,
     }
 
+    /** @hide Used to force static handler to be created. */
+    public static void init() {
+        sHandler.getLooper();
+    }
+
     /**
      * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
      */
@@ -187,6 +202,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 +428,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 8925bd0..9268cd1 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -199,6 +199,18 @@
          * </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> The default theme for applications is now dark holographic:
+         *      {@link android.R.style#Theme_Holo}.
+         * </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..2b4f39a 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,22 @@
     }
 
     /**
+     * Writes native heap data to the specified file descriptor.
+     *
+     * @hide
+     */
+    public static native void dumpNativeHeap(FileDescriptor fd);
+
+    /**
+      * Returns a count of the extant instances of a class.
+     *
+     * @hide
+     */
+    public static long countInstancesOfClass(Class cls) {
+        return VMDebug.countInstancesOfClass(cls);
+    }
+
+    /**
      * 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/FileUtils.java b/core/java/android/os/FileUtils.java
index a17b7fe..72e21de 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -85,6 +85,8 @@
 
     public static native int getPermissions(String file, int[] outPermissions);
 
+    public static native int setUMask(int mask);
+
     /** returns the FAT file system volume ID for the volume mounted 
      * at the given mount point, or -1 for failure
      * @param mount point for FAT volume
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 3b2bf1e..165e438 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -58,7 +58,7 @@
  * they create.  You can create your own threads, and communicate back with
  * the main application thread through a Handler.  This is done by calling
  * the same <em>post</em> or <em>sendMessage</em> methods as before, but from
- * your new thread.  The given Runnable or Message will than be scheduled
+ * your new thread.  The given Runnable or Message will then be scheduled
  * in the Handler's message queue and processed when appropriate.
  */
 public class Handler {
diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java
index a81e16b..f82702a 100644
--- a/core/java/android/os/MemoryFile.java
+++ b/core/java/android/os/MemoryFile.java
@@ -58,7 +58,6 @@
     private int mAddress;   // address of ashmem memory
     private int mLength;    // total length of our ashmem region
     private boolean mAllowPurging = false;  // true if our ashmem region is unpinned
-    private final boolean mOwnsRegion;  // false if this is a ref to an existing ashmem region
 
     /**
      * Allocates a new ashmem region. The region is initially not purgable.
@@ -70,38 +69,11 @@
     public MemoryFile(String name, int length) throws IOException {
         mLength = length;
         mFD = native_open(name, length);
-        mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
-        mOwnsRegion = true;
-    }
-
-    /**
-     * Creates a reference to an existing memory file. Changes to the original file
-     * will be available through this reference.
-     * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail.
-     *
-     * @param fd File descriptor for an existing memory file, as returned by
-     *        {@link #getFileDescriptor()}. This file descriptor will be closed
-     *        by {@link #close()}.
-     * @param length Length of the memory file in bytes.
-     * @param mode File mode. Currently only "r" for read-only access is supported.
-     * @throws NullPointerException if <code>fd</code> is null.
-     * @throws IOException If <code>fd</code> does not refer to an existing memory file,
-     *         or if the file mode of the existing memory file is more restrictive
-     *         than <code>mode</code>.
-     *
-     * @hide
-     */
-    public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException {
-        if (fd == null) {
-            throw new NullPointerException("File descriptor is null.");
+        if (length > 0) {
+            mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
+        } else {
+            mAddress = 0;
         }
-        if (!isMemoryFile(fd)) {
-            throw new IllegalArgumentException("Not a memory file.");
-        }
-        mLength = length;
-        mFD = fd;
-        mAddress = native_mmap(mFD, length, modeToProt(mode));
-        mOwnsRegion = false;
     }
 
     /**
@@ -122,7 +94,7 @@
      *
      * @hide
      */
-    public void deactivate() {
+    void deactivate() {
         if (!isDeactivated()) {
             try {
                 native_munmap(mAddress, mLength);
@@ -181,9 +153,6 @@
      * @return previous value of allowPurging
      */
     synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
-        if (!mOwnsRegion) {
-            throw new IOException("Only the owner can make ashmem regions purgable.");
-        }
         boolean oldValue = mAllowPurging;
         if (oldValue != allowPurging) {
             native_pin(mFD, !allowPurging);
@@ -260,28 +229,7 @@
     }
 
     /**
-     * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()}
-     * for caveats. This must be here to allow classes outside <code>android.os</code< to
-     * make ParcelFileDescriptors from MemoryFiles, as
-     * {@link ParcelFileDescriptor#ParcelFileDescriptor(FileDescriptor)} is package private.
-     *
-     *
-     * @return The file descriptor owned by this memory file object.
-     *         The file descriptor is not duplicated.
-     * @throws IOException If the memory file has been closed.
-     *
-     * @hide
-     */
-    public ParcelFileDescriptor getParcelFileDescriptor() throws IOException {
-        FileDescriptor fd = getFileDescriptor();
-        return fd != null ? new ParcelFileDescriptor(fd) : null;
-    }
-
-    /**
-     * Gets a FileDescriptor for the memory file. Note that this file descriptor
-     * is only safe to pass to {@link #MemoryFile(FileDescriptor,int)}). It
-     * should not be used with file descriptor operations that expect a file descriptor
-     * for a normal file.
+     * Gets a FileDescriptor for the memory file.
      *
      * The returned file descriptor is not duplicated.
      *
@@ -294,17 +242,6 @@
     }
 
     /**
-     * Checks whether the given file descriptor refers to a memory file.
-     *
-     * @throws IOException If <code>fd</code> is not a valid file descriptor.
-     *
-     * @hide
-     */
-    public static boolean isMemoryFile(FileDescriptor fd) throws IOException {
-        return (native_get_size(fd) >= 0);
-    }
-
-    /**
      * Returns the size of the memory file that the file descriptor refers to,
      * or -1 if the file descriptor does not refer to a memory file.
      *
@@ -316,20 +253,6 @@
         return native_get_size(fd);
     }
 
-    /**
-     * Converts a file mode string to a <code>prot</code> value as expected by
-     * native_mmap().
-     *
-     * @throws IllegalArgumentException if the file mode is invalid.
-     */
-    private static int modeToProt(String mode) {
-        if ("r".equals(mode)) {
-            return PROT_READ;
-        } else {
-            throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'");
-        }
-    }
-
     private class MemoryInputStream extends InputStream {
 
         private int mMark = 0;
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 49b72fe..eb941e4 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -73,7 +73,18 @@
      * receiver.
      */
     public Messenger replyTo;
-    
+
+    /** If set message is in use */
+    /*package*/ static final int FLAG_IN_USE = 1;
+
+    /** Flags reserved for future use (All are reserved for now) */
+    /*package*/ static final int FLAGS_RESERVED = ~FLAG_IN_USE;
+
+    /** Flags to clear in the copyFrom method */
+    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAGS_RESERVED | FLAG_IN_USE;
+
+    /*package*/ int flags;
+
     /*package*/ long when;
     
     /*package*/ Bundle data;
@@ -253,6 +264,7 @@
      * target/callback of the original message.
      */
     public void copyFrom(Message o) {
+        this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
         this.what = o.what;
         this.arg1 = o.arg1;
         this.arg2 = o.arg2;
@@ -350,6 +362,7 @@
     }
 
     /*package*/ void clearForRecycle() {
+        flags = 0;
         what = 0;
         arg1 = 0;
         arg2 = 0;
@@ -361,6 +374,14 @@
         data = null;
     }
 
+    /*package*/ boolean isInUse() {
+        return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
+    }
+
+    /*package*/ void markInUse() {
+        flags |= FLAG_IN_USE;
+    }
+
     /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
     */
     public Message() {
@@ -453,4 +474,3 @@
         replyTo = Messenger.readMessengerOrNullFromParcel(source);
     }
 }
-
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 6237c0e..be5b685 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -124,6 +124,7 @@
                     if (now >= when) {
                         mMessages = msg.next;
                         if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
+                        msg.markInUse();
                         return msg;
                     } else {
                         nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
@@ -177,7 +178,7 @@
     }
 
     final boolean enqueueMessage(Message msg, long when) {
-        if (msg.when != 0) {
+        if (msg.isInUse()) {
             throw new AndroidRuntimeException(msg
                     + " This message is already in use.");
         }
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 9d213b3..c1a1809 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -134,6 +134,45 @@
     private static native FileDescriptor getFileDescriptorFromSocket(Socket socket);
 
     /**
+     * Create two ParcelFileDescriptors structured as a data pipe.  The first
+     * ParcelFileDescriptor in the returned array is the read side; the second
+     * is the write side.
+     */
+    public static ParcelFileDescriptor[] createPipe() throws IOException {
+        FileDescriptor[] fds = new FileDescriptor[2];
+        int res = createPipeNative(fds);
+        if (res == 0) {
+            ParcelFileDescriptor[] pfds = new ParcelFileDescriptor[2];
+            pfds[0] = new ParcelFileDescriptor(fds[0]);
+            pfds[1] = new ParcelFileDescriptor(fds[1]);
+            return pfds;
+        }
+        throw new IOException("Unable to create pipe: errno=" + -res);
+    }
+
+    private static native int createPipeNative(FileDescriptor[] outFds);
+
+    /**
+     * Gets a file descriptor for a read-only copy of the given data.
+     *
+     * @param data Data to copy.
+     * @param name Name for the shared memory area that may back the file descriptor.
+     *        This is purely informative and may be {@code null}.
+     * @return A ParcelFileDescriptor.
+     * @throws IOException if there is an error while creating the shared memory area.
+     */
+    public static ParcelFileDescriptor fromData(byte[] data, String name) throws IOException {
+        if (data == null) return null;
+        MemoryFile file = new MemoryFile(name, data.length);
+        if (data.length > 0) {
+            file.writeBytes(data, 0, 0, data.length);
+        }
+        file.deactivate();
+        FileDescriptor fd = file.getFileDescriptor();
+        return fd != null ? new ParcelFileDescriptor(fd) : null;
+    }
+
+    /**
      * Retrieve the actual FileDescriptor associated with this object.
      * 
      * @return Returns the FileDescriptor associated with this object.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index f695dbb..1235b4d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -86,6 +86,12 @@
     public static final int WIFI_UID = 1010;
 
     /**
+     * Defines the GID for the group that allows write access to the SD card.
+     * @hide
+     */
+    public static final int SDCARD_RW_GID = 1015;
+
+    /**
      * Defines the start of a range of UIDs (and GIDs), going from this
      * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
      * to applications.
diff --git a/core/java/android/os/SystemService.java b/core/java/android/os/SystemService.java
index 447cd1f..da27db5 100644
--- a/core/java/android/os/SystemService.java
+++ b/core/java/android/os/SystemService.java
@@ -28,4 +28,9 @@
     public static void stop(String name) {
         SystemProperties.set("ctl.stop", name);
     }
+
+    /** Request that the init daemon restart a named service. */
+    public static void restart(String name) {
+        SystemProperties.set("ctl.restart", name);
+    }
 }
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 2ebd049..8554ece 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -321,7 +321,6 @@
      *
      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
      *
-     * @hide
      */
     public void registerListener(StorageEventListener listener) {
         if (listener == null) {
@@ -338,7 +337,6 @@
      *
      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
      *
-     * @hide
      */
     public void unregisterListener(StorageEventListener listener) {
         if (listener == null) {
@@ -359,8 +357,6 @@
 
     /**
      * Enables USB Mass Storage (UMS) on the device.
-     *
-     * @hide
      */
     public void enableUsbMassStorage() {
         try {
@@ -372,8 +368,6 @@
 
     /**
      * Disables USB Mass Storage (UMS) on the device.
-     *
-     * @hide
      */
     public void disableUsbMassStorage() {
         try {
@@ -386,8 +380,6 @@
     /**
      * Query if a USB Mass Storage (UMS) host is connected.
      * @return true if UMS host is connected.
-     *
-     * @hide
      */
     public boolean isUsbMassStorageConnected() {
         try {
@@ -401,8 +393,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 f5e1bac..282417d 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -206,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 dcfe980..0000000
--- a/core/java/android/pim/vcard/JapaneseUtils.java
+++ /dev/null
@@ -1,379 +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 {
-        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");
-    }
-
-    /**
-     * Returns half-width version of that character if possible. Returns null if not possible
-     * @param ch input character
-     * @return CharSequence object if the mapping for ch exists. Return null otherwise.
-     */
-    public static String tryGetHalfWidthText(final 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 58f6ebe..0000000
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ /dev/null
@@ -1,2147 +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.Base64;
-import android.util.CharsetUtils;
-import android.util.Log;
-
-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;
-
-/**
- * <p>
- * The class which lets users create their own vCard String. Typical usage is as follows:
- * </p>
- * <pre class="prettyprint">final VCardBuilder builder = new VCardBuilder(vcardType);
- * builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
- *     .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
- *     .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
- *     .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
- *     .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
- *     .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
- *     .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
- *     .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
- *     .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
- *     .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
- *     .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
- *     .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
- * return builder.toString();</pre>
- */
-public class VCardBuilder {
-    private static final String LOG_TAG = "VCardBuilder";
-
-    // 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=" + VCardConstants.PARAM_ENCODING_QP;
-    private static final String VCARD_PARAM_ENCODING_BASE64_V21 =
-            "ENCODING=" + VCardConstants.PARAM_ENCODING_BASE64;
-    private static final String VCARD_PARAM_ENCODING_BASE64_AS_B =
-            "ENCODING=" + VCardConstants.PARAM_ENCODING_B;
-
-    private static final String SHIFT_JIS = "SHIFT_JIS";
-
-    private final int mVCardType;
-
-    private final boolean mIsV30OrV40;
-    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 mAppendTypeParamName;
-    private final boolean mRefrainsQPToNameProperties;
-    private final boolean mNeedsToConvertPhoneticString;
-
-    private final boolean mShouldAppendCharsetParam;
-
-    private final String mCharset;
-    private final String mVCardCharsetParameter;
-
-    private StringBuilder mBuilder;
-    private boolean mEndAppended;
-
-    public VCardBuilder(final int vcardType) {
-        // Default charset should be used
-        this(vcardType, null);
-    }
-
-    /**
-     * @param vcardType
-     * @param charset If null, we use default charset for export.
-     * @hide
-     */
-    public VCardBuilder(final int vcardType, String charset) {
-        mVCardType = vcardType;
-
-        if (VCardConfig.isVersion40(vcardType)) {
-            Log.w(LOG_TAG,
-                    "Should not use vCard 4.0 when building vCard. " +
-                    "It is not officially published yet.");
-        }
-
-        mIsV30OrV40 = VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(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);
-        mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
-        mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
-        mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
-
-        // vCard 2.1 requires charset.
-        // vCard 3.0 does not allow it but we found some devices use it to determine
-        // the exact charset.
-        // We currently append it only when charset other than UTF_8 is used.
-        mShouldAppendCharsetParam =
-                !(VCardConfig.isVersion30(vcardType) && "UTF-8".equalsIgnoreCase(charset));
-
-        if (VCardConfig.isDoCoMo(vcardType)) {
-            if (!SHIFT_JIS.equalsIgnoreCase(charset)) {
-                Log.w(LOG_TAG,
-                        "The charset \"" + charset + "\" is used while "
-                        + SHIFT_JIS + " is needed to be used.");
-                if (TextUtils.isEmpty(charset)) {
-                    mCharset = SHIFT_JIS;
-                } else {
-                    try {
-                        charset = CharsetUtils.charsetForVendor(charset).name();
-                    } catch (UnsupportedCharsetException e) {
-                        Log.i(LOG_TAG,
-                                "Career-specific \"" + charset + "\" was not found (as usual). "
-                                + "Use it as is.");
-                    }
-                    mCharset = charset;
-                }
-            } else {
-                if (mIsDoCoMo) {
-                    try {
-                        charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
-                    } catch (UnsupportedCharsetException e) {
-                        Log.e(LOG_TAG,
-                                "DoCoMo-specific SHIFT_JIS was not found. "
-                                + "Use SHIFT_JIS as is.");
-                        charset = SHIFT_JIS;
-                    }
-                } else {
-                    try {
-                        charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
-                    } catch (UnsupportedCharsetException e) {
-                        Log.e(LOG_TAG,
-                                "Career-specific SHIFT_JIS was not found. "
-                                + "Use SHIFT_JIS as is.");
-                        charset = SHIFT_JIS;
-                    }
-                }
-                mCharset = charset;
-            }
-            mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
-        } else {
-            if (TextUtils.isEmpty(charset)) {
-                Log.i(LOG_TAG,
-                        "Use the charset \"" + VCardConfig.DEFAULT_EXPORT_CHARSET
-                        + "\" for export.");
-                mCharset = VCardConfig.DEFAULT_EXPORT_CHARSET;
-                mVCardCharsetParameter = "CHARSET=" + VCardConfig.DEFAULT_EXPORT_CHARSET;
-            } else {
-                try {
-                    charset = CharsetUtils.charsetForVendor(charset).name();
-                } catch (UnsupportedCharsetException e) {
-                    Log.i(LOG_TAG,
-                            "Career-specific \"" + charset + "\" was not found (as usual). "
-                            + "Use it as is.");
-                }
-                mCharset = charset;
-                mVCardCharsetParameter = "CHARSET=" + charset;
-            }
-        }
-        clear();
-    }
-
-    public void clear() {
-        mBuilder = new StringBuilder();
-        mEndAppended = false;
-        appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
-        if (VCardConfig.isVersion40(mVCardType)) {
-            appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V40);
-        } else if (VCardConfig.isVersion30(mVCardType)) {
-            appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30);
-        } else {
-            if (!VCardConfig.isVersion21(mVCardType)) {
-                Log.w(LOG_TAG, "Unknown vCard version detected.");
-            }
-            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;
-    }
-
-    /**
-     * To avoid unnecessary complication in logic, we use this method to construct N, FN
-     * properties for vCard 4.0.
-     */
-    private VCardBuilder appendNamePropertiesV40(final List<ContentValues> contentValuesList) {
-        if (mIsDoCoMo || mNeedsToConvertPhoneticString) {
-            // Ignore all flags that look stale from the view of vCard 4.0 to
-            // simplify construction algorithm. Actually we don't have any vCard file
-            // available from real world yet, so we may need to re-enable some of these
-            // in the future.
-            Log.w(LOG_TAG, "Invalid flag is used in vCard 4.0 construction. Ignored.");
-        }
-
-        if (contentValuesList == null || contentValuesList.isEmpty()) {
-            appendLine(VCardConstants.PROPERTY_FN, "");
-            return this;
-        }
-
-        // We have difficulty here. How can we appropriately handle StructuredName with
-        // missing parts necessary for displaying while it has suppremental information.
-        //
-        // e.g. How to handle non-empty phonetic names with empty structured names?
-
-        final ContentValues contentValues = getPrimaryContentValue(contentValuesList);
-        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 formattedName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
-        if (TextUtils.isEmpty(familyName)
-                && TextUtils.isEmpty(givenName)
-                && TextUtils.isEmpty(middleName)
-                && TextUtils.isEmpty(prefix)
-                && TextUtils.isEmpty(suffix)) {
-            if (TextUtils.isEmpty(formattedName)) {
-                appendLine(VCardConstants.PROPERTY_FN, "");
-                return this;
-            }
-            familyName = formattedName;
-        }
-
-        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 escapedFamily = escapeCharacters(familyName);
-        final String escapedGiven = escapeCharacters(givenName);
-        final String escapedMiddle = escapeCharacters(middleName);
-        final String escapedPrefix = escapeCharacters(prefix);
-        final String escapedSuffix = escapeCharacters(suffix);
-
-        mBuilder.append(VCardConstants.PROPERTY_N);
-
-        if (!(TextUtils.isEmpty(phoneticFamilyName) &&
-                        TextUtils.isEmpty(phoneticMiddleName) &&
-                        TextUtils.isEmpty(phoneticGivenName))) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            final String sortAs = escapeCharacters(phoneticFamilyName)
-                    + ';' + escapeCharacters(phoneticGivenName)
-                    + ';' + escapeCharacters(phoneticMiddleName);
-            mBuilder.append("SORT-AS=").append(
-                    VCardUtils.toStringAsV40ParamValue(sortAs));
-        }
-
-        mBuilder.append(VCARD_DATA_SEPARATOR);
-        mBuilder.append(escapedFamily);
-        mBuilder.append(VCARD_ITEM_SEPARATOR);
-        mBuilder.append(escapedGiven);
-        mBuilder.append(VCARD_ITEM_SEPARATOR);
-        mBuilder.append(escapedMiddle);
-        mBuilder.append(VCARD_ITEM_SEPARATOR);
-        mBuilder.append(escapedPrefix);
-        mBuilder.append(VCARD_ITEM_SEPARATOR);
-        mBuilder.append(escapedSuffix);
-        mBuilder.append(VCARD_END_OF_LINE);
-
-        if (TextUtils.isEmpty(formattedName)) {
-            // Note:
-            // DISPLAY_NAME doesn't exist while some other elements do, which is usually
-            // weird in Android, as DISPLAY_NAME should (usually) be constructed
-            // from the others using locale information and its code points.
-            Log.w(LOG_TAG, "DISPLAY_NAME is empty.");
-
-            final String escaped = escapeCharacters(VCardUtils.constructNameFromElements(
-                    VCardConfig.getNameOrderType(mVCardType),
-                    familyName, middleName, givenName, prefix, suffix));
-            appendLine(VCardConstants.PROPERTY_FN, escaped);
-        } else {
-            final String escapedFormatted = escapeCharacters(formattedName);
-            mBuilder.append(VCardConstants.PROPERTY_FN);
-            mBuilder.append(VCARD_DATA_SEPARATOR);
-            mBuilder.append(escapedFormatted);
-            mBuilder.append(VCARD_END_OF_LINE);
-        }
-
-        // We may need X- properties for phonetic names.
-        appendPhoneticNameFields(contentValues);
-        return this;
-    }
-
-    /**
-     * 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 (VCardConfig.isVersion40(mVCardType)) {
-            return appendNamePropertiesV40(contentValuesList);
-        }
-
-        if (contentValuesList == null || contentValuesList.isEmpty()) {
-            if (VCardConfig.isVersion30(mVCardType)) {
-                // vCard 3.0 requires "N" and "FN" properties.
-                // vCard 4.0 does NOT require N, but we take care of possible backward
-                // compatibility issues.
-                appendLine(VCardConstants.PROPERTY_N, "");
-                appendLine(VCardConstants.PROPERTY_FN, "");
-            } else if (mIsDoCoMo) {
-                appendLine(VCardConstants.PROPERTY_N, "");
-            }
-            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);
-
-            // N
-            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);
-
-            // FN
-            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 or necessary for external importers,
-            //       assuming the external importer allows this vioration of the spec.
-            if (shouldAppendCharsetParam(displayName)) {
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(mVCardCharsetParameter);
-            }
-            mBuilder.append(VCARD_DATA_SEPARATOR);
-            mBuilder.append(encodedDisplayName);
-            mBuilder.append(VCARD_END_OF_LINE);
-        } else if (VCardConfig.isVersion30(mVCardType)) {
-            appendLine(VCardConstants.PROPERTY_N, "");
-            appendLine(VCardConstants.PROPERTY_FN, "");
-        } else if (mIsDoCoMo) {
-            appendLine(VCardConstants.PROPERTY_N, "");
-        }
-
-        appendPhoneticNameFields(contentValues);
-        return this;
-    }
-
-    /**
-     * Emits SOUND;IRMC, SORT-STRING, and de-fact values for phonetic names like X-PHONETIC-FAMILY.
-     */
-    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;
-        }
-
-        if (VCardConfig.isVersion40(mVCardType)) {
-            // We don't want SORT-STRING anyway.
-        } else if (VCardConfig.isVersion30(mVCardType)) {
-            final String sortString =
-                    VCardUtils.constructNameFromElements(mVCardType,
-                            phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
-            mBuilder.append(VCardConstants.PROPERTY_SORT_STRING);
-            if (VCardConfig.isVersion30(mVCardType) && shouldAppendCharsetParam(sortString)) {
-                // vCard 3.0 does not force us to use UTF-8 and actually we see some
-                // programs which emit this value. It is incorrect from the view of
-                // specification, but actually necessary for parsing vCard with non-UTF-8
-                // charsets, expecting other parsers not get confused with this value.
-                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 (Yomigana in Japanese) in vCard 2.1, while there is in
-            //       vCard 3.0 (SORT-STRING).
-            //       We use DoCoMo's way when the device is Japanese one since it is already
-            //       supported by a lot of Japanese mobile phones.
-            //       This is "X-" property, so any parser hopefully would not get
-            //       confused with this.
-            //
-            //       Also, DoCoMo's specification requires vCard composer to use just the first
-            //       column.
-            //       i.e.
-            //       good:  SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
-            //       bad :  SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
-            mBuilder.append(VCardConstants.PROPERTY_SOUND);
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
-
-            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);  // family;given
-            mBuilder.append(VCARD_ITEM_SEPARATOR);  // given;middle
-            mBuilder.append(VCARD_ITEM_SEPARATOR);  // middle;prefix
-            mBuilder.append(VCARD_ITEM_SEPARATOR);  // prefix;suffix
-            mBuilder.append(VCARD_END_OF_LINE);
-        }
-
-        Log.d("@@@", "hoge");
-        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(phoneticGivenName))
-            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(phoneticGivenName))
-            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);
-            }  // if (!TextUtils.isEmpty(phoneticFamilyName))
-        }
-    }
-
-    public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
-        final boolean useAndroidProperty;
-        if (mIsV30OrV40) {   // These specifications have NICKNAME property.
-            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 StringBuilder addressBuilder = new StringBuilder();
-            addressBuilder.append(encodedPoBox);
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // PO BOX ; Extended Address
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Extended Address : Street
-            addressBuilder.append(encodedStreet);
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Street : Locality
-            addressBuilder.append(encodedLocality);
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Locality : Region
-            addressBuilder.append(encodedRegion);
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Region : Postal Code
-            addressBuilder.append(encodedPostalCode);
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Postal Code : Country
-            addressBuilder.append(encodedCountry);
-            return new PostalStruct(
-                    reallyUseQuotedPrintable, appendCharset, addressBuilder.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 StringBuilder addressBuilder = new StringBuilder();
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // PO BOX ; Extended Address
-            addressBuilder.append(encodedFormattedAddress);
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Extended Address : Street
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Street : Locality
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Locality : Region
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Region : Postal Code
-            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Postal Code : Country
-            return new PostalStruct(
-                    reallyUseQuotedPrintable, appendCharset, addressBuilder.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;
-                }
-                // TODO: check this works fine.
-                final String photoString = new String(Base64.encode(data, Base64.NO_WRAP));
-                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) {
-        // There's possibility where a given object may have more than one birthday, which
-        // is inappropriate. We just build one birthday.
-        if (contentValuesList != null) {
-            String primaryBirthday = null;
-            String secondaryBirthday = null;
-            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;
-    }
-
-    /**
-     * @param emitEveryTime If true, builder builds the line even when there's no entry.
-     */
-    public void appendPostalLine(final int type, final String label,
-            final ContentValues contentValues,
-            final boolean isPrimary, final boolean emitEveryTime) {
-        final boolean reallyUseQuotedPrintable;
-        final boolean appendCharset;
-        final String addressValue;
-        {
-            PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
-            if (postalStruct == null) {
-                if (emitEveryTime) {
-                    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 if (mIsV30OrV40) {
-                    // This label is appropriately encoded in appendTypeParameters.
-                    parameterList.add(label);
-                } 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 (mIsV30OrV40) {
-            tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_AS_B);
-        } 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 that violation.
-            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) {
-            if (VCardConfig.isVersion30(mVCardType)) {
-                final String encoded = VCardUtils.toStringAsV30ParamValue(typeValue);
-                if (TextUtils.isEmpty(encoded)) {
-                    continue;
-                }
-
-                // 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 (first) {
-                    first = false;
-                } else {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                }
-                appendTypeParameter(encoded);
-            } else {  // vCard 2.1
-                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 (VCardConfig.isVersion40(mVCardType) ||
-                ((VCardConfig.isVersion30(mVCardType) || 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(mCharset);
-        } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Charset " + mCharset + " 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 (mIsV30OrV40) {
-                        tmpBuilder.append("\\\\");
-                        break;
-                    } else {
-                        // fall through
-                    }
-                }
-                case '<':
-                case '>': {
-                    if (mIsDoCoMo) {
-                        tmpBuilder.append('\\');
-                        tmpBuilder.append(ch);
-                    } else {
-                        tmpBuilder.append(ch);
-                    }
-                    break;
-                }
-                case ',': {
-                    if (mIsV30OrV40) {
-                        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 193cf1e..0000000
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ /dev/null
@@ -1,678 +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.Entity.NamedContentValues;
-import android.content.EntityIterator;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.pim.vcard.exception.VCardException;
-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.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.RawContactsEntity;
-import android.text.TextUtils;
-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.
- * </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>
- * <p>
- * Users have to manually take care of memory efficiency. Even one vCard may contain
- * image of non-trivial size for mobile devices.
- * </p>
- * <p>
- * {@link VCardBuilder} is used to build each vCard.
- * </p>
- */
-public class VCardComposer {
-    private static final String LOG_TAG = "VCardComposer";
-
-    public static final 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";
-
-    // Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here,
-    // since usual vCard devices for Japanese devices already use it.
-    private static final String SHIFT_JIS = "SHIFT_JIS";
-    private static final String UTF_8 = "UTF-8";
-
-    /**
-     * 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);
-        // We don't add Google talk here since it has to be handled separately.
-    }
-
-    public static interface OneEntryHandler {
-        public boolean onInit(Context context);
-        public boolean onEntryCreated(String vcard);
-        public void onTerminate();
-    }
-
-    /**
-     * <p>
-     * An useful handler for emitting vCard String to an OutputStream object one by one.
-     * </p>
-     * <p>
-     * The input OutputStream object is closed() on {@link #onTerminate()}.
-     * Must not close the stream outside this class.
-     * </p>
-     */
-    public final class HandlerForOutputStream implements OneEntryHandler {
-        @SuppressWarnings("hiding")
-        private static final String LOG_TAG = "VCardComposer.HandlerForOutputStream";
-
-        private boolean mOnTerminateIsCalled = false;
-
-        private final OutputStream mOutputStream; // mWriter will close this.
-        private Writer mWriter;
-
-        /**
-         * Input stream will be closed on the detruction of this object.
-         */
-        public HandlerForOutputStream(final OutputStream outputStream) {
-            mOutputStream = outputStream;
-        }
-
-        public boolean onInit(final Context context) {
-            try {
-                mWriter = new BufferedWriter(new OutputStreamWriter(
-                        mOutputStream, mCharset));
-            } catch (UnsupportedEncodingException e1) {
-                Log.e(LOG_TAG, "Unsupported charset: " + mCharset);
-                mErrorReason = "Encoding is not supported (usually this does not happen!): "
-                        + mCharset;
-                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 {
-                    closeOutputStream();
-                }
-            }
-        }
-
-        public void closeOutputStream() {
-            try {
-                mWriter.close();
-            } catch (IOException e) {
-                Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring.");
-            }
-        }
-
-        @Override
-        public void finalize() {
-            if (!mOnTerminateIsCalled) {
-                onTerminate();
-            }
-        }
-    }
-
-    private final Context mContext;
-    private final int mVCardType;
-    private final boolean mCareHandlerErrors;
-    private final ContentResolver mContentResolver;
-
-    private final boolean mIsDoCoMo;
-    private Cursor mCursor;
-    private int mIdColumn;
-
-    private final String mCharset;
-    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, null, true);
-    }
-
-    /**
-     * The variant which sets charset to null and sets careHandlerErrors to true.
-     */
-    public VCardComposer(Context context, int vcardType) {
-        this(context, vcardType, null, true);
-    }
-
-    public VCardComposer(Context context, int vcardType, String charset) {
-        this(context, vcardType, charset, true);
-    }
-
-    /**
-     * The variant which sets charset to null.
-     */
-    public VCardComposer(final Context context, final int vcardType,
-            final boolean careHandlerErrors) {
-        this(context, vcardType, null, careHandlerErrors);
-    }
-
-    /**
-     * Construct for supporting call log entry vCard composing.
-     *
-     * @param context Context to be used during the composition.
-     * @param vcardType The type of vCard, typically available via {@link VCardConfig}.
-     * @param charset The charset to be used. Use null when you don't need the charset.
-     * @param careHandlerErrors If true, This object returns false everytime
-     * a Handler object given via {{@link #addHandler(OneEntryHandler)} returns false.
-     * If false, this ignores those errors.
-     */
-    public VCardComposer(final Context context, final int vcardType, String charset,
-            final boolean careHandlerErrors) {
-        mContext = context;
-        mVCardType = vcardType;
-        mCareHandlerErrors = careHandlerErrors;
-        mContentResolver = context.getContentResolver();
-
-        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
-        mHandlerList = new ArrayList<OneEntryHandler>();
-
-        charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
-        final boolean shouldAppendCharsetParam = !(
-                VCardConfig.isVersion30(vcardType) && UTF_8.equalsIgnoreCase(charset));
-
-        if (mIsDoCoMo || shouldAppendCharsetParam) {
-            if (SHIFT_JIS.equalsIgnoreCase(charset)) {
-                if (mIsDoCoMo) {
-                    try {
-                        charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
-                    } catch (UnsupportedCharsetException e) {
-                        Log.e(LOG_TAG,
-                                "DoCoMo-specific SHIFT_JIS was not found. "
-                                + "Use SHIFT_JIS as is.");
-                        charset = SHIFT_JIS;
-                    }
-                } else {
-                    try {
-                        charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
-                    } catch (UnsupportedCharsetException e) {
-                        Log.e(LOG_TAG,
-                                "Career-specific SHIFT_JIS was not found. "
-                                + "Use SHIFT_JIS as is.");
-                        charset = SHIFT_JIS;
-                    }
-                }
-                mCharset = charset;
-            } else {
-                Log.w(LOG_TAG,
-                        "The charset \"" + charset + "\" is used while "
-                        + SHIFT_JIS + " is needed to be used.");
-                if (TextUtils.isEmpty(charset)) {
-                    mCharset = SHIFT_JIS;
-                } else {
-                    try {
-                        charset = CharsetUtils.charsetForVendor(charset).name();
-                    } catch (UnsupportedCharsetException e) {
-                        Log.i(LOG_TAG,
-                                "Career-specific \"" + charset + "\" was not found (as usual). "
-                                + "Use it as is.");
-                    }
-                    mCharset = charset;
-                }
-            }
-        } else {
-            if (TextUtils.isEmpty(charset)) {
-                mCharset = UTF_8;
-            } else {
-                try {
-                    charset = CharsetUtils.charsetForVendor(charset).name();
-                } catch (UnsupportedCharsetException e) {
-                    Log.i(LOG_TAG,
-                            "Career-specific \"" + charset + "\" was not found (as usual). "
-                            + "Use it as is.");
-                }
-                mCharset = charset;
-            }
-        }
-
-        Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
-    }
-
-    /**
-     * 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) {
-            final 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;
-        }
-        final 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,
-            final Method getEntityIteratorMethod) throws VCardException {
-        final Map<String, List<ContentValues>> contentValuesListMap =
-                new HashMap<String, List<ContentValues>>();
-        // The resolver may return the entity iterator with no data. It is possible.
-        // 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("for_export_only", "1")
-                    .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 unit 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();
-            }
-        }
-
-        return buildVCard(contentValuesListMap);
-    }
-
-    /**
-     * Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
-     * {ContactsContract}. Developers can override this method to customize the output.
-     */
-    public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
-        if (contentValuesListMap == null) {
-            Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
-            return "";
-        } else {
-            final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);
-            builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
-                    .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
-                    .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
-                    .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
-                    .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
-                    .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
-                    .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));
-            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) {
-            Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step.");
-            terminate();
-        }
-    }
-
-    /**
-     * @return returns the number of available entities. The return value is undefined
-     * when this object is not ready yet (typically when {{@link #init()} is not called
-     * or when {@link #terminate()} is already called).
-     */
-    public int getCount() {
-        if (mCursor == null) {
-            Log.w(LOG_TAG, "This object is not ready yet.");
-            return 0;
-        }
-        return mCursor.getCount();
-    }
-
-    /**
-     * @return true when there's no entity to be built. The return value is undefined
-     * when this object is not ready yet.
-     */
-    public boolean isAfterLast() {
-        if (mCursor == null) {
-            Log.w(LOG_TAG, "This object is not ready yet.");
-            return false;
-        }
-        return mCursor.isAfterLast();
-    }
-
-    /**
-     * @return Returns the error reason.
-     */
-    public String getErrorReason() {
-        return mErrorReason;
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
deleted file mode 100644
index 8e759e3..0000000
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ /dev/null
@@ -1,505 +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;
-
-    /**
-     * <p>
-     * The charset used during import.
-     * </p>
-     * <p>
-     * We cannot determine which charset should be used to interpret lines in vCard,
-     * while Java requires us to specify it when InputStream is used.
-     * We need to rely on the mechanism due to some performance reason.
-     * </p>
-     * <p>
-     * In order to avoid "misinterpretation" of charset and lose any data in vCard,
-     * "ISO-8859-1" is first used for reading the stream.
-     * When a charset is specified in a property (with "CHARSET=..." parameter),
-     * the string is decoded to raw bytes and encoded into the specific charset,
-     * </p>
-     * <p>
-     * Unicode specification there's a one to one mapping between each byte in ISO-8859-1
-     * and a codepoint, and Java specification requires runtime must have the charset.
-     * Thus, ISO-8859-1 is one effective mapping for intermediate mapping.
-     * </p>
-     */
-    public static final String DEFAULT_INTERMEDIATE_CHARSET = "ISO-8859-1";
-
-    /**
-     * The charset used when there's no information affbout what charset should be used to
-     * encode the binary given from vCard.
-     */
-    public static final String DEFAULT_IMPORT_CHARSET = "UTF-8";
-    public static final String DEFAULT_EXPORT_CHARSET = "UTF-8";
-
-    /**
-     * Do not use statically like "version == VERSION_V21"
-     */
-    public static final int VERSION_21 = 0;
-    public static final int VERSION_30 = 1;
-    public static final int VERSION_40 = 2;
-    public static final int VERSION_MASK = 3;
-
-    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
-
-    /**
-     * <p>
-     * The flag indicating the vCard composer will add some "X-" properties used only in Android
-     * when the formal vCard specification does not have appropriate fields for that data.
-     * </p>
-     * <p>
-     * For example, Android accepts nickname information while vCard 2.1 does not.
-     * When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
-     * instead of just dropping it.
-     * </p>
-     * <p>
-     * vCard parser code automatically parses the field emitted even when this flag is off.
-     * </p>
-     */
-    private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
-    
-    /**
-     * <p>
-     * The flag indicating the vCard composer will add some "X-" properties seen in the
-     * vCard data emitted by the other softwares/devices when the formal vCard specification
-     * does not have appropriate field(s) for that data.
-     * </p> 
-     * <p>
-     * One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are
-     * for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
-     * non-Android devices/softwares. We chose to enable the vCard composer to use those
-     * defact properties since they are also useful for Android devices.
-     * </p>
-     * <p>
-     * Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
-     * allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
-     * in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
-     * </p>
-     */
-    private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
-
-    /**
-     * <p>
-     * The flag indicating some specific dialect seen in vCard of DoCoMo (one of Japanese
-     * mobile careers) should be used. This flag does not include any other information like
-     * that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's
-     * dialect but the name order should be European", but it is not recommended.
-     * </p>
-     */
-    private static final int FLAG_DOCOMO = 0x20000000;
-
-    /**
-     * <p>
-     * 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>
-     * @hide
-     */
-    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 = 0x08000000;
-
-    /**
-     * <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.
-     * </p>
-     * <ol>
-     * <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li>
-     * <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li>
-     * <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li>
-     * </ol>
-     * <p>
-     * If you are targeting to the importer which cannot accept TYPE params without "TYPE="
-     * strings (which should be rare though), please use this flag.
-     * </p>
-     * <p>
-     * Example usage:
-     * <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre>
-     * </p>
-     */
-    public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
-
-    /**
-     * <p>
-     * The flag 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;
-
-    /**
-     * <p>
-     * For importer only. Ignored in exporter.
-     * </p>
-     * <p>
-     * The flag indicating the parser should handle a nested vCard, in which vCard clause starts
-     * in another vCard clause. Here's a typical example.
-     * </p>
-     * <pre class="prettyprint">BEGIN:VCARD
-     * BEGIN:VCARD
-     * VERSION:2.1
-     * ...
-     * END:VCARD
-     * END:VCARD</pre>
-     * <p>
-     * The vCard 2.1 specification allows the nest, but also let parsers ignore nested entries,
-     * while some mobile devices emit nested ones as primary data to be imported.
-     * </p>
-     * <p>
-     * This flag forces a vCard parser to torelate such a nest and understand its content.
-     * </p>
-     */
-    public static final int FLAG_TORELATE_NEST = 0x01000000;
-
-    //// The followings are VCard types available from importer/exporter. ////
-
-    public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x00800000;
-
-    /**
-     * <p>
-     * The type indicating nothing. Used by {@link VCardSourceDetector} when it
-     * was not able to guess the exact vCard type.
-     * </p>
-     */
-    public static final int VCARD_TYPE_UNKNOWN = 0;
-
-    /**
-     * <p>
-     * Generic vCard format with the vCard 2.1. When composing a vCard entry,
-     * the US convension will be used toward formatting some values.
-     * </p>
-     * <p>
-     * e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
-     * while it should be "Prefix Family Middle Given Suffix" in Japan for example.
-     * </p>
-     * <p>
-     * Uses UTF-8 for the charset as a charset for exporting. Note that old vCard importer
-     * outside Android cannot accept it since vCard 2.1 specifically does not allow
-     * that charset, while we need to use it to support various languages around the world.
-     * </p>
-     * <p>
-     * If you want to use alternative charset, you should notify the charset to the other
-     * compontent to be used.
-     * </p>
-     */
-    public static final int VCARD_TYPE_V21_GENERIC =
-        (VERSION_21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
-    /* package */ static String VCARD_TYPE_V21_GENERIC_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 =
-        (VERSION_30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
-    /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
-
-    /**
-     * General vCard format with the version 4.0.
-     * @hide vCard 4.0 is not published yet.
-     */
-    public static final int VCARD_TYPE_V40_GENERIC =
-        (VERSION_40 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
-    /* package */ static final String VCARD_TYPE_V40_GENERIC_STR = "v40_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 =
-        (VERSION_21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
-    /* package */ static final String VCARD_TYPE_V21_EUROPE_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 =
-        (VERSION_30 | NAME_ORDER_EUROPE | 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 =
-        (VERSION_21 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
-    /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese_utf8";
-
-    /**
-     * <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 =
-        (VERSION_30 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
-    /* package */ static final String VCARD_TYPE_V30_JAPANESE_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. It would be better to use Shift_JIS as a charset for maximum
-     * compatibility.
-     * </p>
-     * @hide Should not be available world wide.
-     */
-    public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
-        (VERSION_21 | NAME_ORDER_JAPANESE |
-                FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
-
-    /* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
-
-    /**
-     * <p>
-     * The 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>
-     * @hide Should not be available world wide.
-     */
-    public static final int VCARD_TYPE_DOCOMO =
-        (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
-
-    /* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
-
-    public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
-
-    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_STR, VCARD_TYPE_V21_GENERIC);
-        sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
-        sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
-        sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
-        sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
-        sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
-        sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
-        sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
-
-        sJapaneseMobileTypeSet = new HashSet<Integer>();
-        sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE);
-        sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE);
-        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 isVersion21(final int vcardType) {
-        return (vcardType & VERSION_MASK) == VERSION_21;
-    }
-
-    public static boolean isVersion30(final int vcardType) {
-        return (vcardType & VERSION_MASK) == VERSION_30;
-    }
-
-    public static boolean isVersion40(final int vcardType) {
-        return (vcardType & VERSION_MASK) == VERSION_40;
-    }
-
-    public static boolean shouldUseQuotedPrintable(final int vcardType) {
-        return !isVersion30(vcardType);
-    }
-
-    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 (isVersion30(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() {
-    }
-}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
deleted file mode 100644
index 76371ef9..0000000
--- a/core/java/android/pim/vcard/VCardConstants.java
+++ /dev/null
@@ -1,175 +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";
-    public static final String VERSION_V40 = "4.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 (3.0, 4.0)
-    public static final String PROPERTY_BIRTH = "BIRTH";  // Place of birth (4.0)
-    public static final String PROPERTY_ANNIVERSARY = "ANNIVERSARY";  // Date of marriage (4.0)
-    public static final String PROPERTY_NAME = "NAME";  // (3.0, 4,0)
-    public static final String PROPERTY_NICKNAME = "NICKNAME";  // (3.0, 4.0)
-    public static final String PROPERTY_SORT_STRING = "SORT-STRING";  // (3.0, 4.0)
-    public static final String PROPERTY_END = "END";
-
-    // Valid property names not supported (not appropriately handled) by our importer.
-    // TODO: Should be removed from the view of memory efficiency?
-    public static final String PROPERTY_REV = "REV";
-    public static final String PROPERTY_AGENT = "AGENT";  // (3.0)
-    public static final String PROPERTY_DDAY = "DDAY";  // Date of death (4.0)
-    public static final String PROPERTY_DEATH = "DEATH";  // Place of death (4.0)
-
-    // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
-    
-    // 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 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";
-
-    public static final String PARAM_CHARSET = "CHARSET";
-    public static final String PARAM_ENCODING = "ENCODING";
-
-    // 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";
-
-    public static final String PARAM_ENCODING_7BIT = "7BIT";
-    public static final String PARAM_ENCODING_8BIT = "8BIT";
-    public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
-    public static final String PARAM_ENCODING_BASE64 = "BASE64";  // Available in vCard 2.1
-    public static final String PARAM_ENCODING_B = "B";  // Available in vCard 3.0
-
-    // TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
-    // These types are basically encoded to "X-" parameters when composing vCard.
-    // Parser passes these when "X-" is added to the parameter or not.
-    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";
-
-    public static final String PARAM_LANGUAGE = "LANGUAGE";
-
-    // SORT-AS parameter introduced in vCard 4.0 (as of rev.13)
-    public static final String PARAM_SORT_AS = "SORT-AS";
-
-    // 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";
-
-    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";
-    }
-
-    //// Mainly for package constants.
-
-    // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
-    // SORT-STRING invCard 3.0.
-    /* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
-
-    // Used in unit test.
-    public 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 02e1674..0000000
--- a/core/java/android/pim/vcard/VCardEntry.java
+++ /dev/null
@@ -1,1515 +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.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.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.RawContacts;
-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);
-    }
-
-    public static class PhoneData {
-        public final int type;
-        public final String data;
-        public final String label;
-        // isPrimary is (not final but) changable, only when there's no appropriate one existing
-        // in the original VCard.
-        public boolean isPrimary;
-        public PhoneData(int type, String data, String label, boolean isPrimary) {
-            this.type = type;
-            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);
-        }
-    }
-
-    public static class EmailData {
-        public final int type;
-        public final String data;
-        // Used only when TYPE is TYPE_CUSTOM.
-        public final String label;
-        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);
-        }
-    }
-
-    public static class PostalData {
-        // Determined by vCard specification.
-        // - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
-        public static final int ADDR_MAX_DATA_SIZE = 7;
-        private final String[] dataArray;
-        public final String pobox;
-        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);
-        }
-    }
-
-    public static class OrganizationData {
-        public final int type;
-        // non-final is Intentional: we may change the values since this info is separated into
-        // two parts in vCard: "ORG" + "TITLE", and we have to cope with each field in
-        // different timing.
-        public String companyName;
-        public String departmentName;
-        public String titleName;
-        public final String phoneticName;  // We won't have this in "TITLE" property.
-        public boolean isPrimary;
-
-        public OrganizationData(int type,
-                final String companyName,
-                final String departmentName,
-                final String titleName,
-                final String phoneticName,
-                final boolean isPrimary) {
-            this.type = type;
-            this.companyName = companyName;
-            this.departmentName = departmentName;
-            this.titleName = titleName;
-            this.phoneticName = phoneticName;
-            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);
-        }
-    }
-
-    public static 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;
-        }
-    }
-
-    // TODO(dmiyakawa): vCard 4.0 logically has multiple formatted names and we need to
-    // select the most preferable one using PREF parameter.
-    //
-    // e.g. (based on rev.13)
-    // FN;PREF=1:John M. Doe
-    // FN;PREF=2:John Doe
-    // FN;PREF=3;John
-
-    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 mFormattedName;
-
-    private String mPhoneticFamilyName;
-    private String mPhoneticGivenName;
-    private String mPhoneticMiddleName;
-
-    private String mPhoneticFullName;
-
-    private List<String> mNickNameList;
-
-    private String mDisplayName;
-
-    private String mBirthday;
-    private String mAnniversary;
-
-    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);
-    }
-
-    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);
-                }
-            }
-
-            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, Map, boolean) or
-     * {@link #handleTitleValue(String)}.
-     */
-    private void addNewOrganization(int type, final String companyName,
-            final String departmentName,
-            final String titleName,
-            final String phoneticName,
-            final boolean isPrimary) {
-        if (mOrganizationList == null) {
-            mOrganizationList = new ArrayList<OrganizationData>();
-        }
-        mOrganizationList.add(new OrganizationData(type, companyName,
-                departmentName, titleName, phoneticName, isPrimary));
-    }
-
-    private static final List<String> sEmptyList =
-            Collections.unmodifiableList(new ArrayList<String>(0));
-
-    private String buildSinglePhoneticNameFromSortAsParam(Map<String, Collection<String>> paramMap) {
-        final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS);
-        if (sortAsCollection != null && sortAsCollection.size() != 0) {
-            if (sortAsCollection.size() > 1) {
-                Log.w(LOG_TAG, "Incorrect multiple SORT_AS parameters detected: " +
-                        Arrays.toString(sortAsCollection.toArray()));
-            }
-            final List<String> sortNames =
-                    VCardUtils.constructListFromValue(sortAsCollection.iterator().next(),
-                            mVCardType);
-            final StringBuilder builder = new StringBuilder();
-            for (final String elem : sortNames) {
-                builder.append(elem);
-            }
-            return builder.toString();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * 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,
-            Map<String, Collection<String>> paramMap, boolean isPrimary) {
-        final String phoneticName = buildSinglePhoneticNameFromSortAsParam(paramMap);
-        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, phoneticName, 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, phoneticName, 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, null, 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, null, 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);
-    }
-
-    /**
-     * Tries to extract paramMap, constructs SORT-AS parameter values, and store them in
-     * appropriate phonetic name variables.
-     *
-     * This method does not care the vCard version. Even when we have SORT-AS parameters in
-     * invalid versions (i.e. 2.1 and 3.0), we scilently accept them so that we won't drop
-     * meaningful information. If we had this parameter in the N field of vCard 3.0, and
-     * the contact data also have SORT-STRING, we will prefer SORT-STRING, since it is
-     * regitimate property to be understood.
-     */
-    private void tryHandleSortAsName(final Map<String, Collection<String>> paramMap) {
-        if (VCardConfig.isVersion30(mVCardType) &&
-                !(TextUtils.isEmpty(mPhoneticFamilyName) &&
-                        TextUtils.isEmpty(mPhoneticMiddleName) &&
-                        TextUtils.isEmpty(mPhoneticGivenName))) {
-            return;
-        }
-
-        final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS);
-        if (sortAsCollection != null && sortAsCollection.size() != 0) {
-            if (sortAsCollection.size() > 1) {
-                Log.w(LOG_TAG, "Incorrect multiple SORT_AS parameters detected: " +
-                        Arrays.toString(sortAsCollection.toArray()));
-            }
-            final List<String> sortNames =
-                    VCardUtils.constructListFromValue(sortAsCollection.iterator().next(),
-                            mVCardType);
-            int size = sortNames.size();
-            if (size > 3) {
-                size = 3;
-            }
-            switch (size) {
-            case 3: mPhoneticMiddleName = sortNames.get(2); //$FALL-THROUGH$
-            case 2: mPhoneticGivenName = sortNames.get(1); //$FALL-THROUGH$
-            default: mPhoneticFamilyName = sortNames.get(0); break;
-            }
-        }
-    }
-
-    @SuppressWarnings("fallthrough")
-    private void handleNProperty(final List<String> paramValues,
-            Map<String, Collection<String>> paramMap) {
-        // in vCard 4.0, SORT-AS parameter is available.
-        tryHandleSortAsName(paramMap);
-
-        // Family, Given, Middle, Prefix, Suffix. (1 - 5)
-        int size;
-        if (paramValues == null || (size = paramValues.size()) < 1) {
-            return;
-        }
-        if (size > 5) {
-            size = 5;
-        }
-
-        switch (size) {
-        // Fall-through.
-        case 5: mSuffix = paramValues.get(4);
-        case 4: mPrefix = paramValues.get(3);
-        case 3: mMiddleName = paramValues.get(2);
-        case 2: mGivenName = paramValues.get(1);
-        default: mFamilyName = paramValues.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)) {
-            mFormattedName = propValue;
-        } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFormattedName == null) {
-            // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
-            // actually exist in the real vCard data, does not exist.
-            mFormattedName = propValue;
-        } else if (propName.equals(VCardConstants.PROPERTY_N)) {
-            handleNProperty(propValueList, paramMap);
-        } 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, 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, paramMap, 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 = Im.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_ANNIVERSARY)) {
-            mAnniversary = 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, mVCardType);
-            handleAndroidCustomProperty(customPropertyList);
-        } else {
-        }
-    }
-
-    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(mFormattedName)) {
-            mDisplayName = mFormattedName;
-        } 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.phoneticName != null) {
-                    builder.withValue(Organization.PHONETIC_NAME, organizationData.phoneticName);
-                }
-                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);
-                builder.withValue(Im.DATA, imData.data);
-                if (imData.protocol == Im.PROTOCOL_CUSTOM) {
-                    builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
-                }
-                if (imData.isPrimary) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
-                }
-                operationList.add(builder.build());
-            }
-        }
-
-        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 (!TextUtils.isEmpty(mAnniversary)) {
-            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, mAnniversary);
-            builder.withValue(Event.TYPE, Event.TYPE_ANNIVERSARY);
-            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(mFormattedName)
-                && 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 mFormattedName;
-    }
-
-    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 a8c8057..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 vcardEntry) {
-        long start = System.currentTimeMillis();
-        mCreatedUris.add(vcardEntry.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 aa3e3e2..0000000
--- a/core/java/android/pim/vcard/VCardEntryConstructor.java
+++ /dev/null
@@ -1,234 +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.text.TextUtils;
-import android.util.Base64;
-import android.util.CharsetUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * <p>
- * The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
- * to easily handle each vCard entry.
- * </p>
- * <p>
- * This class understand details inside vCard and translates it to {@link VCardEntry}.
- * Then the class throw it to {@link VCardEntryHandler} registered via
- * {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
- * are able to handle the {@link VCardEntry} object.
- * </p>
- * <p>
- * If you want to know the detail inside vCard, it would be better to implement
- * {@link VCardInterpreter} directly, instead of relying on this class and
- * {@link VCardEntry} created by the object.
- * </p>
- */
-public class VCardEntryConstructor implements VCardInterpreter {
-    private static String LOG_TAG = "VCardEntryConstructor";
-
-    private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
-    private VCardEntry mCurrentVCardEntry;
-    private String mParamType;
-    
-    // The charset using which {@link VCardInterpreter} parses the text.
-    // Each String is first decoded into binary stream with this charset, and encoded back
-    // to "target charset", which may be explicitly specified by the vCard with "CHARSET"
-    // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
-    private final String mSourceCharset;
-
-    private final boolean mStrictLineBreaking;
-    private final int mVCardType;
-    private final Account mAccount;
-    
-    // For measuring performance.
-    private long mTimePushIntoContentResolver;
-
-    private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
-
-    public VCardEntryConstructor() {
-        this(VCardConfig.VCARD_TYPE_V21_GENERIC, null);
-    }
-
-    public VCardEntryConstructor(final int vcardType) {
-        this(vcardType, null, null, false);
-    }
-
-    public VCardEntryConstructor(final int vcardType, final Account account) {
-        this(vcardType, account, null, false);
-    }
-
-    public VCardEntryConstructor(final int vcardType, final Account account,
-            final String inputCharset) {
-        this(vcardType, account, inputCharset, false);
-    }
-
-    /**
-     * @hide Just for testing.
-     */
-    public VCardEntryConstructor(final int vcardType, final Account account,
-            final String inputCharset, final boolean strictLineBreakParsing) {
-        if (inputCharset != null) {
-            mSourceCharset = inputCharset;
-        } else {
-            mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
-        }
-        mStrictLineBreaking = strictLineBreakParsing;
-        mVCardType = vcardType;
-        mAccount = account;
-    }
-
-    public void addEntryHandler(VCardEntryHandler entryHandler) {
-        mEntryHandlers.add(entryHandler);
-    }
-
-    @Override
-    public void start() {
-        for (VCardEntryHandler entryHandler : mEntryHandlers) {
-            entryHandler.onStart();
-        }
-    }
-
-    @Override
-    public void end() {
-        for (VCardEntryHandler entryHandler : mEntryHandlers) {
-            entryHandler.onEnd();
-        }
-    }
-
-    public void clear() {
-        mCurrentVCardEntry = null;
-        mCurrentProperty = new VCardEntry.Property();
-    }
-
-    @Override
-    public void startEntry() {
-        if (mCurrentVCardEntry != null) {
-            Log.e(LOG_TAG, "Nested VCard code is not supported now.");
-        }
-        mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount);
-    }
-
-    @Override
-    public void endEntry() {
-        mCurrentVCardEntry.consolidateFields();
-        for (VCardEntryHandler entryHandler : mEntryHandlers) {
-            entryHandler.onEntryCreated(mCurrentVCardEntry);
-        }
-        mCurrentVCardEntry = null;
-    }
-
-    @Override
-    public void startProperty() {
-        mCurrentProperty.clear();
-    }
-
-    @Override
-    public void endProperty() {
-        mCurrentVCardEntry.addProperty(mCurrentProperty);
-    }
-    
-    @Override
-    public void propertyName(String name) {
-        mCurrentProperty.setPropertyName(name);
-    }
-
-    @Override
-    public void propertyGroup(String group) {
-    }
-
-    @Override
-    public void propertyParamType(String type) {
-        if (mParamType != null) {
-            Log.e(LOG_TAG, "propertyParamType() is called more than once " +
-                    "before propertyParamValue() is called");
-        }
-        mParamType = type;
-    }
-
-    @Override
-    public void propertyParamValue(String value) {
-        if (mParamType == null) {
-            // From vCard 2.1 specification. vCard 3.0 formally does not allow this case.
-            mParamType = "TYPE";
-        }
-        if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
-            value = VCardUtils.convertStringCharset(
-                    value, mSourceCharset, VCardConfig.DEFAULT_IMPORT_CHARSET);
-        }
-        mCurrentProperty.addParameter(mParamType, value);
-        mParamType = null;
-    }
-
-    private String handleOneValue(String value,
-            String sourceCharset, String targetCharset, String encoding) {
-        // It is possible when some of multiple values are empty.
-        // e.g. N:;a;;; -> values are "", "a", "", "", and "".
-        if (TextUtils.isEmpty(value)) {
-            return "";
-        }
-
-        if (encoding != null) {
-            if (encoding.equals("BASE64") || encoding.equals("B")) {
-                mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT));
-                return value;
-            } else if (encoding.equals("QUOTED-PRINTABLE")) {
-                return VCardUtils.parseQuotedPrintable(
-                        value, mStrictLineBreaking, sourceCharset, targetCharset);
-            }
-            Log.w(LOG_TAG, "Unknown encoding. Fall back to default.");
-        }
-
-        // Just translate the charset of a given String from inputCharset to a system one. 
-        return VCardUtils.convertStringCharset(value, sourceCharset, targetCharset);
-    }
-    
-    public void propertyValues(List<String> values) {
-        if (values == null || values.isEmpty()) {
-            return;
-        }
-
-        final Collection<String> charsetCollection =
-                mCurrentProperty.getParameters(VCardConstants.PARAM_CHARSET);
-        final Collection<String> encodingCollection =
-                mCurrentProperty.getParameters(VCardConstants.PARAM_ENCODING);
-        final String encoding =
-            ((encodingCollection != null) ? encodingCollection.iterator().next() : null);
-        String targetCharset = CharsetUtils.nameForDefaultVendor(
-                ((charsetCollection != null) ? charsetCollection.iterator().next() : null));
-        if (TextUtils.isEmpty(targetCharset)) {
-            targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
-        }
-
-        for (final String value : values) {
-            mCurrentProperty.addToPropertyValueList(
-                    handleOneValue(value, mSourceCharset, targetCharset, encoding));
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public void showPerformanceInfo() {
-        Log.d(LOG_TAG, "time for insert ContactStruct to database: " + 
-                mTimePushIntoContentResolver + " ms");
-    }
-}
diff --git a/core/java/android/pim/vcard/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 56bf69d..0000000
--- a/core/java/android/pim/vcard/VCardEntryHandler.java
+++ /dev/null
@@ -1,43 +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;
-
-/**
- * <p>
- * The interface called by {@link VCardEntryConstructor}.
- * </p>
- * <p>
- * This class is useful when you don't want to know vCard data in detail. If you want to know
- * it, it would be better to consider using {@link VCardInterpreter}.
- * </p>
- */
-public interface VCardEntryHandler {
-    /**
-     * 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 03704a2..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 minutely.
- * </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 77e44a0..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 final 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 31b9369..0000000
--- a/core/java/android/pim/vcard/VCardParser.java
+++ /dev/null
@@ -1,55 +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 interface VCardParser {
-    /**
-     * <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 allowed in vCard 3.0. In vCard 2.1,
-     * In some exreme case, it is allowed for vCard to have different charsets in one vCard.
-     * </p>
-     * <p>
-     * We recommend you use {@link VCardSourceDetector} and detect which kind of source the
-     * vCard comes from and explicitly specify a charset using the result.
-     * </p>
-     *
-     * @param is The source to parse.
-     * @param interepreter A {@link VCardInterpreter} object which used to construct data.
-     * @throws IOException, VCardException
-     */
-    public void parse(InputStream is, VCardInterpreter interepreter)
-            throws IOException, VCardException;
-
-    /**
-     * <p>
-     * Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
-     * </p>
-     * <p>
-     * Actual cancel is done after parsing the current vcard.
-     * </p>
-     */
-    public abstract void cancel();
-}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V21.java b/core/java/android/pim/vcard/VCardParserImpl_V21.java
deleted file mode 100644
index 8b365eb..0000000
--- a/core/java/android/pim/vcard/VCardParserImpl_V21.java
+++ /dev/null
@@ -1,1046 +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.pim.vcard;
-
-import android.pim.vcard.exception.VCardAgentNotSupportedException;
-import android.pim.vcard.exception.VCardException;
-import android.pim.vcard.exception.VCardInvalidCommentLineException;
-import android.pim.vcard.exception.VCardInvalidLineException;
-import android.pim.vcard.exception.VCardNestedException;
-import android.pim.vcard.exception.VCardVersionException;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * <p>
- * Basic implementation achieving vCard parsing. Based on vCard 2.1,
- * </p>
- * @hide
- */
-/* package */ class VCardParserImpl_V21 {
-    private static final String LOG_TAG = "VCardParserImpl_V21";
-
-    private static final class EmptyInterpreter implements VCardInterpreter {
-        @Override
-        public void end() {
-        }
-        @Override
-        public void endEntry() {
-        }
-        @Override
-        public void endProperty() {
-        }
-        @Override
-        public void propertyGroup(String group) {
-        }
-        @Override
-        public void propertyName(String name) {
-        }
-        @Override
-        public void propertyParamType(String type) {
-        }
-        @Override
-        public void propertyParamValue(String value) {
-        }
-        @Override
-        public void propertyValues(List<String> values) {
-        }
-        @Override
-        public void start() {
-        }
-        @Override
-        public void startEntry() {
-        }
-        @Override
-        public void startProperty() {
-        }
-    }
-
-    protected static final class CustomBufferedReader extends BufferedReader {
-        private long mTime;
-
-        /**
-         * Needed since "next line" may be null due to end of line.
-         */
-        private boolean mNextLineIsValid;
-        private String mNextLine;
-
-        public CustomBufferedReader(Reader in) {
-            super(in);
-        }
-
-        @Override
-        public String readLine() throws IOException {
-            if (mNextLineIsValid) {
-                final String ret = mNextLine;
-                mNextLine = null;
-                mNextLineIsValid = false;
-                return ret;
-            }
-
-            long start = System.currentTimeMillis();
-            final String line = super.readLine();
-            long end = System.currentTimeMillis();
-            mTime += end - start;
-            return line;
-        }
-
-        /**
-         * Read one line, but make this object store it in its queue.
-         */
-        public String peekLine() throws IOException {
-            if (!mNextLineIsValid) {
-                long start = System.currentTimeMillis();
-                final String line = super.readLine();
-                long end = System.currentTimeMillis();
-                mTime += end - start;
-
-                mNextLine = line;
-                mNextLineIsValid = true;
-            }
-
-            return mNextLine;
-        }
-
-        public long getTotalmillisecond() {
-            return mTime;
-        }
-    }
-
-    private static final String DEFAULT_ENCODING = "8BIT";
-
-    protected boolean mCanceled;
-    protected VCardInterpreter mInterpreter;
-
-    protected final String mIntermediateCharset;
-
-    /**
-     * <p>
-     * The encoding type for deconding byte streams. This member variable is
-     * reset to a default encoding every time when a new item comes.
-     * </p>
-     * <p>
-     * "Encoding" in vCard is different from "Charset". It is mainly used for
-     * addresses, notes, images. "7BIT", "8BIT", "BASE64", and
-     * "QUOTED-PRINTABLE" are known examples.
-     * </p>
-     */
-    protected String mCurrentEncoding;
-
-    /**
-     * <p>
-     * The reader object to be used internally.
-     * </p>
-     * <p>
-     * Developers should not directly read a line from this object. Use
-     * getLine() unless there some reason.
-     * </p>
-     */
-    protected CustomBufferedReader mReader;
-
-    /**
-     * <p>
-     * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard
-     * specification, but happens to be seen in real world vCard.
-     * </p>
-     */
-    protected final Set<String> mUnknownTypeSet = new HashSet<String>();
-
-    /**
-     * <p>
-     * Set for storing unkonwn VALUE attributes, which is not acceptable in
-     * vCard specification, but happens to be seen in real world vCard.
-     * </p>
-     */
-    protected final Set<String> mUnknownValueSet = new HashSet<String>();
-
-
-    // In some cases, vCard is nested. Currently, we only consider the most
-    // interior vCard data.
-    // See v21_foma_1.vcf in test directory for more information.
-    // TODO: Don't ignore by using count, but read all of information outside vCard.
-    private int mNestCount;
-
-    // Used only for parsing END:VCARD.
-    private String mPreviousLine;
-
-    // For measuring performance.
-    private long mTimeTotal;
-    private long mTimeReadStartRecord;
-    private long mTimeReadEndRecord;
-    private long mTimeStartProperty;
-    private long mTimeEndProperty;
-    private long mTimeParseItems;
-    private long mTimeParseLineAndHandleGroup;
-    private long mTimeParsePropertyValues;
-    private long mTimeParseAdrOrgN;
-    private long mTimeHandleMiscPropertyValue;
-    private long mTimeHandleQuotedPrintable;
-    private long mTimeHandleBase64;
-
-    public VCardParserImpl_V21() {
-        this(VCardConfig.VCARD_TYPE_DEFAULT);
-    }
-
-    public VCardParserImpl_V21(int vcardType) {
-        if ((vcardType & VCardConfig.FLAG_TORELATE_NEST) != 0) {
-            mNestCount = 1;
-        }
-
-        mIntermediateCharset =  VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
-    }
-
-    /**
-     * <p>
-     * Parses the file at the given position.
-     * </p>
-     */
-    // <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre>
-    protected void parseVCardFile() throws IOException, VCardException {
-        boolean readingFirstFile = true;
-        while (true) {
-            if (mCanceled) {
-                break;
-            }
-            if (!parseOneVCard(readingFirstFile)) {
-                break;
-            }
-            readingFirstFile = false;
-        }
-
-        if (mNestCount > 0) {
-            boolean useCache = true;
-            for (int i = 0; i < mNestCount; i++) {
-                readEndVCard(useCache, true);
-                useCache = false;
-            }
-        }
-    }
-
-    /**
-     * @return true when a given property name is a valid property name.
-     */
-    protected boolean isValidPropertyName(final String propertyName) {
-        if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) ||
-                propertyName.startsWith("X-"))
-                && !mUnknownTypeSet.contains(propertyName)) {
-            mUnknownTypeSet.add(propertyName);
-            Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
-        }
-        return true;
-    }
-
-    /**
-     * @return String. It may be null, or its length may be 0
-     * @throws IOException
-     */
-    protected String getLine() throws IOException {
-        return mReader.readLine();
-    }
-
-    protected String peekLine() throws IOException {
-        return mReader.peekLine();
-    }
-
-    /**
-     * @return String with it's length > 0
-     * @throws IOException
-     * @throws VCardException when the stream reached end of line
-     */
-    protected String getNonEmptyLine() throws IOException, VCardException {
-        String line;
-        while (true) {
-            line = getLine();
-            if (line == null) {
-                throw new VCardException("Reached end of buffer.");
-            } else if (line.trim().length() > 0) {
-                return line;
-            }
-        }
-    }
-
-    /*
-     * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
-     *         items *CRLF
-     *         "END" [ws] ":" [ws] "VCARD"
-     */
-    private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException {
-        boolean allowGarbage = false;
-        if (firstRead) {
-            if (mNestCount > 0) {
-                for (int i = 0; i < mNestCount; i++) {
-                    if (!readBeginVCard(allowGarbage)) {
-                        return false;
-                    }
-                    allowGarbage = true;
-                }
-            }
-        }
-
-        if (!readBeginVCard(allowGarbage)) {
-            return false;
-        }
-        final long beforeStartEntry = System.currentTimeMillis();
-        mInterpreter.startEntry();
-        mTimeReadStartRecord += System.currentTimeMillis() - beforeStartEntry;
-
-        final long beforeParseItems = System.currentTimeMillis();
-        parseItems();
-        mTimeParseItems += System.currentTimeMillis() - beforeParseItems;
-
-        readEndVCard(true, false);
-
-        final long beforeEndEntry = System.currentTimeMillis();
-        mInterpreter.endEntry();
-        mTimeReadEndRecord += System.currentTimeMillis() - beforeEndEntry;
-        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;
-                }
-            }
-            final String[] strArray = line.split(":", 2);
-            final int length = strArray.length;
-
-            // Although vCard 2.1/3.0 specification does not allow lower cases,
-            // we found vCard file emitted by some external vCard expoter have such
-            // invalid Strings.
-            // So we allow it.
-            // e.g.
-            // BEGIN:vCard
-            if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN")
-                    && strArray[1].trim().equalsIgnoreCase("VCARD")) {
-                return true;
-            } else if (!allowGarbage) {
-                if (mNestCount > 0) {
-                    mPreviousLine = line;
-                    return false;
-                } else {
-                    throw new VCardException("Expected String \"BEGIN:VCARD\" did not come "
-                            + "(Instead, \"" + line + "\" came)");
-                }
-            }
-        } while (allowGarbage);
-
-        throw new VCardException("Reached where must not be reached.");
-    }
-
-    /**
-     * <p>
-     * The arguments useCache and allowGarbase are usually true and false
-     * accordingly when this function is called outside this function itself.
-     * </p>
-     * 
-     * @param useCache When true, line is obtained from mPreviousline.
-     *            Otherwise, getLine() is used.
-     * @param allowGarbage When true, ignore non "END:VCARD" line.
-     * @throws IOException
-     * @throws VCardException
-     */
-    protected void readEndVCard(boolean useCache, boolean allowGarbage) throws IOException,
-            VCardException {
-        String line;
-        do {
-            if (useCache) {
-                // Though vCard specification does not allow lower cases,
-                // some data may have them, so we allow it.
-                line = mPreviousLine;
-            } else {
-                while (true) {
-                    line = getLine();
-                    if (line == null) {
-                        throw new VCardException("Expected END:VCARD was not found.");
-                    } else if (line.trim().length() > 0) {
-                        break;
-                    }
-                }
-            }
-
-            String[] strArray = line.split(":", 2);
-            if (strArray.length == 2 && strArray[0].trim().equalsIgnoreCase("END")
-                    && strArray[1].trim().equalsIgnoreCase("VCARD")) {
-                return;
-            } else if (!allowGarbage) {
-                throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
-            }
-            useCache = false;
-        } while (allowGarbage);
-    }
-
-    /*
-     * items = *CRLF item / item
-     */
-    protected void parseItems() throws IOException, VCardException {
-        boolean ended = false;
-
-        final long beforeBeginProperty = System.currentTimeMillis();
-        mInterpreter.startProperty();
-        mTimeStartProperty += System.currentTimeMillis() - beforeBeginProperty;
-        ended = parseItem();
-        if (!ended) {
-            final long beforeEndProperty = System.currentTimeMillis();
-            mInterpreter.endProperty();
-            mTimeEndProperty += System.currentTimeMillis() - beforeEndProperty;
-        }
-
-        while (!ended) {
-            final long beforeStartProperty = System.currentTimeMillis();
-            mInterpreter.startProperty();
-            mTimeStartProperty += System.currentTimeMillis() - beforeStartProperty;
-            try {
-                ended = parseItem();
-            } catch (VCardInvalidCommentLineException e) {
-                Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
-                ended = false;
-            }
-
-            if (!ended) {
-                final long beforeEndProperty = System.currentTimeMillis();
-                mInterpreter.endProperty();
-                mTimeEndProperty += System.currentTimeMillis() - beforeEndProperty;
-            }
-        }
-    }
-
-    /*
-     * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR"
-     * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts
-     * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."]
-     * "AGENT" [params] ":" vcard CRLF
-     */
-    protected boolean parseItem() throws IOException, VCardException {
-        mCurrentEncoding = DEFAULT_ENCODING;
-
-        final String line = getNonEmptyLine();
-        long start = System.currentTimeMillis();
-
-        String[] propertyNameAndValue = separateLineAndHandleGroup(line);
-        if (propertyNameAndValue == null) {
-            return true;
-        }
-        if (propertyNameAndValue.length != 2) {
-            throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
-        }
-        String propertyName = propertyNameAndValue[0].toUpperCase();
-        String propertyValue = propertyNameAndValue[1];
-
-        mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
-
-        if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) {
-            start = System.currentTimeMillis();
-            handleMultiplePropertyValue(propertyName, propertyValue);
-            mTimeParseAdrOrgN += System.currentTimeMillis() - start;
-            return false;
-        } else if (propertyName.equals("AGENT")) {
-            handleAgent(propertyValue);
-            return false;
-        } else if (isValidPropertyName(propertyName)) {
-            if (propertyName.equals("BEGIN")) {
-                if (propertyValue.equals("VCARD")) {
-                    throw new VCardNestedException("This vCard has nested vCard data in it.");
-                } else {
-                    throw new VCardException("Unknown BEGIN type: " + propertyValue);
-                }
-            } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) {
-                throw new VCardVersionException("Incompatible version: " + propertyValue + " != "
-                        + getVersionString());
-            }
-            start = System.currentTimeMillis();
-            handlePropertyValue(propertyName, propertyValue);
-            mTimeParsePropertyValues += System.currentTimeMillis() - start;
-            return false;
-        }
-
-        throw new VCardException("Unknown property name: \"" + propertyName + "\"");
-    }
-
-    // For performance reason, the states for group and property name are merged into one.
-    static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
-    static private final int STATE_PARAMS = 1;
-    // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
-    static private final int STATE_PARAMS_IN_DQUOTE = 2;
-
-    protected String[] separateLineAndHandleGroup(String line) throws VCardException {
-        final String[] propertyNameAndValue = new String[2];
-        final int length = line.length();
-        if (length > 0 && line.charAt(0) == '#') {
-            throw new VCardInvalidCommentLineException();
-        }
-
-        int state = STATE_GROUP_OR_PROPERTY_NAME;
-        int nameIndex = 0;
-
-        // This loop is developed so that we don't have to take care of bottle neck here.
-        // Refactor carefully when you need to do so.
-        for (int i = 0; i < length; i++) {
-            final char ch = line.charAt(i);
-            switch (state) {
-                case STATE_GROUP_OR_PROPERTY_NAME: {
-                    if (ch == ':') {  // End of a property name.
-                        final String propertyName = line.substring(nameIndex, i);
-                        if (propertyName.equalsIgnoreCase("END")) {
-                            mPreviousLine = line;
-                            return null;
-                        }
-                        mInterpreter.propertyName(propertyName);
-                        propertyNameAndValue[0] = propertyName;
-                        if (i < length - 1) {
-                            propertyNameAndValue[1] = line.substring(i + 1);
-                        } else {
-                            propertyNameAndValue[1] = "";
-                        }
-                        return propertyNameAndValue;
-                    } else if (ch == '.') {  // Each group is followed by the dot.
-                        final String groupName = line.substring(nameIndex, i);
-                        if (groupName.length() == 0) {
-                            Log.w(LOG_TAG, "Empty group found. Ignoring.");
-                        } else {
-                            mInterpreter.propertyGroup(groupName);
-                        }
-                        nameIndex = i + 1;  // Next should be another group or a property name.
-                    } else if (ch == ';') {  // End of property name and beginneng of parameters.  
-                        final String propertyName = line.substring(nameIndex, i);
-                        if (propertyName.equalsIgnoreCase("END")) {
-                            mPreviousLine = line;
-                            return null;
-                        }
-                        mInterpreter.propertyName(propertyName);
-                        propertyNameAndValue[0] = propertyName;
-                        nameIndex = i + 1;
-                        state = STATE_PARAMS;  // Start parameter parsing.
-                    }
-                    // TODO: comma support (in vCard 3.0 and 4.0).
-                    break;
-                }
-                case STATE_PARAMS: {
-                    if (ch == '"') {
-                        if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
-                            Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
-                                    "Silently allow it");
-                        }
-                        state = STATE_PARAMS_IN_DQUOTE;
-                    } else if (ch == ';') {  // Starts another param.
-                        handleParams(line.substring(nameIndex, i));
-                        nameIndex = i + 1;
-                    } else if (ch == ':') {  // End of param and beginenning of values.
-                        handleParams(line.substring(nameIndex, i));
-                        if (i < length - 1) {
-                            propertyNameAndValue[1] = line.substring(i + 1);
-                        } else {
-                            propertyNameAndValue[1] = "";
-                        }
-                        return propertyNameAndValue;
-                    }
-                    break;
-                }
-                case STATE_PARAMS_IN_DQUOTE: {
-                    if (ch == '"') {
-                        if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
-                            Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
-                                    "Silently allow it");
-                        }
-                        state = STATE_PARAMS;
-                    }
-                    break;
-                }
-            }
-        }
-
-        throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
-    }
-
-    /*
-     * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param /
-     * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws]
-     * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "="
-     * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "="
-     * [ws] word / knowntype
-     */
-    protected void handleParams(String params) throws VCardException {
-        final String[] strArray = params.split("=", 2);
-        if (strArray.length == 2) {
-            final String paramName = strArray[0].trim().toUpperCase();
-            String paramValue = strArray[1].trim();
-            if (paramName.equals("TYPE")) {
-                handleType(paramValue);
-            } else if (paramName.equals("VALUE")) {
-                handleValue(paramValue);
-            } else if (paramName.equals("ENCODING")) {
-                handleEncoding(paramValue);
-            } else if (paramName.equals("CHARSET")) {
-                handleCharset(paramValue);
-            } else if (paramName.equals("LANGUAGE")) {
-                handleLanguage(paramValue);
-            } else if (paramName.startsWith("X-")) {
-                handleAnyParam(paramName, paramValue);
-            } else {
-                throw new VCardException("Unknown type \"" + paramName + "\"");
-            }
-        } else {
-            handleParamWithoutName(strArray[0]);
-        }
-    }
-
-    /**
-     * vCard 3.0 parser implementation may throw VCardException.
-     */
-    @SuppressWarnings("unused")
-    protected void handleParamWithoutName(final String paramValue) throws VCardException {
-        handleType(paramValue);
-    }
-
-    /*
-     * ptypeval = knowntype / "X-" word
-     */
-    protected void handleType(final String ptypeval) {
-        if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
-                || ptypeval.startsWith("X-"))
-                && !mUnknownTypeSet.contains(ptypeval)) {
-            mUnknownTypeSet.add(ptypeval);
-            Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
-        }
-        mInterpreter.propertyParamType("TYPE");
-        mInterpreter.propertyParamValue(ptypeval);
-    }
-
-    /*
-     * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
-     */
-    protected void handleValue(final String pvalueval) {
-        if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
-                || pvalueval.startsWith("X-")
-                || mUnknownValueSet.contains(pvalueval))) {
-            mUnknownValueSet.add(pvalueval);
-            Log.w(LOG_TAG, String.format(
-                    "The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
-        }
-        mInterpreter.propertyParamType("VALUE");
-        mInterpreter.propertyParamValue(pvalueval);
-    }
-
-    /*
-     * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
-     */
-    protected void handleEncoding(String pencodingval) throws VCardException {
-        if (getAvailableEncodingSet().contains(pencodingval) ||
-                pencodingval.startsWith("X-")) {
-            mInterpreter.propertyParamType("ENCODING");
-            mInterpreter.propertyParamValue(pencodingval);
-            mCurrentEncoding = pencodingval;
-        } else {
-            throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
-        }
-    }
-
-    /**
-     * <p>
-     * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
-     * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
-     * We allow any charset.
-     * </p>
-     */
-    protected void handleCharset(String charsetval) {
-        mInterpreter.propertyParamType("CHARSET");
-        mInterpreter.propertyParamValue(charsetval);
-    }
-
-    /**
-     * See also Section 7.1 of RFC 1521
-     */
-    protected void handleLanguage(String langval) throws VCardException {
-        String[] strArray = langval.split("-");
-        if (strArray.length != 2) {
-            throw new VCardException("Invalid Language: \"" + langval + "\"");
-        }
-        String tmp = strArray[0];
-        int length = tmp.length();
-        for (int i = 0; i < length; i++) {
-            if (!isAsciiLetter(tmp.charAt(i))) {
-                throw new VCardException("Invalid Language: \"" + langval + "\"");
-            }
-        }
-        tmp = strArray[1];
-        length = tmp.length();
-        for (int i = 0; i < length; i++) {
-            if (!isAsciiLetter(tmp.charAt(i))) {
-                throw new VCardException("Invalid Language: \"" + langval + "\"");
-            }
-        }
-        mInterpreter.propertyParamType(VCardConstants.PARAM_LANGUAGE);
-        mInterpreter.propertyParamValue(langval);
-    }
-
-    private boolean isAsciiLetter(char ch) {
-        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Mainly for "X-" type. This accepts any kind of type without check.
-     */
-    protected void handleAnyParam(String paramName, String paramValue) {
-        mInterpreter.propertyParamType(paramName);
-        mInterpreter.propertyParamValue(paramValue);
-    }
-
-    protected void handlePropertyValue(String propertyName, String propertyValue)
-            throws IOException, VCardException {
-        final String upperEncoding = mCurrentEncoding.toUpperCase();
-        if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
-            final long start = System.currentTimeMillis();
-            final String result = getQuotedPrintable(propertyValue);
-            final ArrayList<String> v = new ArrayList<String>();
-            v.add(result);
-            mInterpreter.propertyValues(v);
-            mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
-        } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
-                || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) {
-            final long start = System.currentTimeMillis();
-            // It is very rare, but some BASE64 data may be so big that
-            // OutOfMemoryError occurs. To ignore such cases, use try-catch.
-            try {
-                final ArrayList<String> arrayList = new ArrayList<String>();
-                arrayList.add(getBase64(propertyValue));
-                mInterpreter.propertyValues(arrayList);
-            } catch (OutOfMemoryError error) {
-                Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
-                mInterpreter.propertyValues(null);
-            }
-            mTimeHandleBase64 += System.currentTimeMillis() - start;
-        } else {
-            if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
-                    upperEncoding.startsWith("X-"))) {
-                Log.w(LOG_TAG,
-                        String.format("The encoding \"%s\" is unsupported by vCard %s",
-                                mCurrentEncoding, getVersionString()));
-            }
-
-            // Some device uses line folding defined in RFC 2425, which is not allowed
-            // in vCard 2.1 (while needed in vCard 3.0).
-            //
-            // e.g.
-            // BEGIN:VCARD
-            // VERSION:2.1
-            // N:;Omega;;;
-            // EMAIL;INTERNET:"Omega"
-            //   <omega@example.com>
-            // FN:Omega
-            // END:VCARD
-            //
-            // The vCard above assumes that email address should become:
-            // "Omega" <omega@example.com>
-            //
-            // But vCard 2.1 requires Quote-Printable when a line contains line break(s).
-            //
-            // For more information about line folding,
-            // see "5.8.1. Line delimiting and folding" in RFC 2425.
-            //
-            // We take care of this case more formally in vCard 3.0, so we only need to
-            // do this in vCard 2.1.
-            if (getVersion() == VCardConfig.VERSION_21) {
-                StringBuilder builder = null;
-                while (true) {
-                    final String nextLine = peekLine();
-                    // We don't need to care too much about this exceptional case,
-                    // but we should not wrongly eat up "END:VCARD", since it critically
-                    // breaks this parser's state machine.
-                    // Thus we roughly look over the next line and confirm it is at least not
-                    // "END:VCARD". This extra fee is worth paying. This is exceptional
-                    // anyway.
-                    if (!TextUtils.isEmpty(nextLine) &&
-                            nextLine.charAt(0) == ' ' &&
-                            !"END:VCARD".contains(nextLine.toUpperCase())) {
-                        getLine();  // Drop the next line.
-
-                        if (builder == null) {
-                            builder = new StringBuilder();
-                            builder.append(propertyValue);
-                        }
-                        builder.append(nextLine.substring(1));
-                    } else {
-                        break;
-                    }
-                }
-                if (builder != null) {
-                    propertyValue = builder.toString();
-                }
-            }
-
-            final long start = System.currentTimeMillis();
-            ArrayList<String> v = new ArrayList<String>();
-            v.add(maybeUnescapeText(propertyValue));
-            mInterpreter.propertyValues(v);
-            mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
-        }
-    }
-
-    /**
-     * <p>
-     * Parses and returns Quoted-Printable.
-     * </p>
-     *
-     * @param firstString The string following a parameter name and attributes.
-     *            Example: "string" in
-     *            "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r".
-     * @return whole Quoted-Printable string, including a given argument and
-     *         following lines. Excludes the last empty line following to Quoted
-     *         Printable lines.
-     * @throws IOException
-     * @throws VCardException
-     */
-    private String getQuotedPrintable(String firstString) throws IOException, VCardException {
-        // Specifically, there may be some padding between = and CRLF.
-        // See the following:
-        //
-        // qp-line := *(qp-segment transport-padding CRLF)
-        // qp-part transport-padding
-        // qp-segment := qp-section *(SPACE / TAB) "="
-        // ; Maximum length of 76 characters
-        //
-        // e.g. (from RFC 2045)
-        // Now's the time =
-        // for all folk to come=
-        // to the aid of their country.
-        if (firstString.trim().endsWith("=")) {
-            // remove "transport-padding"
-            int pos = firstString.length() - 1;
-            while (firstString.charAt(pos) != '=') {
-            }
-            StringBuilder builder = new StringBuilder();
-            builder.append(firstString.substring(0, pos + 1));
-            builder.append("\r\n");
-            String line;
-            while (true) {
-                line = getLine();
-                if (line == null) {
-                    throw new VCardException("File ended during parsing a Quoted-Printable String");
-                }
-                if (line.trim().endsWith("=")) {
-                    // remove "transport-padding"
-                    pos = line.length() - 1;
-                    while (line.charAt(pos) != '=') {
-                    }
-                    builder.append(line.substring(0, pos + 1));
-                    builder.append("\r\n");
-                } else {
-                    builder.append(line);
-                    break;
-                }
-            }
-            return builder.toString();
-        } else {
-            return firstString;
-        }
-    }
-
-    protected String getBase64(String firstString) throws IOException, VCardException {
-        StringBuilder builder = new StringBuilder();
-        builder.append(firstString);
-
-        while (true) {
-            String line = getLine();
-            if (line == null) {
-                throw new VCardException("File ended during parsing BASE64 binary");
-            }
-            if (line.length() == 0) {
-                break;
-            }
-            builder.append(line);
-        }
-
-        return builder.toString();
-    }
-
-    /**
-     * <p>
-     * Mainly for "ADR", "ORG", and "N"
-     * </p>
-     */
-    /*
-     * addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr,
-     * Street, Locality, Region, Postal Code, Country Name orgparts =
-     * *(strnosemi ";") strnosemi ; First is Organization Name, remainder are
-     * Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family,
-     * Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III,
-     * Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a
-     * semicolon in this string, it must be escaped ; with a "\" character. We
-     * do not care the number of "strnosemi" here. We are not sure whether we
-     * should add "\" CRLF to each value. We exclude them for now.
-     */
-    protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
-            throws IOException, VCardException {
-        // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some
-        // softwares/devices
-        // emit such data.
-        if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
-            propertyValue = getQuotedPrintable(propertyValue);
-        }
-
-        mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue,
-                getVersion()));
-    }
-
-    /*
-     * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an
-     * error toward the AGENT property.
-     * // TODO: Support AGENT property.
-     * item =
-     * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws]
-     * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD"
-     */
-    protected void handleAgent(final String propertyValue) throws VCardException {
-        if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
-            // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
-            return;
-        } else {
-            throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
-        }
-    }
-
-    /**
-     * For vCard 3.0.
-     */
-    protected String maybeUnescapeText(final String text) {
-        return text;
-    }
-
-    /**
-     * Returns unescaped String if the character should be unescaped. Return
-     * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";"
-     * while "\x" should not be.
-     */
-    protected String maybeUnescapeCharacter(final char ch) {
-        return unescapeCharacter(ch);
-    }
-
-    /* package */ static String unescapeCharacter(final char ch) {
-        // Original vCard 2.1 specification does not allow transformation
-        // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous
-        // implementation of
-        // this class allowed them, so keep it as is.
-        if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
-            return String.valueOf(ch);
-        } else {
-            return null;
-        }
-    }
-
-    private void showPerformanceInfo() {
-        Log.d(LOG_TAG, "Total parsing time:  " + mTimeTotal + " ms");
-        Log.d(LOG_TAG, "Total readLine time: " + mReader.getTotalmillisecond() + " ms");
-        Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord
-                + " ms");
-        Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms");
-        Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup
-                + " ms");
-        Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
-        Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
-        Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue
-                + " ms");
-        Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms");
-        Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
-    }
-
-    /**
-     * @return {@link VCardConfig#VERSION_21}
-     */
-    protected int getVersion() {
-        return VCardConfig.VERSION_21;
-    }
-
-    /**
-     * @return {@link VCardConfig#VERSION_30}
-     */
-    protected String getVersionString() {
-        return VCardConstants.VERSION_V21;
-    }
-
-    protected Set<String> getKnownPropertyNameSet() {
-        return VCardParser_V21.sKnownPropertyNameSet;
-    }
-
-    protected Set<String> getKnownTypeSet() {
-        return VCardParser_V21.sKnownTypeSet;
-    }
-
-    protected Set<String> getKnownValueSet() {
-        return VCardParser_V21.sKnownValueSet;
-    }
-
-    protected Set<String> getAvailableEncodingSet() {
-        return VCardParser_V21.sAvailableEncoding;
-    }
-
-    protected String getDefaultEncoding() {
-        return DEFAULT_ENCODING;
-    }
-
-
-    public void parse(InputStream is, VCardInterpreter interpreter)
-            throws IOException, VCardException {
-        if (is == null) {
-            throw new NullPointerException("InputStream must not be null.");
-        }
-
-        final InputStreamReader tmpReader = new InputStreamReader(is, mIntermediateCharset);
-        mReader = new CustomBufferedReader(tmpReader);
-
-        mInterpreter = (interpreter != null ? interpreter : new EmptyInterpreter());
-
-        final long start = System.currentTimeMillis();
-        if (mInterpreter != null) {
-            mInterpreter.start();
-        }
-        parseVCardFile();
-        if (mInterpreter != null) {
-            mInterpreter.end();
-        }
-        mTimeTotal += System.currentTimeMillis() - start;
-
-        if (VCardConfig.showPerformanceLog()) {
-            showPerformanceInfo();
-        }
-    }
-
-    public final void cancel() {
-        mCanceled = true;
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V30.java b/core/java/android/pim/vcard/VCardParserImpl_V30.java
deleted file mode 100644
index 3fad9a0..0000000
--- a/core/java/android/pim/vcard/VCardParserImpl_V30.java
+++ /dev/null
@@ -1,388 +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.pim.vcard;
-
-import android.pim.vcard.exception.VCardException;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.Set;
-
-/**
- * <p>
- * Basic implementation achieving vCard 3.0 parsing.
- * </p>
- * <p>
- * This class inherits vCard 2.1 implementation since technically they are similar,
- * while specifically there's logical no relevance between them.
- * So that developers are not confused with the inheritance,
- * {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
- * {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
- * </p>
- * @hide
- */
-/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
-    private static final String LOG_TAG = "VCardParserImpl_V30";
-
-    private String mPreviousLine;
-    private boolean mEmittedAgentWarning = false;
-
-    public VCardParserImpl_V30() {
-        super();
-    }
-
-    public VCardParserImpl_V30(int vcardType) {
-        super(vcardType);
-    }
-
-    @Override
-    protected int getVersion() {
-        return VCardConfig.VERSION_30;
-    }
-
-    @Override
-    protected String getVersionString() {
-        return VCardConstants.VERSION_V30;
-    }
-
-    @Override
-    protected String getLine() throws IOException {
-        if (mPreviousLine != null) {
-            String ret = mPreviousLine;
-            mPreviousLine = null;
-            return ret;
-        } else {
-            return mReader.readLine();
-        }
-    }
-
-    /**
-     * vCard 3.0 requires that the line with space at the beginning of the line
-     * must be combined with previous line.
-     */
-    @Override
-    protected String getNonEmptyLine() throws IOException, VCardException {
-        String line;
-        StringBuilder builder = null;
-        while (true) {
-            line = mReader.readLine();
-            if (line == null) {
-                if (builder != null) {
-                    return builder.toString();
-                } else if (mPreviousLine != null) {
-                    String ret = mPreviousLine;
-                    mPreviousLine = null;
-                    return ret;
-                }
-                throw new VCardException("Reached end of buffer.");
-            } else if (line.length() == 0) {
-                if (builder != null) {
-                    return builder.toString();
-                } else if (mPreviousLine != null) {
-                    String ret = mPreviousLine;
-                    mPreviousLine = null;
-                    return ret;
-                }
-            } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
-                if (builder != null) {
-                    // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
-                    // Following is the excerpts from it.
-                    //
-                    // DESCRIPTION:This is a long description that exists on a long line.
-                    //
-                    // Can be represented as:
-                    //
-                    // DESCRIPTION:This is a long description
-                    //  that exists on a long line.
-                    //
-                    // It could also be represented as:
-                    //
-                    // DESCRIPTION:This is a long descrip
-                    //  tion that exists o
-                    //  n a long line.
-                    builder.append(line.substring(1));
-                } else if (mPreviousLine != null) {
-                    builder = new StringBuilder();
-                    builder.append(mPreviousLine);
-                    mPreviousLine = null;
-                    builder.append(line.substring(1));
-                } else {
-                    throw new VCardException("Space exists at the beginning of the line");
-                }
-            } else {
-                if (mPreviousLine == null) {
-                    mPreviousLine = line;
-                    if (builder != null) {
-                        return builder.toString();
-                    }
-                } else {
-                    String ret = mPreviousLine;
-                    mPreviousLine = line;
-                    return ret;
-                }
-            }
-        }
-    }
-
-    /*
-     * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
-     *         1 * (contentline)
-     *         ;A vCard object MUST include the VERSION, FN and N types.
-     *         [group "."] "END" ":" "VCARD" 1 * CRLF
-     */
-    @Override
-    protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
-        // TODO: vCard 3.0 supports group.
-        return super.readBeginVCard(allowGarbage);
-    }
-
-    @Override
-    protected void readEndVCard(boolean useCache, boolean allowGarbage)
-            throws IOException, VCardException {
-        // TODO: vCard 3.0 supports group.
-        super.readEndVCard(useCache, allowGarbage);
-    }
-
-    /**
-     * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
-     */
-    @Override
-    protected void handleParams(final String params) throws VCardException {
-        try {
-            super.handleParams(params);
-        } catch (VCardException e) {
-            // maybe IANA type
-            String[] strArray = params.split("=", 2);
-            if (strArray.length == 2) {
-                handleAnyParam(strArray[0], strArray[1]);
-            } else {
-                // Must not come here in the current implementation.
-                throw new VCardException(
-                        "Unknown params value: " + params);
-            }
-        }
-    }
-
-    @Override
-    protected void handleAnyParam(final String paramName, final String paramValue) {
-        mInterpreter.propertyParamType(paramName);
-        splitAndPutParamValue(paramValue);
-    }
-
-    @Override
-    protected void handleParamWithoutName(final String paramValue) {
-        handleType(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
-     *  QSAFE-CHAR    = WSP / %x21 / %x23-7E / NON-ASCII
-     *                ; Any character except CTLs, DQUOTE
-     *
-     *  QSAFE-CHAR must not contain DQUOTE, including escaped one (\").
-     */
-    @Override
-    protected void handleType(final String paramValue) {
-        mInterpreter.propertyParamType("TYPE");
-        splitAndPutParamValue(paramValue);
-    }
-
-    /**
-     * Splits parameter values into pieces in accordance with vCard 3.0 specification and
-     * puts pieces into mInterpreter.
-     */
-    /*
-     *  param-value   = ptext / quoted-string
-     *  quoted-string = DQUOTE QSAFE-CHAR DQUOTE
-     *  QSAFE-CHAR    = WSP / %x21 / %x23-7E / NON-ASCII
-     *                ; Any character except CTLs, DQUOTE
-     *
-     *  QSAFE-CHAR must not contain DQUOTE, including escaped one (\")
-     */
-    private void splitAndPutParamValue(String paramValue) {
-        // "comma,separated:inside.dquote",pref
-        //   -->
-        // - comma,separated:inside.dquote
-        // - pref
-        //
-        // Note: Though there's a code, we don't need to take much care of
-        // wrongly-added quotes like the example above, as they induce
-        // parse errors at the top level (when splitting a line into parts).
-        StringBuilder builder = null;  // Delay initialization.
-        boolean insideDquote = false;
-        final int length = paramValue.length();
-        for (int i = 0; i < length; i++) {
-            final char ch = paramValue.charAt(i);
-            if (ch == '"') {
-                if (insideDquote) {
-                    // End of Dquote.
-                    mInterpreter.propertyParamValue(builder.toString());
-                    builder = null;
-                    insideDquote = false;
-                } else {
-                    if (builder != null) {
-                        if (builder.length() > 0) {
-                            // e.g.
-                            // pref"quoted"
-                            Log.w(LOG_TAG, "Unexpected Dquote inside property.");
-                        } else {
-                            // e.g.
-                            // pref,"quoted"
-                            // "quoted",pref
-                            mInterpreter.propertyParamValue(builder.toString());
-                        }
-                    }
-                    insideDquote = true;
-                }
-            } else if (ch == ',' && !insideDquote) {
-                if (builder == null) {
-                    Log.w(LOG_TAG, "Comma is used before actual string comes. (" +
-                            paramValue + ")");
-                } else {
-                    mInterpreter.propertyParamValue(builder.toString());
-                    builder = null;
-                }
-            } else {
-                // To stop creating empty StringBuffer at the end of parameter,
-                // we delay creating this object until this point.
-                if (builder == null) {
-                    builder = new StringBuilder();
-                }
-                builder.append(ch);
-            }
-        }
-        if (insideDquote) {
-            // e.g.
-            // "non-quote-at-end
-            Log.d(LOG_TAG, "Dangling Dquote.");
-        }
-        if (builder != null) {
-            if (builder.length() == 0) {
-                Log.w(LOG_TAG, "Unintended behavior. We must not see empty StringBuilder " +
-                        "at the end of parameter value parsing.");
-            } else {
-                mInterpreter.propertyParamValue(builder.toString());
-            }
-        }
-    }
-
-    @Override
-    protected void handleAgent(final String propertyValue) {
-        // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
-        //
-        // e.g.
-        // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
-        //  TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
-        //  ET:jfriday@host.com\nEND:VCARD\n
-        //
-        // TODO: fix this.
-        //
-        // issue:
-        //  vCard 3.0 also allows this as an example.
-        //
-        // AGENT;VALUE=uri:
-        //  CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
-        //
-        // This is not vCard. Should we support this?
-        //
-        // Just ignore the line for now, since we cannot know how to handle it...
-        if (!mEmittedAgentWarning) {
-            Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
-            mEmittedAgentWarning = true;
-        }
-    }
-
-    /**
-     * vCard 3.0 does not require two CRLF at the last of BASE64 data.
-     * It only requires that data should be MIME-encoded.
-     */
-    @Override
-    protected String getBase64(final String firstString)
-            throws IOException, VCardException {
-        final StringBuilder builder = new StringBuilder();
-        builder.append(firstString);
-
-        while (true) {
-            final String line = getLine();
-            if (line == null) {
-                throw new VCardException("File ended during parsing BASE64 binary");
-            }
-            if (line.length() == 0) {
-                break;
-            } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
-                mPreviousLine = line;
-                break;
-            }
-            builder.append(line);
-        }
-
-        return builder.toString();
-    }
-
-    /**
-     * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
-     *              ; \\ encodes \, \n or \N encodes newline
-     *              ; \; encodes ;, \, encodes ,
-     *
-     * Note: Apple escapes ':' into '\:' while does not escape '\'
-     */
-    @Override
-    protected String maybeUnescapeText(final String text) {
-        return unescapeText(text);
-    }
-
-    public static String unescapeText(final String text) {
-        StringBuilder builder = new StringBuilder();
-        final int length = text.length();
-        for (int i = 0; i < length; i++) {
-            char ch = text.charAt(i);
-            if (ch == '\\' && i < length - 1) {
-                final char next_ch = text.charAt(++i);
-                if (next_ch == 'n' || next_ch == 'N') {
-                    builder.append("\n");
-                } else {
-                    builder.append(next_ch);
-                }
-            } else {
-                builder.append(ch);
-            }
-        }
-        return builder.toString();
-    }
-
-    @Override
-    protected String maybeUnescapeCharacter(final char ch) {
-        return unescapeCharacter(ch);
-    }
-
-    public static String unescapeCharacter(final char ch) {
-        if (ch == 'n' || ch == 'N') {
-            return "\n";
-        } else {
-            return String.valueOf(ch);
-        }
-    }
-
-    @Override
-    protected Set<String> getKnownPropertyNameSet() {
-        return VCardParser_V30.sKnownPropertyNameSet;
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V40.java b/core/java/android/pim/vcard/VCardParserImpl_V40.java
deleted file mode 100644
index 0fe76bb..0000000
--- a/core/java/android/pim/vcard/VCardParserImpl_V40.java
+++ /dev/null
@@ -1,92 +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.pim.vcard;
-
-import java.util.Set;
-
-
-/**
- * <p>
- * Basic implementation parsing vCard 4.0.
- * </p>
- * <p>
- * vCard 4.0 is not published yet. Also this implementation is premature. 
- * </p>
- * @hide
- */
-/* package */ class VCardParserImpl_V40 extends VCardParserImpl_V30 {
-    // private static final String LOG_TAG = "VCardParserImpl_V40";
-
-    public VCardParserImpl_V40() {
-        super();
-    }
-
-    public VCardParserImpl_V40(final int vcardType) {
-        super(vcardType);
-    }
-
-    @Override
-    protected int getVersion() {
-        return VCardConfig.VERSION_40;
-    }
-
-    @Override
-    protected String getVersionString() {
-        return VCardConstants.VERSION_V40;
-    }
-
-    /**
-     * We escape "\N" into new line for safety.
-     */
-    @Override
-    protected String maybeUnescapeText(final String text) {
-        return unescapeText(text);
-    }
-
-    public static String unescapeText(final String text) {
-        // TODO: more strictly, vCard 4.0 requires different type of unescaping rule
-        //       toward each property.
-        final StringBuilder builder = new StringBuilder();
-        final int length = text.length();
-        for (int i = 0; i < length; i++) {
-            char ch = text.charAt(i);
-            if (ch == '\\' && i < length - 1) {
-                final char next_ch = text.charAt(++i);
-                if (next_ch == 'n' || next_ch == 'N') {
-                    builder.append("\n");
-                } else {
-                    builder.append(next_ch);
-                }
-            } else {
-                builder.append(ch);
-            }
-        }
-        return builder.toString();
-    }
-
-    public static String unescapeCharacter(final char ch) {
-        if (ch == 'n' || ch == 'N') {
-            return "\n";
-        } else {
-            return String.valueOf(ch);
-        }
-    }
-
-    @Override
-    protected Set<String> getKnownPropertyNameSet() {
-        return VCardParser_V40.sKnownPropertyNameSet;
-    }
-}
\ No newline at end of file
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 507a176..0000000
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ /dev/null
@@ -1,109 +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;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * </p>
- * vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
- * </p>
- * <p>
- * The spec is written in 1996, and currently various types of "vCard 2.1" exist.
- * To handle real the world vCard formats appropriately and effectively, this class does not
- * obey with strict vCard 2.1.
- * In stead, not only vCard spec but also real world vCard is considered.
- * </p>
- * e.g. A lot of devices and softwares let vCard importer/exporter to use
- * the PNG format to determine the type of image, while it is not allowed in
- * the original specification. As of 2010, we can see even the FLV format
- * (possible in Japanese mobile phones).
- * </p>
- */
-public final class VCardParser_V21 implements VCardParser {
-    /**
-     * A unmodifiable Set storing the property names available in the vCard 2.1 specification.
-     */
-    /* package */ static final Set<String> sKnownPropertyNameSet =
-            Collections.unmodifiableSet(new HashSet<String>(
-                    Arrays.asList("BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
-                            "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
-                            "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")));
-
-    /**
-     * A unmodifiable Set storing the types known in vCard 2.1.
-     */
-    /* package */ static final Set<String> sKnownTypeSet =
-            Collections.unmodifiableSet(new HashSet<String>(
-                    Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
-                            "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
-                            "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
-                            "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
-                            "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
-                            "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
-                            "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
-                            "WAVE", "AIFF", "PCM", "X509", "PGP")));
-
-    /**
-     * A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
-     */
-    /* package */ static final Set<String> sKnownValueSet =
-            Collections.unmodifiableSet(new HashSet<String>(
-                    Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
-
-    /**
-     * <p>
-     * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
-     * </p>
-     * <p>
-     * Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
-     * We allow it for safety.
-     * </p>
-     */
-    /* package */ static final Set<String> sAvailableEncoding =
-        Collections.unmodifiableSet(new HashSet<String>(
-                Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
-                        VCardConstants.PARAM_ENCODING_8BIT,
-                        VCardConstants.PARAM_ENCODING_QP,
-                        VCardConstants.PARAM_ENCODING_BASE64,
-                        VCardConstants.PARAM_ENCODING_B)));
-
-    private final VCardParserImpl_V21 mVCardParserImpl;
-
-    public VCardParser_V21() {
-        mVCardParserImpl = new VCardParserImpl_V21();
-    }
-
-    public VCardParser_V21(int vcardType) {
-        mVCardParserImpl = new VCardParserImpl_V21(vcardType);
-    }
-
-    public void parse(InputStream is, VCardInterpreter interepreter)
-            throws IOException, VCardException {
-        mVCardParserImpl.parse(is, interepreter);
-    }
-
-    public void cancel() {
-        mVCardParserImpl.cancel();
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
deleted file mode 100644
index 238d3a8..0000000
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ /dev/null
@@ -1,87 +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;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * <p>
- * vCard parser for vCard 3.0. See RFC 2426 for more detail.
- * </p>
- * <p>
- * This parser allows vCard format which is not allowed in the RFC, since
- * we have seen several vCard 3.0 files which don't comply with it.
- * </p>
- * <p>
- * e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
- * have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
- * but it is not a must. We silently allow "CHARSET".
- * </p>
- */
-public class VCardParser_V30 implements VCardParser {
-    /* package */ static final Set<String> sKnownPropertyNameSet =
-            Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
-                    "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", 
-                    "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
-                    "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
-                    "NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
-                    "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0
-
-    /**
-     * <p>
-     * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
-     * </p>
-     * <p>
-     * Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
-     * </p>
-     * <p>
-     * "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
-     * because the encoding ambiguates how the vCard file to be parsed.
-     * </p>
-     */
-    /* package */ static final Set<String> sAcceptableEncoding =
-            Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
-                    VCardConstants.PARAM_ENCODING_7BIT,
-                    VCardConstants.PARAM_ENCODING_8BIT,
-                    VCardConstants.PARAM_ENCODING_BASE64,
-                    VCardConstants.PARAM_ENCODING_B)));
-
-    private final VCardParserImpl_V30 mVCardParserImpl;
-
-    public VCardParser_V30() {
-        mVCardParserImpl = new VCardParserImpl_V30();
-    }
-
-    public VCardParser_V30(int vcardType) {
-        mVCardParserImpl = new VCardParserImpl_V30(vcardType);
-    }
-
-    public void parse(InputStream is, VCardInterpreter interepreter)
-            throws IOException, VCardException {
-        mVCardParserImpl.parse(is, interepreter);
-    }
-
-    public void cancel() {
-        mVCardParserImpl.cancel();
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardParser_V40.java b/core/java/android/pim/vcard/VCardParser_V40.java
deleted file mode 100644
index 65a2f68..0000000
--- a/core/java/android/pim/vcard/VCardParser_V40.java
+++ /dev/null
@@ -1,78 +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.pim.vcard;
-
-import android.pim.vcard.exception.VCardException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * <p>
- * vCard parser for vCard 4.0.
- * </p>
- * <p>
- * Currently this parser is based on vCard 4.0 specification rev 11.
- * </p>
- */
-public class VCardParser_V40 implements VCardParser {
-    /* package */ static final Set<String> sKnownPropertyNameSet =
-            Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
-                    "BEGIN", "END", "SOURCE", "NAME", "KIND", "XML",
-                    "FN", "N", "NICKNAME", "PHOTO", "BDAY", "DDAY",
-                    "BIRTH", "DEATH", "ANNIVERSARY", "SEX", "ADR",
-                    "LABEL", "TEL", "EMAIL", "IMPP", "LANG", "TZ",
-                    "GEO", "TITLE", "ROLE", "LOGO", "ORG", "MEMBER",
-                    "RELATED", "CATEGORIES", "NOTE", "PRODID",
-                    "REV", "SOUND", "UID", "CLIENTPIDMAP",
-                    "URL", "VERSION", "CLASS", "KEY", "FBURL", "CALENDRURI",
-                    "CALURI")));
-
-    /**
-     * <p>
-     * A unmodifiable Set storing the values for the type "ENCODING", available in vCard 4.0.
-     * </p>
-     */
-    /* package */ static final Set<String> sAcceptableEncoding =
-            Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
-                    VCardConstants.PARAM_ENCODING_8BIT,
-                    VCardConstants.PARAM_ENCODING_B)));
-
-    private final VCardParserImpl_V30 mVCardParserImpl;
-
-    public VCardParser_V40() {
-        mVCardParserImpl = new VCardParserImpl_V40();
-    }
-
-    public VCardParser_V40(int vcardType) {
-        mVCardParserImpl = new VCardParserImpl_V40(vcardType);
-    }
-
-    @Override
-    public void parse(InputStream is, VCardInterpreter interepreter)
-            throws IOException, VCardException {
-        mVCardParserImpl.parse(is, interepreter);
-    }
-
-    @Override
-    public void cancel() {
-        mVCardParserImpl.cancel();
-    }
-}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java
deleted file mode 100644
index 4c6461e..0000000
--- a/core/java/android/pim/vcard/VCardSourceDetector.java
+++ /dev/null
@@ -1,205 +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.text.TextUtils;
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * <p>
- * The class which tries to detects the source of a vCard file from its contents.
- * </p>
- * <p>
- * The specification of vCard (including both 2.1 and 3.0) is not so strict as to
- * guess its format just by reading beginning few lines (usually we can, but in
- * some most pessimistic case, we cannot until at almost the end of the file).
- * Also we cannot store all vCard entries in memory, while there's no specification
- * how big the vCard entry would become after the parse.
- * </p>
- * <p>
- * This class is usually used for the "first scan", in which we can understand which vCard
- * version is used (and how many entries exist in a file).
- * </p>
- */
-public class VCardSourceDetector implements VCardInterpreter {
-    private static final String LOG_TAG = "VCardSourceDetector";
-
-    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";
-
-    /**
-     * Represents that no estimation is available. Users of this class is able to this
-     * constant when you don't want to let a vCard parser rely on estimation for parse type.
-     */
-    public static final int PARSE_TYPE_UNKNOWN = 0;
-
-    // For Apple's software, which does not mean this type is effective for all its products.
-    // We confirmed they usually use UTF-8, but not sure about vCard type.
-    private static final int PARSE_TYPE_APPLE = 1;
-    // For Japanese mobile phones, which are usually using Shift_JIS as a charset.
-    private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;
-    // For some of mobile phones released from DoCoMo, which use nested vCard. 
-    private static final int PARSE_TYPE_DOCOMO_TORELATE_NEST = 3;
-    // For Japanese Windows Mobel phones. It's version is supposed to be 6.5.
-    private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
-
-    private int mParseType = 0;  // Not sure.
-
-    private boolean mNeedToParseVersion = false;
-    private int mVersion = -1;  // -1 == unknown
-
-    // Some mobile phones (like FOMA) tells us the charset of the data.
-    private boolean mNeedToParseCharset;
-    private String mSpecifiedCharset;
-    
-    public void start() {
-    }
-    
-    public void end() {
-    }
-
-    public void startEntry() {
-    }    
-
-    public void startProperty() {
-        mNeedToParseCharset = false;
-        mNeedToParseVersion = false;
-    }
-    
-    public void endProperty() {
-    }
-
-    public void endEntry() {
-    }
-
-    public void propertyGroup(String group) {
-    }
-    
-    public void propertyName(String name) {
-        if (name.equalsIgnoreCase(VCardConstants.PROPERTY_VERSION)) {
-            mNeedToParseVersion = true;
-            return;
-        } else if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
-            mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
-            // Probably Shift_JIS is used, but we should double confirm.
-            mNeedToParseCharset = true;
-            return;
-        }
-        if (mParseType != PARSE_TYPE_UNKNOWN) {
-            return;
-        }
-        if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
-            mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
-        } else if (FOMA_SIGNS.contains(name)) {
-            mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
-        } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
-            mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
-        } else if (APPLE_SIGNS.contains(name)) {
-            mParseType = PARSE_TYPE_APPLE;
-        }
-    }
-
-    public void propertyParamType(String type) {
-    }
-
-    public void propertyParamValue(String value) {
-    }
-
-    public void propertyValues(List<String> values) {
-        if (mNeedToParseVersion && values.size() > 0) {
-            final String versionString = values.get(0);
-            if (versionString.equals(VCardConstants.VERSION_V21)) {
-                mVersion = VCardConfig.VERSION_21;
-            } else if (versionString.equals(VCardConstants.VERSION_V30)) {
-                mVersion = VCardConfig.VERSION_30;
-            } else if (versionString.equals(VCardConstants.VERSION_V40)) {
-                mVersion = VCardConfig.VERSION_40;
-            } else {
-                Log.w(LOG_TAG, "Invalid version string: " + versionString);
-            }
-        } else if (mNeedToParseCharset && values.size() > 0) {
-            mSpecifiedCharset = values.get(0);
-        }
-    }
-
-    /**
-     * @return The available type can be used with vCard parser. You probably need to
-     * use {{@link #getEstimatedCharset()} to understand the charset to be used.
-     */
-    public int getEstimatedType() {
-        switch (mParseType) {
-            case PARSE_TYPE_DOCOMO_TORELATE_NEST:
-                return VCardConfig.VCARD_TYPE_DOCOMO | VCardConfig.FLAG_TORELATE_NEST;
-            case PARSE_TYPE_MOBILE_PHONE_JP:
-                return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
-            case PARSE_TYPE_APPLE:
-            case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
-            default: {
-                if (mVersion == VCardConfig.VERSION_21) {
-                    return VCardConfig.VCARD_TYPE_V21_GENERIC;
-                } else if (mVersion == VCardConfig.VERSION_30) {
-                    return VCardConfig.VCARD_TYPE_V30_GENERIC;
-                } else if (mVersion == VCardConfig.VERSION_40) {
-                    return VCardConfig.VCARD_TYPE_V40_GENERIC;
-                } else {
-                    return VCardConfig.VCARD_TYPE_UNKNOWN;
-                }
-            }
-        }
-    }
-
-    /**
-     * <p>
-     * Returns charset String guessed from the source's properties.
-     * This method must be called after parsing target file(s).
-     * </p>
-     * @return Charset String. Null is returned if guessing the source fails.
-     */
-    public String getEstimatedCharset() {
-        if (TextUtils.isEmpty(mSpecifiedCharset)) {
-            return mSpecifiedCharset;
-        }
-        switch (mParseType) {
-            case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
-            case PARSE_TYPE_DOCOMO_TORELATE_NEST:
-            case PARSE_TYPE_MOBILE_PHONE_JP:
-                return "SHIFT_JIS";
-            case 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 abceca0..0000000
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ /dev/null
@@ -1,796 +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.pim.vcard.exception.VCardException;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.Data;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.apache.commons.codec.DecoderException;
-import org.apache.commons.codec.net.QuotedPrintableCodec;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-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 {
-    private static final String LOG_TAG = "VCardUtils";
-
-    // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
-    // converted to two parameter Strings. These only contain some minor fields valid in both
-    // vCard and current (as of 2009-08-07) Contacts structure. 
-    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 ("_AUTO_CELL".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;
-        }
-    }
-
-    /**
-     * <p>
-     * Inserts postal data into the builder object.
-     * </p>
-     * <p>
-     * Note that the data structure of ContactsContract is different from that defined in vCard.
-     * So some conversion may be performed in this method.
-     * </p>
-     */
-    public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
-            final ContentProviderOperation.Builder builder,
-            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();
-    }
-
-    /**
-     * Splits the given value into pieces using the delimiter ';' inside it.
-     *
-     * Escaped characters in those values are automatically unescaped into original form.
-     */
-    public static List<String> constructListFromValue(final String value,
-            final int vcardType) {
-        final List<String> list = new ArrayList<String>();
-        StringBuilder builder = new StringBuilder();
-        final int length = value.length();
-        for (int i = 0; i < length; i++) {
-            char ch = value.charAt(i);
-            if (ch == '\\' && i < length - 1) {
-                char nextCh = value.charAt(i + 1);
-                final String unescapedString;
-                if (VCardConfig.isVersion40(vcardType)) {
-                    unescapedString = VCardParserImpl_V40.unescapeCharacter(nextCh);
-                } else if (VCardConfig.isVersion30(vcardType)) {
-                    unescapedString = VCardParserImpl_V30.unescapeCharacter(nextCh);
-                } else {
-                    if (!VCardConfig.isVersion21(vcardType)) {
-                        // Unknown vCard type
-                        Log.w(LOG_TAG, "Unknown vCard type");
-                    }
-                    unescapedString = VCardParserImpl_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;
-    }
-
-    /**
-     * <p>
-     * This is useful when checking the string should be encoded into quoted-printable
-     * or not, which is required by vCard 2.1.
-     * </p>
-     * <p>
-     * See the definition of "7bit" in vCard 2.1 spec for more information.
-     * </p>
-     */
-    public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
-        if (values == null) {
-            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('[', ']', '=', ':', '.', ',', ' '));
-
-    /**
-     * <p>
-     * This is useful since vCard 3.0 often requires the ("X-") properties and groups
-     * should contain only alphabets, digits, and hyphen.
-     * </p>
-     * <p> 
-     * Note: It is already known some devices (wrongly) outputs properties with characters
-     *       which should not be in the field. One example is "X-GOOGLE TALK". We accept
-     *       such kind of input but must never output it unless the target is very specific
-     *       to the device which is able to parse the malformed input.
-     * </p>
-     */
-    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;
-    }
-
-    public static boolean containsOnlyWhiteSpaces(final String...values) {
-        if (values == null) {
-            return true;
-        }
-        return containsOnlyWhiteSpaces(Arrays.asList(values));
-    }
-
-    public static boolean containsOnlyWhiteSpaces(final Collection<String> values) {
-        if (values == null) {
-            return true;
-        }
-        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)) {
-                if (!Character.isWhitespace(str.codePointAt(i))) {
-                    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;
-    }
-
-    private static final int[] sEscapeIndicatorsV30 = new int[]{
-        ':', ';', ',', ' '
-    };
-
-    private static final int[] sEscapeIndicatorsV40 = new int[]{
-        ';', ':'
-    };
-
-    /**
-     * <P>
-     * Returns String available as parameter value in vCard 3.0.
-     * </P>
-     * <P>
-     * RFC 2426 requires vCard composer to quote parameter values when it contains
-     * semi-colon, for example (See RFC 2426 for more information).
-     * This method checks whether the given String can be used without quotes.
-     * </P>
-     * <P>
-     * Note: We remove DQUOTE inside the given value silently for now.
-     * </P>
-     */
-    public static String toStringAsV30ParamValue(String value) {
-        return toStringAsParamValue(value, sEscapeIndicatorsV30);
-    }
-
-    public static String toStringAsV40ParamValue(String value) {
-        return toStringAsParamValue(value, sEscapeIndicatorsV40);
-    }
-
-    private static String toStringAsParamValue(String value, final int[] escapeIndicators) {
-        if (TextUtils.isEmpty(value)) {
-            value = "";
-        }
-        final int asciiFirst = 0x20;
-        final int asciiLast = 0x7E;  // included
-        final StringBuilder builder = new StringBuilder();
-        final int length = value.length();
-        boolean needQuote = false;
-        for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
-            final int codePoint = value.codePointAt(i);
-            if (codePoint < asciiFirst || codePoint == '"') {
-                // CTL characters and DQUOTE are never accepted. Remove them.
-                continue;
-            }
-            builder.appendCodePoint(codePoint);
-            for (int indicator : escapeIndicators) {
-                if (codePoint == indicator) {
-                    needQuote = true;
-                    break;
-                }
-            }
-        }
-
-        final String result = builder.toString();
-        return ((result.isEmpty() || VCardUtils.containsOnlyWhiteSpaces(result))
-                ? ""
-                : (needQuote ? ('"' + result + '"')
-                : result));
-    }
-
-    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;
-    }
-
-    //// The methods bellow may be used by unit test.
-
-    /**
-     * Unquotes given Quoted-Printable value. value must not be null.
-     */
-    public static String parseQuotedPrintable(
-            final String value, boolean strictLineBreaking,
-            String sourceCharset, String targetCharset) {
-        // "= " -> " ", "=\t" -> "\t".
-        // Previous code had done this replacement. Keep on the safe side.
-        final String quotedPrintable;
-        {
-            final StringBuilder builder = new StringBuilder();
-            final int length = value.length();
-            for (int i = 0; i < length; i++) {
-                char ch = value.charAt(i);
-                if (ch == '=' && i < length - 1) {
-                    char nextCh = value.charAt(i + 1);
-                    if (nextCh == ' ' || nextCh == '\t') {
-                        builder.append(nextCh);
-                        i++;
-                        continue;
-                    }
-                }
-                builder.append(ch);
-            }
-            quotedPrintable = builder.toString();
-        }
-
-        String[] lines;
-        if (strictLineBreaking) {
-            lines = quotedPrintable.split("\r\n");
-        } else {
-            StringBuilder builder = new StringBuilder();
-            final int length = quotedPrintable.length();
-            ArrayList<String> list = new ArrayList<String>();
-            for (int i = 0; i < length; i++) {
-                char ch = quotedPrintable.charAt(i);
-                if (ch == '\n') {
-                    list.add(builder.toString());
-                    builder = new StringBuilder();
-                } else if (ch == '\r') {
-                    list.add(builder.toString());
-                    builder = new StringBuilder();
-                    if (i < length - 1) {
-                        char nextCh = quotedPrintable.charAt(i + 1);
-                        if (nextCh == '\n') {
-                            i++;
-                        }
-                    }
-                } else {
-                    builder.append(ch);
-                }
-            }
-            final String lastLine = builder.toString();
-            if (lastLine.length() > 0) {
-                list.add(lastLine);
-            }
-            lines = list.toArray(new String[0]);
-        }
-
-        final StringBuilder builder = new StringBuilder();
-        for (String line : lines) {
-            if (line.endsWith("=")) {
-                line = line.substring(0, line.length() - 1);
-            }
-            builder.append(line);
-        }
-
-        final String rawString = builder.toString();
-        if (TextUtils.isEmpty(rawString)) {
-            Log.w(LOG_TAG, "Given raw string is empty.");
-        }
-
-        byte[] rawBytes = null;
-        try {
-            rawBytes = rawString.getBytes(sourceCharset); 
-        } catch (UnsupportedEncodingException e) {
-            Log.w(LOG_TAG, "Failed to decode: " + sourceCharset);
-            rawBytes = rawString.getBytes();
-        }
-
-        byte[] decodedBytes = null;
-        try {
-            decodedBytes = QuotedPrintableCodec.decodeQuotedPrintable(rawBytes);
-        } catch (DecoderException e) {
-            Log.e(LOG_TAG, "DecoderException is thrown.");
-            decodedBytes = rawBytes;
-        }
-
-        try {
-            return new String(decodedBytes, targetCharset);
-        } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
-            return new String(decodedBytes);
-        }
-    }
-
-    public static final VCardParser getAppropriateParser(int vcardType)
-            throws VCardException {
-        if (VCardConfig.isVersion21(vcardType)) {
-            return new VCardParser_V21();
-        } else if (VCardConfig.isVersion30(vcardType)) {
-            return new VCardParser_V30();
-        } else if (VCardConfig.isVersion40(vcardType)) {
-            return new VCardParser_V40();
-        } else {
-            throw new VCardException("Version is not specified");
-        }
-    }
-
-    public static final String convertStringCharset(
-            String originalString, String sourceCharset, String targetCharset) {
-        if (sourceCharset.equalsIgnoreCase(targetCharset)) {
-            return originalString;
-        }
-        final Charset charset = Charset.forName(sourceCharset);
-        final ByteBuffer byteBuffer = charset.encode(originalString);
-        // byteBuffer.array() "may" return byte array which is larger than
-        // byteBuffer.remaining(). Here, we keep on the safe side.
-        final 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;
-        }
-    }
-
-    // TODO: utilities for vCard 4.0: datetime, timestamp, integer, float, and boolean
-
-    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 b80584b..0000000
--- a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
+++ /dev/null
@@ -1,31 +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 0709fe4..0000000
--- a/core/java/android/pim/vcard/exception/VCardVersionException.java
+++ /dev/null
@@ -1,28 +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 dde6493..17b2e82 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,8 @@
     private CharSequence mSummary;
     private String mKey;
     private Intent mIntent;
+    private String mFragment;
+    private Bundle mExtras;
     private boolean mEnabled = true;
     private boolean mSelectable = true;
     private boolean mRequiresKey;
@@ -208,6 +213,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 +322,44 @@
     }
 
     /**
+     * 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;
+    }
+
+    /**
+     * Return the extras Bundle object associated with this preference, creating
+     * a new Bundle if there currently isn't one.  You can use this to get and
+     * set individual extra key/value pairs.
+     */
+    public Bundle getExtras() {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        return mExtras;
+    }
+
+    /**
+     * Return the extras Bundle object associated with this preference,
+     * returning null if there is not currently one.
+     */
+    public Bundle peekExtras() {
+        return mExtras;
+    }
+
+    /**
      * 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 +1297,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..a59b2f8 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -16,135 +16,778 @@
 
 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.FragmentBreadCrumbs;
+import android.app.FragmentTransaction;
 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.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.FrameLayout;
+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 {
-    
-    private static final String PREFERENCES_TAG = "android:preferences";
-    
+        PreferenceManager.OnPreferenceTreeClickListener,
+        PreferenceFragment.OnPreferenceStartFragmentCallback {
+    private static final String TAG = "PreferenceActivity";
+
+    // Constants for state save/restore
+    private static final String HEADERS_TAG = ":android:headers";
+    private static final String CUR_HEADER_TAG = ":android:cur_header";
+    private static final String PREFERENCES_TAG = ":android:preferences";
+
+    /**
+     * When starting this activity, the invoking Intent can contain this extra
+     * string to specify which fragment should be initially displayed.
+     */
+    public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
+
+    /**
+     * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
+     * this extra can also be specify to supply a Bundle of arguments to pass
+     * to that fragment when it is instantiated during the initial creation
+     * of PreferenceActivity.
+     */
+    public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
+
+    /**
+     * When starting this activity, the invoking Intent can contain this extra
+     * boolean that the header list should not be displayed.  This is most often
+     * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
+     * the activity to display a specific fragment that the user has navigated
+     * to.
+     */
+    public static final String EXTRA_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";
+
+    // add a Skip button?
+    private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
+
+    // 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 FrameLayout mListFooter;
+
+    private View mPrefsContainer;
+
+    private FragmentBreadCrumbs mFragmentBreadCrumbs;
+
+    private boolean mSinglePane;
+
+    private Header mCurHeader;
+
+    // --- 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 static final int MSG_BIND_PREFERENCES = 1;
+    private static final int MSG_BUILD_HEADERS = 2;
     private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                
-                case MSG_BIND_PREFERENCES:
+                case MSG_BIND_PREFERENCES: {
                     bindPreferences();
-                    break;
+                } break;
+                case MSG_BUILD_HEADERS: {
+                    ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders);
+                    mHeaders.clear();
+                    onBuildHeaders(mHeaders);
+                    mAdapter.notifyDataSetChanged();
+                    Header header = onGetNewHeader();
+                    if (header != null && header.fragment != null) {
+                        Header mappedHeader = findBestMatchingHeader(header, oldHeaders);
+                        if (mappedHeader == null || mCurHeader != mappedHeader) {
+                            switchToHeader(header);
+                        }
+                    } else if (mCurHeader != null) {
+                        Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders);
+                        if (mappedHeader != null) {
+                            setSelectedHeader(mappedHeader);
+                        } else {
+                            switchToHeader(null);
+                        }
+                    }
+                } break;
             }
         }
     };
 
+    private static class HeaderAdapter extends ArrayAdapter<Header> {
+        private static class HeaderViewHolder {
+            ImageView icon;
+            TextView title;
+            TextView summary;
+        }
+
+        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_header_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();
+            }
+
+            // All view fields must be updated every time, because the view may be recycled 
+            Header header = getItem(position);
+            holder.icon.setImageResource(header.iconRes);
+            holder.title.setText(header.title);
+            if (TextUtils.isEmpty(header.summary)) {
+                holder.summary.setVisibility(View.GONE);
+            } else {
+                holder.summary.setVisibility(View.VISIBLE);
+                holder.summary.setText(header.summary);
+            }
+
+            return view;
+        }
+    }
+
+    /**
+     * Default value for {@link Header#id Header.id} indicating that no
+     * identifier value is set.  All other values (including those below -1)
+     * are valid.
+     */
+    public static final long HEADER_ID_UNDEFINED = -1;
+    
+    /**
+     * Description of a single Header item that the user can select.
+     */
+    public static final class Header implements Parcelable {
+        /**
+         * Identifier for this header, to correlate with a new list when
+         * it is updated.  The default value is
+         * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id.
+         * @attr ref android.R.styleable#PreferenceHeader_id
+         */
+        public long id = HEADER_ID_UNDEFINED;
+
+        /**
+         * Title of the header that is shown to the user.
+         * @attr ref android.R.styleable#PreferenceHeader_title
+         */
+        public CharSequence title;
+
+        /**
+         * Optional summary describing what this header controls.
+         * @attr ref android.R.styleable#PreferenceHeader_summary
+         */
+        public CharSequence summary;
+
+        /**
+         * Optional text to show as the title in the bread crumb.
+         * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
+         */
+        public CharSequence breadCrumbTitle;
+
+        /**
+         * Optional text to show as the short title in the bread crumb.
+         * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
+         */
+        public CharSequence breadCrumbShortTitle;
+
+        /**
+         * Optional icon resource to show for this header.
+         * @attr ref android.R.styleable#PreferenceHeader_icon
+         */
+        public int iconRes;
+
+        /**
+         * Full class name of the fragment to display when this header is
+         * selected.
+         * @attr ref android.R.styleable#PreferenceHeader_fragment
+         */
+        public String fragment;
+
+        /**
+         * Optional arguments to supply to the fragment when it is
+         * instantiated.
+         */
+        public Bundle fragmentArguments;
+
+        /**
+         * Intent to launch when the preference is selected.
+         */
+        public Intent intent;
+
+        /**
+         * Optional additional data for use by subclasses of PreferenceActivity.
+         */
+        public Bundle extras;
+
+        public Header() {
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(id);
+            TextUtils.writeToParcel(title, dest, flags);
+            TextUtils.writeToParcel(summary, dest, flags);
+            TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
+            TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
+            dest.writeInt(iconRes);
+            dest.writeString(fragment);
+            dest.writeBundle(fragmentArguments);
+            if (intent != null) {
+                dest.writeInt(1);
+                intent.writeToParcel(dest, flags);
+            } else {
+                dest.writeInt(0);
+            }
+            dest.writeBundle(extras);
+        }
+
+        public void readFromParcel(Parcel in) {
+            id = in.readLong();
+            title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            iconRes = in.readInt();
+            fragment = in.readString();
+            fragmentArguments = in.readBundle();
+            if (in.readInt() != 0) {
+                intent = Intent.CREATOR.createFromParcel(in);
+            }
+            extras = in.readBundle();
+        }
+
+        Header(Parcel in) {
+            readFromParcel(in);
+        }
+
+        public static final Creator<Header> CREATOR = new Creator<Header>() {
+            public Header createFromParcel(Parcel source) {
+                return new Header(source);
+            }
+            public Header[] newArray(int size) {
+                return new Header[size];
+            }
+        };
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         setContentView(com.android.internal.R.layout.preference_list_content);
-        
-        mPreferenceManager = onCreatePreferenceManager();
+
+        mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
+        mPrefsContainer = findViewById(com.android.internal.R.id.prefs);
+        boolean hidingHeaders = onIsHidingHeaders();
+        mSinglePane = hidingHeaders || !onIsMultiPane();
+        String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
+        Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+
+        if (savedInstanceState != null) {
+            // We are restarting from a previous saved state; used that to
+            // initialize, instead of starting fresh.
+            ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
+            if (headers != null) {
+                mHeaders.addAll(headers);
+                int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
+                        (int)HEADER_ID_UNDEFINED);
+                if (curHeader >= 0 && curHeader < mHeaders.size()) {
+                    setSelectedHeader(mHeaders.get(curHeader));
+                }
+            }
+
+        } else {
+            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.
+                switchToHeader(initialFragment, initialArguments);
+
+            } 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) {
+                    if (!mSinglePane) {
+                        if (initialFragment == null) {
+                            Header h = onGetInitialHeader();
+                            switchToHeader(h);
+                        } else {
+                            switchToHeader(initialFragment, initialArguments);
+                        }
+                    }
+                }
+            }
+        }
+
+        // The default configuration is to only show the list view.  Adjust
+        // visibility for other configurations.
+        if (initialFragment != null && mSinglePane) {
+            // Single pane, showing just a prefs fragment.
+            findViewById(com.android.internal.R.id.headers).setVisibility(View.GONE);
+            mPrefsContainer.setVisibility(View.VISIBLE);
+        } else if (mHeaders.size() > 0) {
+            mAdapter = new HeaderAdapter(this, mHeaders);
+            setListAdapter(mAdapter);
+            if (!mSinglePane) {
+                // Multi-pane.
+                getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
+                if (mCurHeader != null) {
+                    setSelectedHeader(mCurHeader);
+                }
+                mPrefsContainer.setVisibility(View.VISIBLE);
+            }
+        } else {
+            // If there are no headers, we are in the old "just show a screen
+            // of preferences" mode.
+            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();
+                }
+            });
+            Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
+            skipButton.setOnClickListener(new OnClickListener() {
+                public void onClick(View v) {
+                    setResult(RESULT_OK);
+                    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);
+                }
+            }
+            if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
+                skipButton.setVisibility(View.VISIBLE);
+            }
+        }
+    }
+
+    /**
+     * Returns true if this activity is currently showing the header list.
+     */
+    public boolean hasHeaders() {
+        return getListView().getVisibility() == View.VISIBLE
+                && mPreferenceManager == null;
+    }
+
+    /**
+     * Returns true if this activity is showing multiple panes -- the headers
+     * and a preference fragment.
+     */
+    public boolean isMultiPane() {
+        return hasHeaders() && mPrefsContainer.getVisibility() == View.VISIBLE;
+    }
+
+    /**
+     * 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) {
+            return true;
+        }
+        if ((config.screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK)
+                == Configuration.SCREENLAYOUT_SIZE_LARGE
+                && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Called to determine whether the header list should be hidden.
+     * The default implementation returns the
+     * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
+     * This is set to false, for example, when the activity is being re-launched
+     * to show a particular preference activity.
+     */
+    public boolean onIsHidingHeaders() {
+        return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
+    }
+
+    /**
+     * Called to determine the initial header to be shown.  The default
+     * implementation simply returns the fragment of the first header.  Note
+     * that the returned Header object does not actually need to exist in
+     * your header list -- whatever its fragment is will simply be used to
+     * show for the initial UI.
+     */
+    public Header onGetInitialHeader() {
+        return mHeaders.get(0);
+    }
+
+    /**
+     * Called after the header list has been updated ({@link #onBuildHeaders}
+     * has been called and returned due to {@link #invalidateHeaders()}) to
+     * specify the header that should now be selected.  The default implementation
+     * returns null to keep whatever header is currently selected.
+     */
+    public Header onGetNewHeader() {
+        return null;
+    }
+
+    /**
+     * 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) {
+    }
+
+    /**
+     * Call when you need to change the headers being displayed.  Will result
+     * in onBuildHeaders() later being called to retrieve the new list.
+     */
+    public void invalidateHeaders() {
+        if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
+            mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
+        }
+    }
+
+    /**
+     * 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 (!"preference-headers".equals(nodeName)) {
+                throw new RuntimeException(
+                        "XML document must start with <preference-headers> tag; found"
+                        + nodeName + " at " + parser.getPositionDescription());
+            }
+
+            Bundle curBundle = null;
+
+            final 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.id = sa.getResourceId(
+                            com.android.internal.R.styleable.PreferenceHeader_id,
+                            (int)HEADER_ID_UNDEFINED);
+                    header.title = sa.getText(
+                            com.android.internal.R.styleable.PreferenceHeader_title);
+                    header.summary = sa.getText(
+                            com.android.internal.R.styleable.PreferenceHeader_summary);
+                    header.breadCrumbTitle = sa.getText(
+                            com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
+                    header.breadCrumbShortTitle = sa.getText(
+                            com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
+                    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();
+
+                    if (curBundle == null) {
+                        curBundle = new Bundle();
+                    }
+
+                    final int innerDepth = parser.getDepth();
+                    while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                           && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+
+                        String innerNodeName = parser.getName();
+                        if (innerNodeName.equals("extra")) {
+                            getResources().parseBundleExtra("extra", attrs, curBundle);
+                            XmlUtils.skipCurrentTag(parser);
+
+                        } else if (innerNodeName.equals("intent")) {
+                            header.intent = Intent.parseIntent(getResources(), parser, attrs);
+
+                        } else {
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    }
+
+                    if (curBundle.size() > 0) {
+                        header.fragmentArguments = curBundle;
+                        curBundle = null;
+                    }
+
+                    target.add(header);
+                } 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();
+        }
+
+    }
+
+    /**
+     * Set a footer that should be shown at the bottom of the header list.
+     */
+    public void setListFooter(View view) {
+        mListFooter.removeAllViews();
+        mListFooter.addView(view, new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.WRAP_CONTENT));
     }
 
     @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 (mHeaders.size() > 0) {
+            outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
+            if (mCurHeader != null) {
+                int index = mHeaders.indexOf(mCurHeader);
+                if (index >= 0) {
+                    outState.putInt(CUR_HEADER_TAG, index);
+                }
+            }
+        }
+
+        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 +799,220 @@
     @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, Bundle)}
+     * or {@link #switchToHeader(Header)} 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 (header.fragment != null) {
+            if (mSinglePane) {
+                startWithFragment(header.fragment, header.fragmentArguments);
+            } else {
+                switchToHeader(header);
+            }
+        } else if (header.intent != null) {
+            startActivity(header.intent);
+        }
+    }
+
+    /**
+     * 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.
+     * @param args Optional arguments to supply to the fragment.
+     */
+    public void startWithFragment(String fragmentName, Bundle args) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClass(this, getClass());
+        intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
+        intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
+        intent.putExtra(EXTRA_NO_HEADERS, true);
+        startActivity(intent);
+    }
+
+    /**
+     * Change the base title of the bread crumbs for the current preferences.
+     * This will normally be called for you.  See
+     * {@link android.app.FragmentBreadCrumbs} for more information.
+     */
+    public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
+        if (mFragmentBreadCrumbs == null) {
+            mFragmentBreadCrumbs = new FragmentBreadCrumbs(this);
+            mFragmentBreadCrumbs.setActivity(this);
+            getActionBar().setCustomNavigationMode(mFragmentBreadCrumbs);
+        }
+        mFragmentBreadCrumbs.setTitle(title, shortTitle);
+    }
+
+    void setSelectedHeader(Header header) {
+        mCurHeader = header;
+        int index = mHeaders.indexOf(header);
+        if (index >= 0) {
+            getListView().setItemChecked(index, true);
+        } else {
+            getListView().clearChoices();
+        }
+        if (header != null) {
+            CharSequence title = header.breadCrumbTitle;
+            if (title == null) title = header.title;
+            if (title == null) title = getTitle();
+            showBreadCrumbs(title, header.breadCrumbShortTitle);
+        } else {
+            showBreadCrumbs(getTitle(), null);
+        }
+    }
+
+    public void switchToHeaderInner(String fragmentName, Bundle args, boolean next) {
+        getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
+        Fragment f = Fragment.instantiate(this, fragmentName, args);
+        FragmentTransaction transaction = getFragmentManager().openTransaction();
+        transaction.setTransition(next ?
+                FragmentTransaction.TRANSIT_FRAGMENT_NEXT :
+                FragmentTransaction.TRANSIT_FRAGMENT_PREV);
+        transaction.replace(com.android.internal.R.id.prefs, f);
+        transaction.commit();
+    }
+
+    /**
+     * When in two-pane mode, switch the fragment pane to show the given
+     * preference fragment.
+     *
+     * @param fragmentName The name of the fragment to display.
+     * @param args Optional arguments to supply to the fragment.
+     */
+    public void switchToHeader(String fragmentName, Bundle args) {
+        setSelectedHeader(null);
+        switchToHeaderInner(fragmentName, args, true);
+    }
+
+    /**
+     * When in two-pane mode, switch to the fragment pane to show the given
+     * preference fragment.
+     *
+     * @param header The new header to display.
+     */
+    public void switchToHeader(Header header) {
+        switchToHeaderInner(header.fragment, header.fragmentArguments,
+                mHeaders.indexOf(header) > mHeaders.indexOf(mCurHeader));
+        setSelectedHeader(header);
+    }
+
+    Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
+        ArrayList<Header> matches = new ArrayList<Header>();
+        for (int j=0; j<from.size(); j++) {
+            Header oh = from.get(j);
+            if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
+                // Must be this one.
+                matches.clear();
+                matches.add(oh);
+                break;
+            }
+            if (cur.fragment != null) {
+                if (cur.fragment.equals(oh.fragment)) {
+                    matches.add(oh);
+                }
+            } else if (cur.intent != null) {
+                if (cur.intent.equals(oh.intent)) {
+                    matches.add(oh);
+                }
+            } else if (cur.title != null) {
+                if (cur.title.equals(oh.title)) {
+                    matches.add(oh);
+                }
+            }
+        }
+        final int NM = matches.size();
+        if (NM == 1) {
+            return matches.get(0);
+        } else if (NM > 1) {
+            for (int j=0; j<NM; j++) {
+                Header oh = matches.get(j);
+                if (cur.fragmentArguments != null &&
+                        cur.fragmentArguments.equals(oh.fragmentArguments)) {
+                    return oh;
+                }
+                if (cur.extras != null && cur.extras.equals(oh.extras)) {
+                    return oh;
+                }
+                if (cur.title != null && cur.title.equals(oh.title)) {
+                    return oh;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Start a new fragment.
+     *
+     * @param fragment The fragment to start
+     * @param push If true, the current fragment will be pushed onto the back stack.  If false,
+     * the current fragment will be replaced.
+     */
+    public void startPreferenceFragment(Fragment fragment, boolean push) {
+        FragmentTransaction transaction = getFragmentManager().openTransaction();
+        startPreferenceFragment(fragment, transaction);
+        if (push) {
+            transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+            transaction.addToBackStack(BACK_STACK_PREFS);
+        } else {
+            transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_NEXT);
+        }
+        transaction.commit();
+    }
+
+    /**
+     * Start a new fragment.
+     *
+     * @param fragment The fragment to start
+     * @param ft The FragmentTransaction in which to perform this operation.
+     * Will not be added to the back stack or committed for you; you use do that.
+     */
+    public void startPreferenceFragment(Fragment fragment, FragmentTransaction ft) {
+        ft.replace(com.android.internal.R.id.prefs, fragment);
+    }
+
+    @Override
+    public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
+        Fragment f = Fragment.instantiate(this, pref.getFragment(), pref.getExtras());
+        FragmentTransaction transaction = getFragmentManager().openTransaction();
+        startPreferenceFragment(f, transaction);
+        transaction.setBreadCrumbTitle(pref.getTitle());
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        transaction.addToBackStack(BACK_STACK_PREFS);
+        transaction.commit();
+        return true;
     }
 
     /**
@@ -176,7 +1025,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 +1036,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 +1080,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 +1167,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..7629c31
--- /dev/null
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 = 1;
+    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.setFragment(this);
+        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());
+        }
+    }
+
+    /** @hide */
+    public 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/PreferenceInflater.java b/core/java/android/preference/PreferenceInflater.java
index 779e746..c21aa18 100644
--- a/core/java/android/preference/PreferenceInflater.java
+++ b/core/java/android/preference/PreferenceInflater.java
@@ -16,6 +16,8 @@
 
 package android.preference;
 
+import com.android.internal.util.XmlUtils;
+
 import java.io.IOException;
 import java.util.Map;
 
@@ -39,6 +41,7 @@
 class PreferenceInflater extends GenericInflater<Preference, PreferenceGroup> {
     private static final String TAG = "PreferenceInflater";
     private static final String INTENT_TAG_NAME = "intent";
+    private static final String EXTRA_TAG_NAME = "extra";
 
     private PreferenceManager mPreferenceManager;
     
@@ -73,8 +76,10 @@
             try {
                 intent = Intent.parseIntent(getContext().getResources(), parser, attrs);
             } catch (IOException e) {
-                Log.w(TAG, "Could not parse Intent.");
-                Log.w(TAG, e);
+                XmlPullParserException ex = new XmlPullParserException(
+                        "Error parsing preference");
+                ex.initCause(e);
+                throw ex;
             }
             
             if (intent != null) {
@@ -82,6 +87,18 @@
             }
             
             return true;
+        } else if (tag.equals(EXTRA_TAG_NAME)) {
+            getContext().getResources().parseBundleExtra(EXTRA_TAG_NAME, attrs,
+                    parentPreference.getExtras());
+            try {
+                XmlUtils.skipCurrentTag(parser);
+            } catch (IOException e) {
+                XmlPullParserException ex = new XmlPullParserException(
+                        "Error parsing preference");
+                ex.initCause(e);
+                throw ex;
+            }
+            return true;
         }
         
         return false;
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index fa83897..42150ed 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -16,12 +16,7 @@
 
 package android.preference;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-
 import android.app.Activity;
-import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -34,6 +29,10 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
 /**
  * Used to help create {@link Preference} hierarchies
  * from activities or XML.
@@ -61,6 +60,11 @@
     private Activity mActivity;
 
     /**
+     * Fragment that owns this instance.
+     */
+    private PreferenceFragment mFragment;
+
+    /**
      * The context to use. This should always be set.
      * 
      * @see #mActivity
@@ -158,7 +162,21 @@
         
         setSharedPreferencesName(getDefaultSharedPreferencesName(context));
     }
-    
+
+    /**
+     * Sets the owning preference fragment
+     */
+    void setFragment(PreferenceFragment fragment) {
+        mFragment = fragment;
+    }
+
+    /**
+     * Returns the owning preference fragment, if any.
+     */
+    PreferenceFragment getFragment() {
+        return mFragment;
+    }
+
     /**
      * Returns a list of {@link Activity} (indirectly) that match a given
      * {@link Intent}.
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index 95e54324..c7f8ab2 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -91,7 +91,8 @@
 
     /**
      * Returns an adapter that can be attached to a {@link PreferenceActivity}
-     * to show the preferences contained in this {@link PreferenceScreen}.
+     * or {@link PreferenceFragment} to show the preferences contained in this
+     * {@link PreferenceScreen}.
      * <p>
      * This {@link PreferenceScreen} will NOT appear in the returned adapter, instead
      * it appears in the hierarchy above this {@link PreferenceScreen}.
@@ -136,7 +137,7 @@
     
     @Override
     protected void onClick() {
-        if (getIntent() != null || getPreferenceCount() == 0) {
+        if (getIntent() != null || getFragment() != null || getPreferenceCount() == 0) {
             return;
         }
         
diff --git a/core/java/android/preference/RingtonePreference.java b/core/java/android/preference/RingtonePreference.java
index b46f180..cf14097 100644
--- a/core/java/android/preference/RingtonePreference.java
+++ b/core/java/android/preference/RingtonePreference.java
@@ -24,7 +24,6 @@
 import android.provider.Settings.System;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.Log;
 
 /**
  * A {@link Preference} that allows the user to choose a ringtone from those on the device. 
@@ -137,7 +136,12 @@
         // Launch the ringtone picker
         Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
         onPrepareRingtonePickerIntent(intent);
-        getPreferenceManager().getActivity().startActivityForResult(intent, mRequestCode);
+        PreferenceFragment owningFragment = getPreferenceManager().getFragment();
+        if (owningFragment != null) {
+            owningFragment.startActivityForResult(intent, mRequestCode);
+        } else {
+            getPreferenceManager().getActivity().startActivityForResult(intent, mRequestCode);
+        }
     }
 
     /**
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index 2fba1d79..14d6485 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -17,22 +17,30 @@
 package android.provider;
 
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 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.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.History;
+import android.provider.BrowserContract.Searches;
 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 +57,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 +75,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 +106,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 +149,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 +181,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));
     }
 
     /**
@@ -174,48 +210,49 @@
         send.putExtra(Intent.EXTRA_TEXT, stringToSend);
 
         try {
-            c.startActivity(Intent.createChooser(send, chooserDialogTitle));
+            Intent i = Intent.createChooser(send, chooserDialogTitle);
+            // In case this is called from outside an Activity
+            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            c.startActivity(i);
         } catch(android.content.ActivityNotFoundException ex) {
             // if no app handles it, do nothing
         }
     }
 
     /**
-     *  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);
+        return cr.query(Bookmarks.CONTENT_URI,
+                new String[] { Bookmarks.URL }, 
+                Bookmarks.IS_FOLDER + " = 0", 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
             IllegalStateException {
-        return cr.query(BOOKMARKS_URI,
-                new String[] { BookmarkColumns.URL }, null, null, null);
+        return cr.query(History.CONTENT_URI,
+                new String[] { History.URL }, null, null, null);
     }
 
     private static final void addOrUrlEquals(StringBuilder sb) {
         sb.append(" OR " + BookmarkColumns.URL + " = ");
     }
 
-    /**
-     *  Return a Cursor with all history/bookmarks that are similar to url,
-     *  where similar means 'http(s)://' and 'www.' are optional, but the rest
-     *  of the url is the same.
-     *  @param cr   The ContentResolver used to access the database.
-     *  @param url  The url to compare to.
-     *  @hide
-     */
-    public static final Cursor getVisitedLike(ContentResolver cr, String url) {
+    private static final Cursor getVisitedLike(ContentResolver cr, String url) {
         boolean secure = false;
         String compareString = url;
         if (compareString.startsWith("http://")) {
@@ -229,14 +266,14 @@
         }
         StringBuilder whereClause = null;
         if (secure) {
-            whereClause = new StringBuilder(BookmarkColumns.URL + " = ");
+            whereClause = new StringBuilder(Bookmarks.URL + " = ");
             DatabaseUtils.appendEscapedSQLString(whereClause,
                     "https://" + compareString);
             addOrUrlEquals(whereClause);
             DatabaseUtils.appendEscapedSQLString(whereClause,
                     "https://www." + compareString);
         } else {
-            whereClause = new StringBuilder(BookmarkColumns.URL + " = ");
+            whereClause = new StringBuilder(Bookmarks.URL + " = ");
             DatabaseUtils.appendEscapedSQLString(whereClause,
                     compareString);
             addOrUrlEquals(whereClause);
@@ -250,7 +287,7 @@
             DatabaseUtils.appendEscapedSQLString(whereClause,
                     "http://" + wwwString);
         }
-        return cr.query(BOOKMARKS_URI, HISTORY_PROJECTION,
+        return cr.query(History.CONTENT_URI, new String[] { History._ID, History.VISITS },
                 whereClause.toString(), null, null);
     }
 
@@ -266,26 +303,24 @@
      */
     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);
             /* We should only get one answer that is exactly the same. */
             if (c.moveToFirst()) {
-                ContentValues map = new ContentValues();
+                ContentValues values = new ContentValues();
                 if (real) {
-                    map.put(BookmarkColumns.VISITS, c
-                            .getInt(HISTORY_PROJECTION_VISITS_INDEX) + 1);
+                    values.put(History.VISITS, c.getInt(1) + 1);
                 } else {
-                    map.put(BookmarkColumns.USER_ENTERED, 1);
+                    values.put(History.USER_ENTERED, 1);
                 }
-                map.put(BookmarkColumns.DATE, now);
-                String[] projection = new String[]
-                        { Integer.valueOf(c.getInt(0)).toString() };
-                cr.update(BOOKMARKS_URI, map, "_id = ?", projection);
+                values.put(History.DATE_LAST_VISITED, now);
+                cr.update(ContentUris.withAppendedId(History.CONTENT_URI, c.getLong(0)),
+                        values, null, null);
             } else {
                 truncateHistory(cr);
-                ContentValues map = new ContentValues();
+                ContentValues values = new ContentValues();
                 int visits;
                 int user_entered;
                 if (real) {
@@ -295,14 +330,13 @@
                     visits = 0;
                     user_entered = 1;
                 }
-                map.put(BookmarkColumns.URL, url);
-                map.put(BookmarkColumns.VISITS, visits);
-                map.put(BookmarkColumns.DATE, now);
-                map.put(BookmarkColumns.BOOKMARK, 0);
-                map.put(BookmarkColumns.TITLE, url);
-                map.put(BookmarkColumns.CREATED, 0);
-                map.put(BookmarkColumns.USER_ENTERED, user_entered);
-                cr.insert(BOOKMARKS_URI, map);
+                values.put(History.URL, url);
+                values.put(History.VISITS, visits);
+                values.put(History.DATE_LAST_VISITED, now);
+                values.put(History.TITLE, url);
+                values.put(History.DATE_CREATED, 0);
+                values.put(History.USER_ENTERED, user_entered);
+                cr.insert(History.CONTENT_URI, values);
             }
         } catch (IllegalStateException e) {
             Log.e(LOGTAG, "updateVisitedHistory", e);
@@ -322,10 +356,10 @@
         String[] str = null;
         try {
             String[] projection = new String[] {
-                "url"
+                    History.URL,
             };
-            c = cr.query(BOOKMARKS_URI, projection, "visits > 0", null,
-                    null);
+            c = cr.query(History.CONTENT_URI, projection, History.VISITS + " > 0", null, null);
+            if (c == null) return new String[0];
             str = new String[c.getCount()];
             int i = 0;
             while (c.moveToNext()) {
@@ -353,31 +387,29 @@
      * @param cr The ContentResolver used to access the database.
      */
     public static final void truncateHistory(ContentResolver cr) {
-        Cursor c = null;
+        // TODO make a single request to the provider to do this in a single transaction
+        Cursor cursor = null;
         try {
+            
             // Select non-bookmark history, ordered by date
-            c = cr.query(
-                    BOOKMARKS_URI,
-                    TRUNCATE_HISTORY_PROJECTION,
-                    "bookmark = 0",
-                    null,
-                    BookmarkColumns.DATE);
-            // Log.v(LOGTAG, "history count " + c.count());
-            if (c.moveToFirst() && c.getCount() >= MAX_HISTORY_COUNT) {
+            cursor = cr.query(History.CONTENT_URI,
+                    new String[] { History._ID, History.URL, History.DATE_LAST_VISITED },
+                    null, null, History.DATE_LAST_VISITED + " ASC");
+
+            if (cursor.moveToFirst() && cursor.getCount() >= MAX_HISTORY_COUNT) {
+                final WebIconDatabase iconDb = WebIconDatabase.getInstance();
                 /* eliminate oldest history items */
                 for (int i = 0; i < TRUNCATE_N_OLDEST; i++) {
-                    // Log.v(LOGTAG, "truncate history " +
-                    // c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX));
-                    cr.delete(BOOKMARKS_URI, "_id = " +
-                            c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX),
-                            null);
-                    if (!c.moveToNext()) break;
+                    cr.delete(ContentUris.withAppendedId(History.CONTENT_URI, cursor.getLong(0)),
+                            null, null);
+                    iconDb.releaseIconForPageUrl(cursor.getString(1));
+                    if (!cursor.moveToNext()) break;
                 }
             }
         } catch (IllegalStateException e) {
             Log.e(LOGTAG, "truncateHistory", e);
         } finally {
-            if (c != null) c.close();
+            if (cursor != null) cursor.close();
         }
     }
 
@@ -388,23 +420,17 @@
      * @return boolean  True if the history can be cleared.
      */
     public static final boolean canClearHistory(ContentResolver cr) {
-        Cursor c = null;
+        Cursor cursor = null;
         boolean ret = false;
         try {
-            c = cr.query(
-                BOOKMARKS_URI,
-                new String [] { BookmarkColumns._ID, 
-                                BookmarkColumns.BOOKMARK,
-                                BookmarkColumns.VISITS },
-                "bookmark = 0 OR visits > 0", 
-                null,
-                null
-                );
-            ret = c.moveToFirst();
+            cursor = cr.query(History.CONTENT_URI,
+                new String [] { History._ID, History.VISITS },
+                null, null, null);
+            ret = cursor.getCount() > 0;
         } catch (IllegalStateException e) {
             Log.e(LOGTAG, "canClearHistory", e);
         } finally {
-            if (c != null) c.close();
+            if (cursor != null) cursor.close();
         }
         return ret;
     }
@@ -420,67 +446,36 @@
     }
 
     /**
-     * Helper function to delete all history items and revert all
-     * bookmarks to zero visits which meet the criteria provided.
-     *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
-     *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
+     * Helper function to delete all history items and release the icons for them in the
+     * {@link WebIconDatabase}.
+     *
+     * Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
+     * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
+     *
      * @param cr   The ContentResolver used to access the database.
      * @param whereClause   String to limit the items affected.
      *                      null means all items.
      */
-    private static final void deleteHistoryWhere(ContentResolver cr,
-            String whereClause) {
-        Cursor c = null;
+    private static final void deleteHistoryWhere(ContentResolver cr, String whereClause) {
+        Cursor cursor = null;
         try {
-            c = cr.query(BOOKMARKS_URI,
-                HISTORY_PROJECTION,
-                whereClause,
-                null,
-                null);
-            if (c.moveToFirst()) {
+            cursor = cr.query(History.CONTENT_URI, new String[] { History.URL }, whereClause,
+                    null, null);
+            if (cursor.moveToFirst()) {
                 final WebIconDatabase iconDb = WebIconDatabase.getInstance();
-                /* Delete favicons, and revert bookmarks which have been visited
-                 * to simply bookmarks.
-                 */
-                StringBuffer sb = new StringBuffer();
-                boolean firstTime = true;
                 do {
-                    String url = c.getString(HISTORY_PROJECTION_URL_INDEX);
-                    boolean isBookmark =
-                        c.getInt(HISTORY_PROJECTION_BOOKMARK_INDEX) == 1;
-                    if (isBookmark) {
-                        if (firstTime) {
-                            firstTime = false;
-                        } else {
-                            sb.append(" OR ");
-                        }
-                        sb.append("( _id = ");
-                        sb.append(c.getInt(0));
-                        sb.append(" )");
-                    } else {
-                        iconDb.releaseIconForPageUrl(url);
-                    }
-                } while (c.moveToNext());
+                    // Delete favicons
+                    // TODO don't release if the URL is bookmarked
+                    iconDb.releaseIconForPageUrl(cursor.getString(0));
+                } while (cursor.moveToNext());
 
-                if (!firstTime) {
-                    ContentValues map = new ContentValues();
-                    map.put(BookmarkColumns.VISITS, 0);
-                    map.put(BookmarkColumns.DATE, 0);
-                    /* FIXME: Should I also remove the title? */
-                    cr.update(BOOKMARKS_URI, map, sb.toString(), null);
-                }
-
-                String deleteWhereClause = BookmarkColumns.BOOKMARK + " = 0";
-                if (whereClause != null) {
-                    deleteWhereClause += " AND " + whereClause;
-                }
-                cr.delete(BOOKMARKS_URI, deleteWhereClause, null);
+                cr.delete(History.CONTENT_URI, whereClause, null);
             }
         } catch (IllegalStateException e) {
             Log.e(LOGTAG, "deleteHistoryWhere", e);
             return;
         } finally {
-            if (c != null) c.close();
+            if (cursor != null) cursor.close();
         }
     }
 
@@ -520,10 +515,7 @@
      */
     public static final void deleteFromHistory(ContentResolver cr, 
                                                String url) {
-        StringBuilder sb = new StringBuilder(BookmarkColumns.URL + " = ");
-        DatabaseUtils.appendEscapedSQLString(sb, url);
-        String matchesUrl = sb.toString();
-        deleteHistoryWhere(cr, matchesUrl);
+        cr.delete(History.CONTENT_URI, History.URL + "=?", new String[] { url });
     }
 
     /**
@@ -534,30 +526,13 @@
      * @param search    The string to add to the searches database.
      */
     public static final void addSearchUrl(ContentResolver cr, String search) {
-        long now = new Date().getTime();
-        Cursor c = null;
-        try {
-            c = cr.query(
-                SEARCHES_URI,
-                SEARCHES_PROJECTION,
-                SEARCHES_WHERE_CLAUSE,
-                new String [] { search },
-                null);
-            ContentValues map = new ContentValues();
-            map.put(SearchColumns.SEARCH, search);
-            map.put(SearchColumns.DATE, now);
-            /* We should only get one answer that is exactly the same. */
-            if (c.moveToFirst()) {
-                cr.update(SEARCHES_URI, map, "_id = " + c.getInt(0), null);
-            } else {
-                cr.insert(SEARCHES_URI, map);
-            }
-        } catch (IllegalStateException e) {
-            Log.e(LOGTAG, "addSearchUrl", e);
-        } finally {
-            if (c != null) c.close();
-        }
+        // The content provider will take care of updating existing searches instead of duplicating
+        ContentValues values = new ContentValues();
+        values.put(Searches.SEARCH, search);
+        values.put(Searches.DATE, System.currentTimeMillis());
+        cr.insert(Searches.CONTENT_URI, values);
     }
+
     /**
      * Remove all searches from the search database.
      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
@@ -567,7 +542,7 @@
         // FIXME: Should this clear the urls to which these searches lead?
         // (i.e. remove google.com/query= blah blah blah)
         try {
-            cr.delete(SEARCHES_URI, null, null);
+            cr.delete(Searches.CONTENT_URI, null, null);
         } catch (IllegalStateException e) {
             Log.e(LOGTAG, "clearSearches", e);
         }
@@ -586,35 +561,92 @@
      */
     public static final void requestAllIcons(ContentResolver cr, String where,
             WebIconDatabase.IconListener listener) {
-        WebIconDatabase.getInstance()
-                .bulkRequestIconForPageUrl(cr, where, listener);
+        WebIconDatabase.getInstance().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 {
+        /**
+         * @deprecated Not used.
+         */
+        @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/BrowserContract.java b/core/java/android/provider/BrowserContract.java
new file mode 100644
index 0000000..03bc41a
--- /dev/null
+++ b/core/java/android/provider/BrowserContract.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.provider;
+
+import android.accounts.Account;
+import android.content.ContentProviderClient;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.util.Pair;
+
+/**
+ * @hide
+ */
+public class BrowserContract {
+    /** The authority for the browser provider */
+    public static final String AUTHORITY = "com.android.browser";
+
+    /** A content:// style uri to the authority for the browser provider */
+    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+    /**
+     * An optional insert, update or delete URI parameter that allows the caller
+     * to specify that it is a sync adapter. The default value is false. If true
+     * the dirty flag is not automatically set and the "syncToNetwork" parameter
+     * is set to false when calling
+     * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+     */
+    public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
+
+    /**
+     * A parameter for use when querying any table that allows specifying a limit on the number
+     * of rows returned.
+     */
+    public static final String PARAM_LIMIT = "limit";
+
+    /**
+     * 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 these columns.
+     */
+    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";
+    }
+
+    /**
+     * Convenience definitions for use in implementing chrome bookmarks sync in the Bookmarks table.
+     */
+    public static final class ChromeSyncColumns {
+        private ChromeSyncColumns() {}
+
+        /** The server unique ID for an item */
+        public static final String SERVER_UNIQUE = BaseSyncColumns.SYNC3;
+
+        public static final String FOLDER_NAME_ROOT = "google_chrome";
+        public static final String FOLDER_NAME_BOOKMARKS = "google_chrome_bookmarks";
+        public static final String FOLDER_NAME_BOOKMARKS_BAR = "bookmark_bar";
+        public static final String FOLDER_NAME_OTHER_BOOKMARKS = "other_bookmarks";
+
+        /** The client unique ID for an item */
+        public static final String CLIENT_UNIQUE = BaseSyncColumns.SYNC4;
+    }
+
+    /**
+     * Columns that appear when each row of a table belongs to a specific
+     * account, including sync information that an account may need.
+     */
+    interface SyncColumns extends BaseSyncColumns {
+        /**
+         * 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";
+
+        /**
+         * String that uniquely identifies this row to its source account.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SOURCE_ID = "sourceid";
+
+        /**
+         * Version number that is updated whenever this row or its related data
+         * changes.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String VERSION = "version";
+
+        /**
+         * Flag indicating that {@link #VERSION} has changed, and this row needs
+         * to be synchronized by its owning account.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String DIRTY = "dirty";
+
+        /**
+         * The time that this row was last modified by a client (msecs since the epoch).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DATE_MODIFIED = "modified";
+    }
+
+    interface CommonColumns {
+        /**
+         * The unique ID for a row.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String _ID = "_id";
+
+        /**
+         * The URL of the bookmark.
+         * <P>Type: TEXT (URL)</P>
+         */
+        public static final String URL = "url";
+
+        /**
+         * The user visible title of the bookmark.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TITLE = "title";
+
+        /**
+         * The time that this row was created on its originating client (msecs
+         * since the epoch).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DATE_CREATED = "created";
+    }
+
+    interface ImageColumns {
+        /**
+         * The favicon of the bookmark, may be NULL.
+         * Must decode via {@link BitmapFactory#decodeByteArray}.
+         * <p>Type: BLOB (image)</p>
+         */
+        public static final String FAVICON = "favicon";
+
+        /**
+         * A thumbnail of the page,may be NULL.
+         * Must decode via {@link BitmapFactory#decodeByteArray}.
+         * <p>Type: BLOB (image)</p>
+         */
+        public static final String THUMBNAIL = "thumbnail";
+
+        /**
+         * The touch icon for the web page, may be NULL.
+         * Must decode via {@link BitmapFactory#decodeByteArray}.
+         * <p>Type: BLOB (image)</p>
+         * @hide
+         */
+        public static final String TOUCH_ICON = "touch_icon";
+    }
+
+    interface HistoryColumns {
+        /**
+         * The date the item was last visited, in milliseconds since the epoch.
+         * <p>Type: INTEGER (date in milliseconds since January 1, 1970)</p>
+         */
+        public static final String DATE_LAST_VISITED = "date";
+
+        /**
+         * The number of times the item has been visited.
+         * <p>Type: INTEGER</p>
+         */
+        public static final String VISITS = "visits";
+
+        public static final String USER_ENTERED = "user_entered";
+    }
+
+    /**
+     * The bookmarks table, which holds the user's browser bookmarks.
+     */
+    public static final class Bookmarks implements CommonColumns, ImageColumns, SyncColumns {
+        /**
+         * This utility class cannot be instantiated.
+         */
+        private Bookmarks() {}
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
+
+        /**
+         * The content:// style URI for the default folder
+         */
+        public static final Uri CONTENT_URI_DEFAULT_FOLDER =
+                Uri.withAppendedPath(CONTENT_URI, "folder");
+
+        /**
+         * Query parameter used to specify an account name
+         */
+        public static final String PARAM_ACCOUNT_NAME = "acct_name";
+
+        /**
+         * Query parameter used to specify an account type
+         */
+        public static final String PARAM_ACCOUNT_TYPE = "acct_type";
+
+        /**
+         * Builds a URI that points to a specific folder.
+         * @param folderId the ID of the folder to point to
+         */
+        public static final Uri buildFolderUri(long folderId) {
+            return ContentUris.withAppendedId(CONTENT_URI_DEFAULT_FOLDER, folderId);
+        }
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of bookmarks.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/bookmark";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} of a single bookmark.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/bookmark";
+
+        /**
+         * Query parameter to use if you want to see deleted bookmarks that are still
+         * around on the device and haven't been synced yet.
+         * @see #IS_DELETED
+         */
+        public static final String QUERY_PARAMETER_SHOW_DELETED = "show_deleted";
+
+        /**
+         * Flag indicating if an item is a folder or bookmark. Non-zero values indicate
+         * a folder and zero indicates a bookmark.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String IS_FOLDER = "folder";
+
+        /**
+         * The ID of the parent folder. ID 0 is the root folder.
+         * <P>Type: INTEGER (reference to item in the same table)</P>
+         */
+        public static final String PARENT = "parent";
+
+        /**
+         * The source ID for an item's parent. Read-only.
+         * @see #PARENT
+         */
+        public static final String PARENT_SOURCE_ID = "parent_source";
+
+        /**
+         * The position of the bookmark in relation to it's siblings that share the same
+         * {@link #PARENT}. May be negative.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String POSITION = "position";
+
+        /**
+         * The item that the bookmark should be inserted after.
+         * May be negative.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String INSERT_AFTER = "insert_after";
+
+        /**
+         * The source ID for the item that the bookmark should be inserted after. Read-only.
+         * May be negative.
+         * <P>Type: INTEGER</P>
+         * @see #INSERT_AFTER
+         */
+        public static final String INSERT_AFTER_SOURCE_ID = "insert_after_source";
+
+        /**
+         * A flag to indicate if an item has been deleted. Queries will not return deleted
+         * entries unless you add the {@link #QUERY_PARAMETER_SHOW_DELETED} query paramter
+         * to the URI when performing your query.
+         * <p>Type: INTEGER (non-zero if the item has been deleted, zero if it hasn't)
+         * @see #QUERY_PARAMETER_SHOW_DELETED
+         */
+        public static final String IS_DELETED = "deleted";
+    }
+
+    /**
+     * Read-only table that lists all the accounts that are used to provide bookmarks.
+     */
+    public static final class Accounts {
+        /**
+         * Directory under {@link Bookmarks#CONTENT_URI}
+         */
+        public static final Uri CONTENT_URI =
+                AUTHORITY_URI.buildUpon().appendPath("accounts").build();
+
+        /**
+         * 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";
+    }
+
+    /**
+     * The history table, which holds the browsing history.
+     */
+    public static final class History implements CommonColumns, HistoryColumns, ImageColumns {
+        /**
+         * This utility class cannot be instantiated.
+         */
+        private History() {}
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "history");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of browser history items.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/browser-history";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} of a single browser history item.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/browser-history";
+    }
+
+    /**
+     * The search history table.
+     * @hide
+     */
+    public static final class Searches {
+        private Searches() {}
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "searches");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of browser search items.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/searches";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} of a single browser search item.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/searches";
+
+        /**
+         * The unique ID for a row.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String _ID = "_id";
+
+        /**
+         * 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";
+    }
+
+    /**
+     * A table provided for sync adapters to use for storing private sync state data.
+     *
+     * @see SyncStateContract
+     */
+    public static final class SyncState implements SyncStateContract.Columns {
+        /**
+         * This utility class cannot be instantiated
+         */
+        private SyncState() {}
+
+        public static final String CONTENT_DIRECTORY =
+                SyncStateContract.Constants.CONTENT_DIRECTORY;
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI =
+                Uri.withAppendedPath(AUTHORITY_URI, CONTENT_DIRECTORY);
+
+        /**
+         * @see android.provider.SyncStateContract.Helpers#get
+         */
+        public static byte[] get(ContentProviderClient provider, Account account)
+                throws RemoteException {
+            return SyncStateContract.Helpers.get(provider, CONTENT_URI, account);
+        }
+
+        /**
+         * @see android.provider.SyncStateContract.Helpers#get
+         */
+        public static Pair<Uri, byte[]> getWithUri(ContentProviderClient provider, Account account)
+                throws RemoteException {
+            return SyncStateContract.Helpers.getWithUri(provider, CONTENT_URI, account);
+        }
+
+        /**
+         * @see android.provider.SyncStateContract.Helpers#set
+         */
+        public static void set(ContentProviderClient provider, Account account, byte[] data)
+                throws RemoteException {
+            SyncStateContract.Helpers.set(provider, CONTENT_URI, account, data);
+        }
+
+        /**
+         * @see android.provider.SyncStateContract.Helpers#newSetOperation
+         */
+        public static ContentProviderOperation newSetOperation(Account account, byte[] data) {
+            return SyncStateContract.Helpers.newSetOperation(CONTENT_URI, account, data);
+        }
+    }
+
+    /**
+     * Stores images for URLs. Only support query() and update().
+     * @hide
+     */
+    public static final class Images implements ImageColumns {
+        /**
+         * This utility class cannot be instantiated
+         */
+        private Images() {}
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "images");
+
+        /**
+         * The URL the images came from.
+         * <P>Type: TEXT (URL)</P>
+         */
+        public static final String URL = "url_key";
+    }
+
+    /**
+     * A combined view of bookmarks and history. All bookmarks in all folders are included and
+     * no folders are included.
+     */
+    public static final class Combined implements CommonColumns, HistoryColumns, ImageColumns {
+        /**
+         * This utility class cannot be instantiated
+         */
+        private Combined() {}
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "combined");
+
+        /**
+         * 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 IS_BOOKMARK = "bookmark";
+    }
+
+    /**
+     * A table that stores settings specific to the browser. Only support query and insert.
+     */
+    public static final class Settings {
+        /**
+         * This utility class cannot be instantiated
+         */
+        private Settings() {}
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "settings");
+
+        /**
+         * Key for a setting value.
+         */
+        public static final String KEY = "key";
+
+        /**
+         * Value for a setting.
+         */
+        public static final String VALUE = "value";
+
+        /**
+         * If set to non-0 the user has opted into bookmark sync.
+         */
+        public static final String KEY_SYNC_ENABLED = "sync_enabled";
+
+        /**
+         * Returns true if bookmark sync is enabled
+         */
+        static public boolean isSyncEnabled(Context context) {
+            Cursor cursor = null;
+            try {
+                cursor = context.getContentResolver().query(CONTENT_URI, new String[] { VALUE },
+                        KEY + "=?", new String[] { KEY_SYNC_ENABLED }, null);
+                if (cursor == null || !cursor.moveToFirst()) {
+                    return false;
+                }
+                return cursor.getInt(0) != 0;
+            } finally {
+                if (cursor != null) cursor.close();
+            }
+        }
+
+        /**
+         * Sets the bookmark sync enabled setting.
+         */
+        static public void setSyncEnabled(Context context, boolean enabled) {
+            ContentValues values = new ContentValues();
+            values.put(KEY, KEY_SYNC_ENABLED);
+            values.put(VALUE, enabled ? 1 : 0);
+            context.getContentResolver().insert(CONTENT_URI, values);
+        }
+    }
+}
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 6355a9c..88ce0f0 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>
@@ -1055,7 +1239,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 {
@@ -1453,4 +1637,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/CallLog.java b/core/java/android/provider/CallLog.java
index d52632b..bf051f5 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -88,6 +88,17 @@
         public static final String NUMBER = "number";
 
         /**
+         * The ISO 3166-1 two letters country code of the country where the
+         * user received or made the call.
+         * <P>
+         * Type: TEXT
+         * </P>
+         *
+         * @hide
+         */
+        public static final String COUNTRY_ISO = "countryiso";
+
+        /**
          * The date the call occured, in milliseconds since the epoch
          * <P>Type: INTEGER (long)</P>
          */
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 7484964..36e2c56 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -30,9 +30,9 @@
 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.net.Uri.Builder;
 import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
@@ -41,6 +41,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
+import java.util.ArrayList;
 
 /**
  * <p>
@@ -130,6 +131,20 @@
     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}
+     */
+    public static final String DIRECTORY_PARAM_KEY = "directory";
+
+    /**
+     * A query parameter that limits the number of results returned. The
+     * parameter value should be an integer.
+     */
+    public static final String LIMIT_PARAM_KEY = "limit";
+
+    /**
      * @hide
      */
     public static final class Preferences {
@@ -181,6 +196,326 @@
     }
 
     /**
+     * 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 ContactsContract.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 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>
+     */
+    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.
+         */
+        public static final Uri CONTENT_URI =
+                Uri.withAppendedPath(AUTHORITY_URI, "directories");
+
+        /**
+         * The MIME-type of {@link #CONTENT_URI} providing a directory of
+         * contact directories.
+         */
+        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.
+         */
+        public static final long DEFAULT = 0;
+
+        /**
+         * _ID of the directory that represents locally stored invisible contacts.
+         */
+        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>
+         */
+        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>
+         */
+        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>
+         */
+        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>
+         */
+        public static final String DIRECTORY_AUTHORITY = "authority";
+
+        /**
+         * The account type which this directory is associated.
+         *
+         * <p>TYPE: text</p>
+         */
+        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>
+         */
+        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.
+         */
+        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.
+         */
+        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}.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        public static final int SHORTCUT_SUPPORT_FULL = 2;
+
+        /**
+         * One of {@link #PHOTO_SUPPORT_NONE}, {@link #PHOTO_SUPPORT_THUMBNAIL_ONLY},
+         * {@link #PHOTO_SUPPORT_FULL}. This is a feature flag indicating the extent
+         * to which the directory supports contact photos.
+         */
+        public static final String PHOTO_SUPPORT = "photoSupport";
+
+        /**
+         * An {@link #PHOTO_SUPPORT} setting that indicates that the directory
+         * does not provide any photos.
+         */
+        public static final int PHOTO_SUPPORT_NONE = 0;
+
+        /**
+         * An {@link #PHOTO_SUPPORT} setting that indicates that the directory
+         * can only produce small size thumbnails of contact photos.
+         */
+        public static final int PHOTO_SUPPORT_THUMBNAIL_ONLY = 1;
+
+        /**
+         * An {@link #PHOTO_SUPPORT} setting that indicates that the directory
+         * has full-size contact photos, but cannot provide scaled thumbnails.
+         */
+        public static final int PHOTO_SUPPORT_FULL_SIZE_ONLY = 2;
+
+        /**
+         * An {@link #PHOTO_SUPPORT} setting that indicates that the directory
+         * can produce thumbnails as well as full-size contact photos.
+         */
+        public static final int PHOTO_SUPPORT_FULL = 3;
+
+        /**
+         * 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.
+         */
+        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
      */
@@ -372,12 +707,42 @@
         public static final String NAME_RAW_CONTACT_ID = "name_raw_contact_id";
 
         /**
-         * Reference to the row in the data table holding the photo.
+         * Reference to the row in the data table holding the photo.  A photo can
+         * be referred to either by ID (this field) or by URI (see {@link #PHOTO_THUMBNAIL_URI}
+         * and {@link #PHOTO_URI}).
+         * If PHOTO_ID is null, consult {@link #PHOTO_URI} or {@link #PHOTO_THUMBNAIL_URI},
+         * which is a more generic mechanism for referencing the contact photo, especially for
+         * contacts returned by non-local directories (see {@link Directory}).
+         *
          * <P>Type: INTEGER REFERENCES data(_id)</P>
          */
         public static final String PHOTO_ID = "photo_id";
 
         /**
+         * A URI that can be used to retrieve the contact's full-size photo.
+         * A photo can be referred to either by a URI (this field) or by ID
+         * (see {@link #PHOTO_ID}). If PHOTO_ID is not null, PHOTO_URI and
+         * PHOTO_THUMBNAIL_URI shall not be null (but not necessarily vice versa).
+         * Thus using PHOTO_URI is a more robust method of retrieving contact photos.
+         *
+         * <P>Type: TEXT</P>
+         */
+        public static final String PHOTO_URI = "photo_uri";
+
+        /**
+         * A URI that can be used to retrieve a thumbnail of the contact's photo.
+         * A photo can be referred to either by a URI (this field or {@link #PHOTO_URI})
+         * or by ID (see {@link #PHOTO_ID}). If PHOTO_ID is not null, PHOTO_URI and
+         * PHOTO_THUMBNAIL_URI shall not be null (but not necessarily vice versa).
+         * If the content provider does not differentiate between full-size photos
+         * and thumbnail photos, PHOTO_THUMBNAIL_URI and {@link #PHOTO_URI} can contain
+         * the same value, but either both shell be null or both not null.
+         *
+         * <P>Type: TEXT</P>
+         */
+        public static final String PHOTO_THUMBNAIL_URI = "photo_thumb_uri";
+
+        /**
          * Lookup value that reflects the {@link Groups#GROUP_VISIBLE} state of
          * any {@link CommonDataKinds.GroupMembership} for this contact.
          */
@@ -412,7 +777,6 @@
          * Contact Chat Capabilities. See {@link StatusUpdates} for individual
          * definitions.
          * <p>Type: NUMBER</p>
-         * @hide
          */
         public static final String CONTACT_CHAT_CAPABILITY = "contact_chat_capability";
 
@@ -458,7 +822,6 @@
      * 'family name', 'given name' 'middle name'.  The CJK tradition is
      * 'family name' 'middle name' 'given name', with Japanese favoring a space between
      * the names and Chinese omitting the space.
-     * @hide
      */
     public interface FullNameStyle {
         public static final int UNDEFINED = 0;
@@ -477,7 +840,6 @@
 
     /**
      * Constants for various styles of capturing the pronunciation of a person's name.
-     * @hide
      */
     public interface PhoneticNameStyle {
         public static final int UNDEFINED = 0;
@@ -503,8 +865,6 @@
     /**
      * Types of data used to produce the display name for a contact. Listed in the order
      * of increasing priority.
-     *
-     * @hide
      */
     public interface DisplayNameSources {
         public static final int UNDEFINED = 0;
@@ -520,15 +880,12 @@
      *
      * @see Contacts
      * @see RawContacts
-     * @hide
      */
     protected interface ContactNameColumns {
 
         /**
          * The kind of data that is used as the display name for the contact, such as
-         * structured name or email address.  See DisplayNameSources.
-         *
-         * TODO: convert DisplayNameSources to a link after it is un-hidden
+         * structured name or email address.  See {@link DisplayNameSources}.
          */
         public static final String DISPLAY_NAME_SOURCE = "display_name_source";
 
@@ -576,9 +933,7 @@
 
         /**
          * The phonetic alphabet used to represent the {@link #PHONETIC_NAME}.  See
-         * PhoneticNameStyle.
-         *
-         * TODO: convert PhoneticNameStyle to a link after it is un-hidden
+         * {@link PhoneticNameStyle}.
          */
         public static final String PHONETIC_NAME_STYLE = "phonetic_name_style";
 
@@ -588,13 +943,10 @@
          * {@link #PHONETIC_NAME_STYLE}.
          * </p>
          * <p>
-         * The value may be set manually by the user.
-         * This capability is is of interest only in countries
-         * with commonly used phonetic
-         * alphabets, such as Japan and Korea.  See PhoneticNameStyle.
+         * The value may be set manually by the user. This capability is of
+         * interest only in countries with commonly used phonetic alphabets,
+         * such as Japan and Korea. See {@link PhoneticNameStyle}.
          * </p>
-         *
-         * TODO: convert PhoneticNameStyle to a link after it is un-hidden
          */
         public static final String PHONETIC_NAME = "phonetic_name";
 
@@ -741,6 +1093,20 @@
      * that mime type.</td>
      * </tr>
      * <tr>
+     * <td>long</td>
+     * <td>{@link #PHOTO_URI}</td>
+     * <td>read-only</td>
+     * <td>A URI that can be used to retrieve the contact's full-size photo. This
+     * column is the preferred method of retrieving the contact photo.</td>
+     * </tr>
+     * <tr>
+     * <td>long</td>
+     * <td>{@link #PHOTO_THUMBNAIL_URI}</td>
+     * <td>read-only</td>
+     * <td>A URI that can be used to retrieve the thumbnail of contact's photo.  This
+     * column is the preferred method of retrieving the contact photo.</td>
+     * </tr>
+     * <tr>
      * <td>int</td>
      * <td>{@link #IN_VISIBLE_GROUP}</td>
      * <td>read-only</td>
@@ -976,10 +1342,10 @@
         }
 
         /**
-         * Mark a contact as having been contacted.  This updates the
-         * {@link #TIMES_CONTACTED} and {@link #LAST_TIME_CONTACTED} for the
-         * contact, plus the corresponding values of any associated raw
-         * contacts.
+         * Mark a contact as having been contacted. Updates two fields:
+         * {@link #TIMES_CONTACTED} and {@link #LAST_TIME_CONTACTED}. The
+         * TIMES_CONTACTED field is incremented by 1 and the LAST_TIME_CONTACTED
+         * field is populated with the current system time.
          *
          * @param resolver the ContentResolver to use
          * @param contactId the person who was contacted
@@ -1041,7 +1407,8 @@
 
         /**
          * A sub-directory of a single contact that contains all of the constituent raw contact
-         * {@link ContactsContract.Data} rows.
+         * {@link ContactsContract.Data} rows.  This directory can be used either
+         * with a {@link #CONTENT_URI} or {@link #CONTENT_LOOKUP_URI}.
          */
         public static final class Data implements BaseColumns, DataColumns {
             /**
@@ -1057,6 +1424,57 @@
 
         /**
          * <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>
+         */
+        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
@@ -1081,9 +1499,13 @@
          * </pre>
          *
          * </p>
+         * <p>
+         * This directory can be used either with a {@link #CONTENT_URI} or
+         * {@link #CONTENT_LOOKUP_URI}.
+         * </p>
          */
-        // TODO: add ContactOptionsColumns, ContactStatusColumns
-        public static final class AggregationSuggestions implements BaseColumns, ContactsColumns {
+        public static final class AggregationSuggestions implements BaseColumns, ContactsColumns,
+                ContactOptionsColumns, ContactStatusColumns {
             /**
              * No public constructor since this is a utility class
              */
@@ -1095,6 +1517,106 @@
              * {@link android.provider.ContactsContract.Contacts#CONTENT_FILTER_URI}.
              */
             public static final String CONTENT_DIRECTORY = "suggestions";
+
+            /**
+             * Used with {@link Builder#addParameter} to specify what kind of data is
+             * supplied for the suggestion query.
+             *
+             * @hide
+             */
+            public static final String PARAMETER_MATCH_NAME = "name";
+
+            /**
+             * Used with {@link Builder#addParameter} to specify what kind of data is
+             * supplied for the suggestion query.
+             *
+             * @hide
+             */
+            public static final String PARAMETER_MATCH_EMAIL = "email";
+
+            /**
+             * Used with {@link Builder#addParameter} to specify what kind of data is
+             * supplied for the suggestion query.
+             *
+             * @hide
+             */
+            public static final String PARAMETER_MATCH_PHONE = "phone";
+
+            /**
+             * Used with {@link Builder#addParameter} to specify what kind of data is
+             * supplied for the suggestion query.
+             *
+             * @hide
+             */
+            public static final String PARAMETER_MATCH_NICKNAME = "nickname";
+
+            /**
+             * A convenience builder for aggregation suggestion content URIs.
+             *
+             * TODO: change documentation for this class to use the builder.
+             * @hide
+             */
+            public static final class Builder {
+                private long mContactId;
+                private ArrayList<String> mKinds = new ArrayList<String>();
+                private ArrayList<String> mValues = new ArrayList<String>();
+                private int mLimit;
+
+                /**
+                 * Optional existing contact ID.  If it is not provided, the search
+                 * will be based exclusively on the values supplied with {@link #addParameter}.
+                 */
+                public Builder setContactId(long contactId) {
+                    this.mContactId = contactId;
+                    return this;
+                }
+
+                /**
+                 * A value that can be used when searching for an aggregation
+                 * suggestion.
+                 *
+                 * @param kind can be one of
+                 *            {@link AggregationSuggestions#PARAMETER_MATCH_NAME},
+                 *            {@link AggregationSuggestions#PARAMETER_MATCH_EMAIL},
+                 *            {@link AggregationSuggestions#PARAMETER_MATCH_NICKNAME},
+                 *            {@link AggregationSuggestions#PARAMETER_MATCH_PHONE}
+                 */
+                public Builder addParameter(String kind, String value) {
+                    if (!TextUtils.isEmpty(value)) {
+                        mKinds.add(kind);
+                        mValues.add(value);
+                    }
+                    return this;
+                }
+
+                public Builder setLimit(int limit) {
+                    mLimit = limit;
+                    return this;
+                }
+
+                public Uri build() {
+                    android.net.Uri.Builder builder = Contacts.CONTENT_URI.buildUpon();
+                    builder.appendEncodedPath(String.valueOf(mContactId));
+                    builder.appendPath(Contacts.AggregationSuggestions.CONTENT_DIRECTORY);
+                    if (mLimit != 0) {
+                        builder.appendQueryParameter("limit", String.valueOf(mLimit));
+                    }
+
+                    int count = mKinds.size();
+                    for (int i = 0; i < count; i++) {
+                        builder.appendQueryParameter("query", mKinds.get(i) + ":" + mValues.get(i));
+                    }
+
+                    return builder.build();
+                }
+            }
+
+            /**
+             * @hide
+             */
+            public static final Builder builder() {
+                return new Builder();
+            }
         }
 
         /**
@@ -1130,9 +1652,12 @@
          * <p>You should also consider using the convenience method
          * {@link ContactsContract.Contacts#openContactPhotoInputStream(ContentResolver, Uri)}
          * </p>
+         * <p>
+         * This directory can be used either with a {@link #CONTENT_URI} or
+         * {@link #CONTENT_LOOKUP_URI}.
+         * </p>
          */
-        // TODO: change DataColumns to DataColumnsWithJoins
-        public static final class Photo implements BaseColumns, DataColumns {
+        public static final class Photo implements BaseColumns, DataColumnsWithJoins {
             /**
              * no public constructor since this is a utility class
              */
@@ -1148,7 +1673,6 @@
              * that could be inflated using {@link android.graphics.BitmapFactory}.
              * <p>
              * Type: BLOB
-             * @hide TODO: Unhide in a separate CL
              */
             public static final String PHOTO = DATA15;
         }
@@ -1157,7 +1681,10 @@
          * Opens an InputStream for the contacts's default photo and returns the
          * photo as a byte stream. If there is not photo null will be returned.
          *
-         * @param contactUri the contact whose photo should be used
+         * @param contactUri the contact whose photo should be used. This can be used with
+         * either a {@link #CONTENT_URI} or a {@link #CONTENT_LOOKUP_URI} URI.
+         * </p>
+
          * @return an InputStream of the photo, or null if no photo is present
          */
         public static InputStream openContactPhotoInputStream(ContentResolver cr, Uri contactUri) {
@@ -1245,6 +1772,13 @@
          * @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>
+         */
+        public static final String RAW_CONTACT_IS_READ_ONLY = "raw_contact_is_read_only";
     }
 
     /**
@@ -1630,10 +2164,10 @@
         public static final int AGGREGATION_MODE_DEFAULT = 0;
 
         /**
-         * Do not use.
-         *
-         * TODO: deprecate in favor of {@link #AGGREGATION_MODE_DEFAULT}
+         * Aggregation mode: aggregate at the time the raw contact is inserted/updated.
+         * @deprecated Aggregation is synchronous, this historic value is a no-op
          */
+        @Deprecated
         public static final int AGGREGATION_MODE_IMMEDIATE = 1;
 
         /**
@@ -1701,9 +2235,7 @@
         /**
          * A sub-directory of a single raw contact that contains all of its
          * {@link ContactsContract.Data} rows. To access this directory
-         * append {@link Data#CONTENT_DIRECTORY} to the contact URI.
-         *
-         * TODO: deprecate in favor of {@link RawContacts.Entity}.
+         * append {@link Data#CONTENT_DIRECTORY} to the raw contact URI.
          */
         public static final class Data implements BaseColumns, DataColumns {
             /**
@@ -1722,7 +2254,7 @@
          * <p>
          * A sub-directory of a single raw contact that contains all of its
          * {@link ContactsContract.Data} rows. To access this directory append
-         * {@link #CONTENT_DIRECTORY} to the contact URI. See
+         * {@link RawContacts.Entity#CONTENT_DIRECTORY} to the raw contact URI. See
          * {@link RawContactsEntity} for a stand-alone table containing the same
          * data.
          * </p>
@@ -1734,10 +2266,10 @@
          * null.
          * </p>
          * <p>
-         * Entity reads all
-         * data for a raw contact in one transaction, to guarantee
-         * consistency.
-         * </p>
+         * Using Entity should be preferred to using two separate queries:
+         * RawContacts followed by Data. The reason is that Entity reads all
+         * data for a raw contact in one transaction, so there is no possibility
+         * of the data changing between the two queries.
          */
         public static final class Entity implements BaseColumns, DataColumns {
             /**
@@ -1752,7 +2284,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>
              */
@@ -1840,28 +2372,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());
@@ -1962,27 +2487,24 @@
         /**
          * Contact's audio/video chat capability level.
          * <P>Type: INTEGER (one of the values below)</P>
-         * @hide
          */
         public static final String CHAT_CAPABILITY = "chat_capability";
 
         /**
-         * An allowed value of {@link #CHAT_CAPABILITY}. Indicates that the contact's device can
+         * An allowed flag of {@link #CHAT_CAPABILITY}. Indicates audio-chat capability (microphone
+         * and speaker)
+         */
+        public static final int CAPABILITY_HAS_VOICE = 1;
+
+        /**
+         * An allowed flag of {@link #CHAT_CAPABILITY}. Indicates that the contact's device can
          * display a video feed.
-         * @hide
          */
-        public static final int CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY = 1;
+        public static final int CAPABILITY_HAS_VIDEO = 2;
 
         /**
-         * An allowed value of {@link #CHAT_CAPABILITY}. Indicates audio-chat capability.
-         * @hide
-         */
-        public static final int CAPABILITY_HAS_VOICE = 2;
-
-        /**
-         * An allowed value of {@link #CHAT_CAPABILITY}. Indicates that the contact's device has a
+         * An allowed flag of {@link #CHAT_CAPABILITY}. Indicates that the contact's device has a
          * camera that can be used for video chat (e.g. a front-facing camera on a phone).
-         * @hide
          */
         public static final int CAPABILITY_HAS_CAMERA = 4;
     }
@@ -2028,6 +2550,13 @@
         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>
+         */
+        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.
@@ -2862,6 +3391,14 @@
          * <P>Type: TEXT</P>
          */
         public static final String LABEL = "label";
+
+        /**
+         * The phone number's E164 representation.
+         * <P>Type: TEXT</P>
+         *
+         * @hide
+         */
+        public static final String NORMALIZED_NUMBER = "normalized_number";
     }
 
     /**
@@ -3124,6 +3661,32 @@
      * <li>{@link #AVAILABLE}</li>
      * </ul>
      * </p>
+     * <p>
+     * Since presence status is inherently volatile, the content provider
+     * may choose not to store this field in long-term storage.
+     * </p>
+     * </td>
+     * </tr>
+     * <tr>
+     * <td>int</td>
+     * <td>{@link #CHAT_CAPABILITY}</td>
+     * <td>read/write</td>
+     * <td>Contact IM chat compatibility value. The allowed values combinations of the following
+     * flags. If None of these flags is set, the device can only do text messaging.
+     * <p>
+     * <ul>
+     * <li>{@link #CAPABILITY_HAS_VIDEO}</li>
+     * <li>{@link #CAPABILITY_HAS_VOICE}</li>
+     * <li>{@link #CAPABILITY_HAS_CAMERA}</li>
+     * </ul>
+     * </p>
+     * <p>
+     * Since chat compatibility is inherently volatile as the contact's availability moves from
+     * one device to another, the content provider may choose not to store this field in long-term
+     * storage.
+     * </p>
+     * </td>
+     * </tr>
      * <tr>
      * <td>String</td>
      * <td>{@link #STATUS}</td>
@@ -3691,6 +4254,14 @@
             public static final String NUMBER = DATA;
 
             /**
+             * The phone number's E164 representation.
+             * <P>Type: TEXT</P>
+             *
+             * @hide
+             */
+            public static final String NORMALIZED_NUMBER = DATA4;
+
+            /**
              * @deprecated use {@link #getTypeLabel(Resources, int, CharSequence)} instead.
              * @hide
              */
@@ -3772,7 +4343,7 @@
          * </tr>
          * <tr>
          * <td>String</td>
-         * <td>{@link #DATA}</td>
+         * <td>{@link #ADDRESS}</td>
          * <td>{@link #DATA1}</td>
          * <td>Email address itself.</td>
          * </tr>
@@ -3863,7 +4434,6 @@
             /**
              * The email address.
              * <P>Type: TEXT</P>
-             * @hide TODO: Unhide in a separate CL
              */
             public static final String ADDRESS = DATA1;
 
@@ -5027,6 +5597,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";
     }
 
     /**
@@ -5167,6 +5754,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);
             }
@@ -5683,6 +6272,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.
@@ -5703,6 +6314,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/DrmStore.java b/core/java/android/provider/DrmStore.java
index c438ac4..34f2f0d 100644
--- a/core/java/android/provider/DrmStore.java
+++ b/core/java/android/provider/DrmStore.java
@@ -131,7 +131,7 @@
      * Utility function for inserting a file stream into the DRM content provider.
      *
      * @param cr The content resolver to use
-     * @param fileStream The FileInputStream to insert
+     * @param fis The FileInputStream to insert
      * @param title The title for the content (or null)
      * @return uri to the DRM record or null
      */
@@ -143,11 +143,11 @@
             DrmRawContent content = new DrmRawContent(fis, (int) fis.available(),
                     DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING);
             String mimeType = content.getContentType();
+            long size = fis.getChannel().size();
 
             DrmRightsManager manager = manager = DrmRightsManager.getInstance();
             DrmRights rights = manager.queryRights(content);
             InputStream stream = content.getContentInputStream(rights);
-            long size = stream.available();
 
             Uri contentUri = null;
             if (mimeType.startsWith("audio/")) {
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 075da33..b74e76f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -253,9 +253,103 @@
          * <P>Type: TEXT</P>
          */
         public static final String MIME_TYPE = "mime_type";
+
+        /**
+         * 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 table containing an index of all files in the storage.
+     * This can be used by applications to find all documents of a particular type
+     * and is also used internally by the device side MTP implementation.
+     * @hide
+     */
+    public static final class Files {
+
+        public static Uri getContentUri(String volumeName) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                    "/file");
+        }
+
+        public static final Uri getContentUri(String volumeName,
+                long fileId) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+                    + "/file/" + fileId);
+        }
+
+        public static Uri getMtpObjectsUri(String volumeName) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                    "/object");
+        }
+
+        public static final Uri getMtpObjectsUri(String volumeName,
+                long fileId) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+                    + "/object/" + fileId);
+        }
+
+        // Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
+        public static final Uri getMtpReferencesUri(String volumeName,
+                long fileId) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+                    + "/object/" + fileId + "/references");
+        }
+
+        /**
+         * Fields for master table for all media files.
+         * Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED.
+         */
+        public interface FileColumns 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";
+
+            /**
+             * The MIME type of the file
+             * <P>Type: TEXT</P>
+             */
+            public static final String MIME_TYPE = "mime_type";
+
+            /**
+             * The title of the content
+             * <P>Type: TEXT</P>
+             */
+            public static final String TITLE = "title";
+
+            /**
+             * The media type (audio, video, image or playlist)
+             * of the file, or 0 for not a media file
+             * <P>Type: TEXT</P>
+             */
+            public static final String MEDIA_TYPE = "media_type";
+
+            /**
+             * Constants for MEDIA_TYPE
+             */
+            public static final int MEDIA_TYPE_NONE = 0;
+            public static final int MEDIA_TYPE_IMAGE = 1;
+            public static final int MEDIA_TYPE_AUDIO = 2;
+            public static final int MEDIA_TYPE_VIDEO = 3;
+            public static final int MEDIA_TYPE_PLAYLIST = 4;
+        }
+    }
+
+    /**
      * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
      * to be accessed elsewhere.
      */
@@ -335,26 +429,27 @@
             // 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) {
-                    synchronized (sThumbBufLock) {
-                        if (sThumbBuf == null) {
-                            sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
-                        }
-                        if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
-                            bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.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) {
+                        synchronized (sThumbBufLock) {
+                            if (sThumbBuf == null) {
+                                sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
+                            }
+                            if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
+                                bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.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);
@@ -362,17 +457,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;
@@ -423,6 +514,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;
         }
@@ -1140,6 +1234,19 @@
             }
 
             /**
+             * Get the content:// style URI for querying the genres of an audio file.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @param audioId the ID of the audio file for which to retrieve the genres
+             * @return the URI to for querying the genres for the audio file
+             * with the given the volume and audioID
+             */
+            public static Uri getContentUriForAudioId(String volumeName, int audioId) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/audio/media/" + audioId + "/genres");
+            }
+
+            /**
              * The content:// style URI for the internal storage.
              */
             public static final Uri INTERNAL_CONTENT_URI =
diff --git a/core/java/android/provider/Mtp.java b/core/java/android/provider/Mtp.java
new file mode 100644
index 0000000..de161e7
--- /dev/null
+++ b/core/java/android/provider/Mtp.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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/";
+
+
+   /**
+     * Broadcast Action:  A broadcast to indicate the end of an MTP session with the host.
+     * This broadcast is only sent if MTP activity has modified the media database during the
+     * most recent MTP session
+     */
+    public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END";
+
+    /**
+     * 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, long 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, long objectID) {
+            return Uri.parse(CONTENT_AUTHORITY_DEVICE_SLASH + deviceID
+                    + "/object/" + objectID);
+        }
+
+        public static Uri getContentUriForObjectChildren(int deviceID, long objectID) {
+            return Uri.parse(CONTENT_AUTHORITY_DEVICE_SLASH + deviceID
+                    + "/object/" + objectID + "/child");
+        }
+
+        public static Uri getContentUriForStorageChildren(int deviceID, long 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 = "thumb_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";
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 99d4e87..cf95872 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;
@@ -37,20 +34,18 @@
 import android.database.Cursor;
 import android.database.SQLException;
 import android.net.Uri;
-import android.os.*;
-import android.telephony.TelephonyManager;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
 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;
 
 
 /**
@@ -1023,7 +1018,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;
         }
@@ -1147,6 +1142,7 @@
          */
         public static final int WIFI_SLEEP_POLICY_NEVER = 2;
 
+        //TODO: deprecate static IP constants
         /**
          * Whether to use static IP and other static network attributes.
          * <p>
@@ -1230,7 +1226,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
          */
@@ -1512,6 +1508,12 @@
         public static final String AUTO_TIME = "auto_time";
 
         /**
+         * Value to specify if the user prefers the time zone
+         * to be automatically fetched from the network (NITZ). 1=yes, 0=no
+         */
+        public static final String AUTO_TIME_ZONE = "auto_time_zone";
+
+        /**
          * Display times as 12 or 24 hours
          *   12
          *   24
@@ -1788,6 +1790,12 @@
         public static final String UNLOCK_SOUND = "unlock_sound";
 
         /**
+         * True if we should appear as a PTP device instead of MTP.
+         * @hide
+         */
+        public static final String USE_PTP_INTERFACE = "use_ptp_interface";
+
+        /**
          * Receive incoming SIP calls?
          * 0 = no
          * 1 = yes
@@ -1867,6 +1875,7 @@
             TEXT_AUTO_PUNCTUATE,
             TEXT_SHOW_PASSWORD,
             AUTO_TIME,
+            AUTO_TIME_ZONE,
             TIME_12_24,
             DATE_FORMAT,
             ACCELEROMETER_ROTATION,
@@ -1883,6 +1892,7 @@
             LOCKSCREEN_SOUNDS_ENABLED,
             SHOW_WEB_SUGGESTIONS,
             NOTIFICATION_LIGHT_PULSE,
+            USE_PTP_INTERFACE,
             SIP_CALL_OPTIONS,
             SIP_RECEIVE_CALLS,
             NFC_ON,
@@ -2433,6 +2443,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";
@@ -2444,6 +2462,13 @@
         public static final String DEFAULT_INPUT_METHOD = "default_input_method";
 
         /**
+         * Setting to record the input method subtype used by default, holding the ID
+         * of the desired method.
+         */
+        public static final String SELECTED_INPUT_METHOD_SUBTYPE =
+                "selected_input_method_subtype";
+
+        /**
          * Whether the device has been provisioned (0 = false, 1 = true)
          */
         public static final String DEVICE_PROVISIONED = "device_provisioned";
@@ -2464,11 +2489,32 @@
         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";
+
+        /**
+         * Setting for default DNS in case nobody suggests one
+         * @hide
+         */
+        public static final String DEFAULT_DNS_SERVER = "default_dns_server";
+
+        /**
          * Whether the package installer should allow installation of apps downloaded from
          * sources other than the Android Market (vending machine).
          *
@@ -2499,6 +2545,14 @@
             "lock_pattern_tactile_feedback_enabled";
 
         /**
+         * This preference allows the device to be locked given time after screen goes off,
+         * subject to current DeviceAdmin policy limits.
+         * @hide
+         */
+        public static final String LOCK_SCREEN_LOCK_AFTER_TIMEOUT = "lock_screen_lock_after_timeout";
+
+
+        /**
          * Whether assisted GPS should be enabled or not.
          * @hide
          */
@@ -2559,6 +2613,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.
@@ -2589,6 +2651,60 @@
             "enabled_accessibility_services";
 
         /**
+         * If injection of accessibility enhancing JavaScript scripts
+         * is enabled.
+         * <p>
+         *   Note: Accessibility injecting scripts are served by the
+         *   Google infrastructure and enable users with disabilities to
+         *   efficiantly navigate in and explore web content.
+         * </p>
+         * <p>
+         *   This property represents a boolean value.
+         * </p>
+         * @hide
+         */
+        public static final String ACCESSIBILITY_SCRIPT_INJECTION =
+            "accessibility_script_injection";
+
+        /**
+         * Key bindings for navigation in built-in accessibility support for web content.
+         * <p>
+         *   Note: These key bindings are for the built-in accessibility navigation for
+         *   web content which is used as a fall back solution if JavaScript in a WebView
+         *   is not enabled or the user has not opted-in script injection from Google.
+         * </p>
+         * <p>
+         *   The bindings are separated by semi-colon. A binding is a mapping from
+         *   a key to a sequence of actions (for more details look at
+         *   android.webkit.AccessibilityInjector). A key is represented as the hexademical
+         *   string representation of an integer obtained from a meta state (optional) shifted
+         *   sixteen times left and bitwise ored with a key code. An action is represented
+         *   as a hexademical string representation of an integer where the first two digits
+         *   are navigation action index, the second, the third, and the fourth digit pairs
+         *   represent the action arguments. The separate actions in a binding are colon
+         *   separated. The key and the action sequence it maps to are separated by equals.
+         * </p>
+         * <p>
+         *   For example, the binding below maps the DPAD right button to traverse the
+         *   current navigation axis once without firing an accessibility event and to
+         *   perform the same traversal again but to fire an event:
+         *   <code>
+         *     0x16=0x01000100:0x01000101;
+         *   </code>
+         * </p>
+         * <p>
+         *   The goal of this binding is to enable dynamic rebinding of keys to
+         *   navigation actions for web content without requiring a framework change.
+         * </p>
+         * <p>
+         *   This property represents a string value.
+         * </p>
+         * @hide
+         */
+        public static final String ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS =
+            "accessibility_web_content_key_bindings";
+
+        /**
          * Setting to always use the default text-to-speech settings regardless
          * of the application settings.
          * 1 = override application settings,
@@ -3568,6 +3684,7 @@
             PARENTAL_CONTROL_REDIRECT_URL,
             USB_MASS_STORAGE_ENABLED,
             ACCESSIBILITY_ENABLED,
+            ACCESSIBILITY_SCRIPT_INJECTION,
             BACKUP_AUTO_RESTORE,
             ENABLED_ACCESSIBILITY_SERVICES,
             TTS_USE_DEFAULTS,
@@ -3751,20 +3868,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/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index a52a221..946c266 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -25,6 +25,7 @@
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothA2dp;
 import android.content.BroadcastReceiver;
@@ -32,17 +33,15 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.os.Handler;
-import android.os.Message;
 import android.os.ParcelUuid;
 import android.provider.Settings;
 import android.util.Log;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
+
 
 public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
     private static final String TAG = "BluetoothA2dpService";
@@ -57,11 +56,6 @@
 
     private static final String PROPERTY_STATE = "State";
 
-    private static final String SINK_STATE_DISCONNECTED = "disconnected";
-    private static final String SINK_STATE_CONNECTING = "connecting";
-    private static final String SINK_STATE_CONNECTED = "connected";
-    private static final String SINK_STATE_PLAYING = "playing";
-
     private static int mSinkCount;
 
     private final Context mContext;
@@ -72,6 +66,7 @@
     private final BluetoothAdapter mAdapter;
     private int   mTargetA2dpState;
     private boolean mAdjustedPriority = false;
+    private BluetoothDevice mPlayingA2dpDevice;
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -95,12 +90,12 @@
                                                    BluetoothDevice.ERROR);
                 switch(bondState) {
                 case BluetoothDevice.BOND_BONDED:
-                    if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) {
-                        setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
+                    if (getPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) {
+                        setPriority(device, BluetoothA2dp.PRIORITY_ON);
                     }
                     break;
                 case BluetoothDevice.BOND_NONE:
-                    setSinkPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED);
+                    setPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED);
                     break;
                 }
             } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
@@ -113,7 +108,8 @@
             } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                 if (streamType == AudioManager.STREAM_MUSIC) {
-                    BluetoothDevice sinks[] = getConnectedSinks();
+                    BluetoothDevice sinks[] = getConnectedDevices();
+
                     if (sinks.length != 0 && isPhoneDocked(sinks[0])) {
                         String address = sinks[0].getAddress();
                         int newVolLevel =
@@ -254,7 +250,7 @@
             BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()];
             devices = mAudioDevices.keySet().toArray(devices);
             for (BluetoothDevice device : devices) {
-                int state = getSinkState(device);
+                int state = getConnectionState(device);
                 switch (state) {
                     case BluetoothA2dp.STATE_CONNECTING:
                     case BluetoothA2dp.STATE_CONNECTED:
@@ -277,7 +273,7 @@
 
     private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) {
         if (!mBluetoothService.isEnabled() || !isSinkDevice(device) ||
-                getSinkPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
+                getPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
                 return false;
             }
 
@@ -292,12 +288,26 @@
             return true;
     }
 
-    public synchronized boolean connectSink(BluetoothDevice device) {
+    public synchronized boolean isA2dpPlaying(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+            "Need BLUETOOTH_ADMIN permission");
+        if (DBG) log("isA2dpPlaying(" + device + ")");
+        if (device.equals(mPlayingA2dpDevice)) return true;
+        return false;
+    }
+
+    public synchronized boolean connect(BluetoothDevice device) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                 "Need BLUETOOTH_ADMIN permission");
         if (DBG) log("connectSink(" + device + ")");
         if (!isConnectSinkFeasible(device)) return false;
 
+        for (BluetoothDevice sinkDevice : mAudioDevices.keySet()) {
+            if (getConnectionState(sinkDevice) != BluetoothProfile.STATE_DISCONNECTED) {
+                disconnect(sinkDevice);
+            }
+        }
+
         return mBluetoothService.connectSink(device.getAddress());
     }
 
@@ -307,17 +317,15 @@
         int state = mAudioDevices.get(device);
 
         // ignore if there are any active sinks
-        if (lookupSinksMatchingStates(new int[] {
+        if (getDevicesMatchingConnectionStates(new int[] {
                 BluetoothA2dp.STATE_CONNECTING,
                 BluetoothA2dp.STATE_CONNECTED,
-                BluetoothA2dp.STATE_PLAYING,
-                BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) {
+                BluetoothA2dp.STATE_DISCONNECTING}).length != 0) {
             return false;
         }
 
         switch (state) {
         case BluetoothA2dp.STATE_CONNECTED:
-        case BluetoothA2dp.STATE_PLAYING:
         case BluetoothA2dp.STATE_DISCONNECTING:
             return false;
         case BluetoothA2dp.STATE_CONNECTING:
@@ -343,17 +351,16 @@
             return false;
         }
 
-        int state = getSinkState(device);
+        int state = getConnectionState(device);
         switch (state) {
         case BluetoothA2dp.STATE_DISCONNECTED:
-            return false;
         case BluetoothA2dp.STATE_DISCONNECTING:
-            return true;
+            return false;
         }
         return true;
     }
 
-    public synchronized boolean disconnectSink(BluetoothDevice device) {
+    public synchronized boolean disconnect(BluetoothDevice device) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                 "Need BLUETOOTH_ADMIN permission");
         if (DBG) log("disconnectSink(" + device + ")");
@@ -362,7 +369,7 @@
     }
 
     public synchronized boolean disconnectSinkInternal(BluetoothDevice device) {
-        int state = getSinkState(device);
+        int state = getConnectionState(device);
         String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
 
         // State is CONNECTING or CONNECTED or PLAYING
@@ -408,24 +415,7 @@
         return checkSinkSuspendState(state.intValue());
     }
 
-    public synchronized BluetoothDevice[] getConnectedSinks() {
-        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
-                new int[] {BluetoothA2dp.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING});
-        return sinks.toArray(new BluetoothDevice[sinks.size()]);
-    }
-
-    public synchronized BluetoothDevice[] getNonDisconnectedSinks() {
-        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
-                new int[] {BluetoothA2dp.STATE_CONNECTED,
-                           BluetoothA2dp.STATE_PLAYING,
-                           BluetoothA2dp.STATE_CONNECTING,
-                           BluetoothA2dp.STATE_DISCONNECTING});
-        return sinks.toArray(new BluetoothDevice[sinks.size()]);
-    }
-
-    public synchronized int getSinkState(BluetoothDevice device) {
+    public synchronized int getConnectionState(BluetoothDevice device) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         Integer state = mAudioDevices.get(device);
         if (state == null)
@@ -433,19 +423,41 @@
         return state;
     }
 
-    public synchronized int getSinkPriority(BluetoothDevice device) {
+    public synchronized BluetoothDevice[] getConnectedDevices() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        BluetoothDevice[] sinks = getDevicesMatchingConnectionStates(
+                new int[] {BluetoothA2dp.STATE_CONNECTED});
+        return sinks;
+    }
+
+    public synchronized BluetoothDevice[] getDevicesMatchingConnectionStates(int[] states) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        ArrayList<BluetoothDevice> sinks = new ArrayList();
+        if (mAudioDevices.isEmpty()) {
+            return sinks.toArray(new BluetoothDevice[sinks.size()]);
+        }
+        for (BluetoothDevice device: mAudioDevices.keySet()) {
+            int sinkState = getConnectionState(device);
+            for (int state : states) {
+                if (state == sinkState) {
+                    sinks.add(device);
+                    break;
+                }
+            }
+        }
+        return sinks.toArray(new BluetoothDevice[sinks.size()]);
+    }
+
+    public synchronized int getPriority(BluetoothDevice device) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
                 BluetoothA2dp.PRIORITY_UNDEFINED);
     }
 
-    public synchronized boolean setSinkPriority(BluetoothDevice device, int priority) {
+    public synchronized boolean setPriority(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.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
     }
@@ -471,8 +483,17 @@
                 // We have authorized it and bluez state has changed.
                 addAudioSink(device);
             } else {
-                int prevState = mAudioDevices.get(device);
-                handleSinkStateChange(device, prevState, state);
+                if (state == BluetoothA2dp.STATE_PLAYING && mPlayingA2dpDevice == null) {
+                   mPlayingA2dpDevice = device;
+                   handleSinkPlayingStateChange(device, state, BluetoothA2dp.STATE_NOT_PLAYING);
+                } else if (state == BluetoothA2dp.STATE_CONNECTED && mPlayingA2dpDevice != null) {
+                    mPlayingA2dpDevice = null;
+                    handleSinkPlayingStateChange(device, BluetoothA2dp.STATE_NOT_PLAYING,
+                        BluetoothA2dp.STATE_PLAYING);
+                } else {
+                   int prevState = mAudioDevices.get(device);
+                   handleSinkStateChange(device, prevState, state);
+                }
             }
         }
     }
@@ -484,18 +505,19 @@
                 mSinkCount--;
             } else if (state == BluetoothA2dp.STATE_CONNECTED) {
                 mSinkCount ++;
+                mPlayingA2dpDevice = null;
             }
             mAudioDevices.put(device, state);
 
             checkSinkSuspendState(state);
             mTargetA2dpState = -1;
 
-            if (getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
+            if (getPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
                     state == BluetoothA2dp.STATE_CONNECTING ||
                     state == BluetoothA2dp.STATE_CONNECTED) {
                 // We have connected or attempting to connect.
                 // Bump priority
-                setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
+                setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
             }
 
             if (state == BluetoothA2dp.STATE_CONNECTED) {
@@ -504,45 +526,38 @@
                 adjustOtherSinkPriorities(device);
             }
 
-            Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
+            Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-            intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState);
-            intent.putExtra(BluetoothA2dp.EXTRA_SINK_STATE, state);
+            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+            intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
 
             if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
         }
     }
 
+    private void handleSinkPlayingStateChange(BluetoothDevice device, int state, int prevState) {
+        Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+        if (DBG) log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
+    }
+
     private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
         if (!mAdjustedPriority) {
             for (BluetoothDevice device : mAdapter.getBondedDevices()) {
-                if (getSinkPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
+                if (getPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
                     !device.equals(connectedDevice)) {
-                    setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
+                    setPriority(device, BluetoothA2dp.PRIORITY_ON);
                 }
             }
             mAdjustedPriority = true;
         }
     }
 
-    private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) {
-        Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>();
-        if (mAudioDevices.isEmpty()) {
-            return sinks;
-        }
-        for (BluetoothDevice device: mAudioDevices.keySet()) {
-            int sinkState = getSinkState(device);
-            for (int state : states) {
-                if (state == sinkState) {
-                    sinks.add(device);
-                    break;
-                }
-            }
-        }
-        return sinks;
-    }
-
     private boolean checkSinkSuspendState(int state) {
         boolean result = true;
 
@@ -568,7 +583,7 @@
                 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
                 if (address == null) return;
                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
-                int state = getSinkState(device);
+                int state = getConnectionState(device);
                 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
             }
         }
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 75807bf..4edc01f 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -20,6 +20,9 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
 import android.content.Intent;
@@ -52,6 +55,7 @@
     private final HashMap<String, Integer> mPasskeyAgentRequestData;
     private final BluetoothService mBluetoothService;
     private final BluetoothAdapter mAdapter;
+    private BluetoothA2dp mA2dp;
     private final Context mContext;
     // The WakeLock is used for bringing up the LCD during a pairing request
     // from remote device when Android is in Suspend state.
@@ -116,8 +120,21 @@
                 | PowerManager.ON_AFTER_RELEASE, TAG);
         mWakeLock.setReferenceCounted(false);
         initializeNativeDataNative();
+
+        mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP);
     }
 
+    private BluetoothProfile.ServiceListener mProfileServiceListener =
+        new BluetoothProfile.ServiceListener() {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mA2dp = (BluetoothA2dp) proxy;
+        }
+        public void onServiceDisconnected(int profile) {
+            mA2dp = null;
+        }
+    };
+
+
     protected void finalize() throws Throwable {
         try {
             cleanupNativeDataNative();
@@ -300,7 +317,8 @@
             return;
         }
         if (DBG) {
-            log("Device property changed:" + address + "property:" + name);
+            log("Device property changed: " + address + " property: "
+                    + name + " value: " + propValues[1]);
         }
         BluetoothDevice device = mAdapter.getRemoteDevice(address);
         if (name.equals("Name")) {
@@ -367,6 +385,44 @@
         }
     }
 
+    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 void onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
+        String name = propValues[0];
+        String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
+        if (address == null) {
+            Log.e(TAG, "onPanDevicePropertyChanged: Address of the remote device in null");
+            return;
+        }
+        if (DBG) {
+            log("Pan Device property changed: " + address + "  property: "
+                    + name + " value: "+ propValues[1]);
+        }
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        if (name.equals("Connected")) {
+            if (propValues[1].equals("false")) {
+                mBluetoothService.handlePanDeviceStateChange(device,
+                                          BluetoothInputDevice.STATE_DISCONNECTED);
+            }
+        } else if (name.equals("Interface")) {
+            String iface = propValues[1];
+            mBluetoothService.handlePanDeviceStateChange(device, iface,
+                                            BluetoothInputDevice.STATE_CONNECTED);
+        }
+    }
+
     private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
         if (address == null) {
@@ -519,6 +575,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");
@@ -527,22 +585,33 @@
 
         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);
-            authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
+        if (mA2dp != null &&
+            (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
+              || BluetoothUuid.isAdvAudioDist(uuid)) &&
+              !isOtherSinkInNonDisconnectedState(address)) {
+            authorized = mA2dp.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
             if (authorized) {
                 Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address);
                 mBluetoothService.notifyIncomingA2dpConnection(address);
             } 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 if (BluetoothUuid.isBnep(uuid) || BluetoothUuid.isNap(uuid) &&
+                mBluetoothService.allowIncomingTethering()){
+            authorized = true;
         } else {
             Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
         }
@@ -550,6 +619,18 @@
         return authorized;
     }
 
+    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 onAgentOutOfBandDataAvailable(String objectPath) {
         if (!mBluetoothService.isEnabled()) return false;
 
@@ -561,12 +642,14 @@
             return true;
         }
         return false;
-
     }
 
-    private boolean isOtherSinkInNonDisconnectingState(String address) {
-        BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
-        Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks();
+    private boolean isOtherSinkInNonDisconnectedState(String address) {
+        Set<BluetoothDevice> devices =
+            mA2dp.getDevicesMatchingConnectionStates(new int[] {BluetoothA2dp.STATE_CONNECTED,
+                                                     BluetoothA2dp.STATE_CONNECTING,
+                                                     BluetoothA2dp.STATE_DISCONNECTING});
+
         if (devices.size() == 0) return false;
         for(BluetoothDevice dev: devices) {
             if (!dev.getAddress().equals(address)) return true;
@@ -615,6 +698,60 @@
         }
     }
 
+    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 onPanDeviceConnectionResult(String path, boolean result) {
+        log ("onPanDeviceConnectionResult " + path + " " + 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.getPanDeviceState(device);
+            if (state == BluetoothPan.STATE_CONNECTING) {
+                connected = false;
+            } else if (state == BluetoothPan.STATE_DISCONNECTING) {
+                connected = true;
+            } else {
+                Log.e(TAG, "Error onPanDeviceConnectionResult. State is: "
+                        + state + " result: "+ result);
+            }
+            int newState = connected? BluetoothPan.STATE_CONNECTED :
+                BluetoothPan.STATE_DISCONNECTED;
+            mBluetoothService.handlePanDeviceStateChange(device, newState);
+        }
+    }
+
+    private void onNetworkDeviceDisconnected(String address) {
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        mBluetoothService.handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED);
+    }
+
+    private void onNetworkDeviceConnected(String address, String iface, int destUuid) {
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        mBluetoothService.handlePanDeviceStateChange(device, iface, BluetoothPan.STATE_CONNECTED);
+    }
+
     private void onRestartRequired() {
         if (mBluetoothService.isEnabled()) {
             Log.e(TAG, "*** A serious error occurred (did bluetoothd crash?) - " +
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 71b4ee2..f8caa2c 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -29,7 +29,10 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothDeviceProfileState;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProfileState;
+import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
@@ -40,9 +43,13 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.res.Resources.NotFoundException;
+import android.net.ConnectivityManager;
+import android.net.InterfaceConfiguration;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
@@ -71,8 +78,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";
@@ -80,11 +89,13 @@
 
     private int mNativeData;
     private BluetoothEventLoop mEventLoop;
+    private BluetoothHeadset mBluetoothHeadset;
     private boolean mIsAirplaneSensitive;
     private boolean mIsAirplaneToggleable;
     private int mBluetoothState;
     private boolean mRestart = false;  // need to call enable() after disable()
     private boolean mIsDiscovering;
+    private boolean mTetheringOn;
 
     private BluetoothAdapter mAdapter;  // constant after init()
     private final BondState mBondState = new BondState();  // local cache of bondings
@@ -113,6 +124,13 @@
     private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
     private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
 
+    private ArrayList<String> mBluetoothIfaceAddresses;
+    private int mMaxPanDevices;
+
+    private static final String BLUETOOTH_IFACE_ADDR_START= "192.168.44.1";
+    private static final int BLUETOOTH_MAX_PAN_CONNECTIONS = 5;
+    private static final String BLUETOOTH_NETMASK        = "255.255.255.0";
+
     // The timeout used to sent the UUIDs Intent
     // This timeout should be greater than the page timeout
     private static final int UUID_INTENT_DELAY = 6000;
@@ -136,10 +154,14 @@
     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 final HashMap<BluetoothDevice, Pair<Integer, String>> mPanDevices;
     private final HashMap<String, Pair<byte[], byte[]>> mDeviceOobData;
 
+
     private static String mDockAddress;
     private String mDockPin;
 
@@ -190,6 +212,7 @@
 
         mBluetoothState = BluetoothAdapter.STATE_OFF;
         mIsDiscovering = false;
+        mTetheringOn = false;
         mAdapterProperties = new HashMap<String, String>();
         mDeviceProperties = new HashMap<String, Map<String,String>>();
 
@@ -201,15 +224,27 @@
         mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
         mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
         mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
+        mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID);
+
+        mBluetoothIfaceAddresses = new ArrayList<String>();
+        try {
+            mMaxPanDevices = context.getResources().getInteger(
+                            com.android.internal.R.integer.config_max_pan_devices);
+        } catch (NotFoundException e) {
+            mMaxPanDevices = BLUETOOTH_MAX_PAN_CONNECTIONS;
+        }
 
         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>();
+        mPanDevices = new HashMap<BluetoothDevice, Pair<Integer, String>>();
     }
 
     public static synchronized String readDockBluetoothAddress() {
@@ -337,6 +372,7 @@
         }
         setBluetoothState(BluetoothAdapter.STATE_TURNING_OFF);
         mHandler.removeMessages(MESSAGE_REGISTER_SDP_RECORDS);
+        setBluetoothTetheringNative(false, BluetoothPan.NAP_ROLE, BluetoothPan.NAP_BRIDGE);
 
         // Allow 3 seconds for profiles to gracefully disconnect
         // TODO: Introduce a callback mechanism so that each profile can notify
@@ -567,8 +603,12 @@
                 mBondState.readAutoPairingData();
                 mBondState.loadBondState();
                 initProfileState();
+
+                //Register SDP records.
                 mHandler.sendMessageDelayed(
                         mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000);
+                setBluetoothTetheringNative(true, BluetoothPan.NAP_ROLE, BluetoothPan.NAP_BRIDGE);
+
 
                 // Log bluetooth on to battery stats.
                 long ident = Binder.clearCallingIdentity();
@@ -747,6 +787,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);
@@ -1362,6 +1406,409 @@
         return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address);
     }
 
+    public synchronized boolean isTetheringOn() {
+        return mTetheringOn;
+    }
+
+    /*package*/ synchronized boolean allowIncomingTethering() {
+        if (isTetheringOn() && getConnectedPanDevices().length < mMaxPanDevices)
+            return true;
+        return false;
+    }
+
+    private BroadcastReceiver mTetheringReceiver = null;
+
+    public synchronized void setBluetoothTethering(boolean value) {
+        if (!value) {
+            disconnectPan();
+        }
+
+        if (getBluetoothState() != BluetoothAdapter.STATE_ON && value) {
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+            mTetheringReceiver = new BroadcastReceiver() {
+                @Override
+                public synchronized void onReceive(Context context, Intent intent) {
+                    if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
+                            == BluetoothAdapter.STATE_ON) {
+                        mTetheringOn = true;
+                        mContext.unregisterReceiver(mTetheringReceiver);
+                    }
+                }
+            };
+            mContext.registerReceiver(mTetheringReceiver, filter);
+        } else {
+            mTetheringOn = value;
+        }
+    }
+
+    public synchronized int getPanDeviceState(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Pair<Integer, String> panDevice = mPanDevices.get(device);
+        if (panDevice == null) {
+            return BluetoothPan.STATE_DISCONNECTED;
+        }
+        return panDevice.first;
+    }
+
+    public synchronized boolean connectPanDevice(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        if (DBG) log("connect PAN(" + objectPath + ")");
+        if (getPanDeviceState(device) != BluetoothPan.STATE_DISCONNECTED) {
+            log (device + " already connected to PAN");
+        }
+
+        int connectedCount = 0;
+        for (BluetoothDevice panDevice: mPanDevices.keySet()) {
+            if (getPanDeviceState(panDevice) == BluetoothPan.STATE_CONNECTED) {
+                connectedCount ++;
+            }
+        }
+        if (connectedCount > 8) {
+            log (device + " could not connect to PAN because 8 other devices are already connected");
+            return false;
+        }
+
+        handlePanDeviceStateChange(device, BluetoothPan.STATE_CONNECTING);
+        if (connectPanDeviceNative(objectPath, "nap", "panu")) {
+            log ("connecting to PAN");
+            return true;
+        } else {
+            handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED);
+            log ("could not connect to PAN");
+            return false;
+        }
+    }
+
+    private synchronized boolean disconnectPan() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (DBG) log("disconnect all PAN devices");
+
+        for (BluetoothDevice device: mPanDevices.keySet()) {
+            if (getPanDeviceState(device) == BluetoothPan.STATE_CONNECTED) {
+                if (!disconnectPanDevice(device)) {
+                    log ("could not disconnect Pan Device "+device.getAddress());
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public synchronized BluetoothDevice[] getConnectedPanDevices() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>();
+        for (BluetoothDevice device: mPanDevices.keySet()) {
+            if (getPanDeviceState(device) == BluetoothPan.STATE_CONNECTED) {
+                devices.add(device);
+            }
+        }
+        return devices.toArray(new BluetoothDevice[devices.size()]);
+    }
+
+    public synchronized boolean disconnectPanDevice(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        if (DBG) log("disconnect PAN(" + objectPath + ")");
+        if (getPanDeviceState(device) != BluetoothPan.STATE_CONNECTED) {
+            log (device + " already disconnected from PAN");
+        }
+        handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTING);
+        return disconnectPanDeviceNative(objectPath);
+    }
+
+    /*package*/ synchronized void handlePanDeviceStateChange(BluetoothDevice device,
+                                                             String iface,
+                                                             int state) {
+        int prevState;
+        String ifaceAddr = null;
+
+        if (mPanDevices.get(device) == null) {
+            prevState = BluetoothPan.STATE_DISCONNECTED;
+        } else {
+            prevState = mPanDevices.get(device).first;
+            ifaceAddr = mPanDevices.get(device).second;
+        }
+        if (prevState == state) return;
+
+        if (state == BluetoothPan.STATE_CONNECTED) {
+            ifaceAddr = enableTethering(iface);
+            if (ifaceAddr == null) Log.e(TAG, "Error seting up tether interface");
+        } else if (state == BluetoothPan.STATE_DISCONNECTED) {
+            if (ifaceAddr != null) {
+                mBluetoothIfaceAddresses.remove(ifaceAddr);
+                ifaceAddr = null;
+            }
+        }
+
+        Pair<Integer, String> value = new Pair<Integer, String>(state, ifaceAddr);
+        mPanDevices.put(device, value);
+
+        Intent intent = new Intent(BluetoothPan.ACTION_PAN_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothPan.EXTRA_PREVIOUS_PAN_STATE, prevState);
+        intent.putExtra(BluetoothPan.EXTRA_PAN_STATE, state);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+        if (DBG) log("Pan Device state : device: " + device + " State:" + prevState + "->" + state);
+    }
+
+    /*package*/ synchronized void handlePanDeviceStateChange(BluetoothDevice device,
+                                                             int state) {
+        handlePanDeviceStateChange(device, null, state);
+    }
+
+    private String createNewTetheringAddressLocked() {
+        if (getConnectedPanDevices().length == mMaxPanDevices) {
+            log("Max PAN device connections reached");
+            return null;
+        }
+        String address = BLUETOOTH_IFACE_ADDR_START;
+        while (true) {
+            if (mBluetoothIfaceAddresses.contains(address)) {
+                String[] addr = address.split("\\.");
+                Integer newIp = Integer.parseInt(addr[2]) + 1;
+                address = address.replace(addr[2], newIp.toString());
+            } else {
+                break;
+            }
+        }
+        mBluetoothIfaceAddresses.add(address);
+        return address;
+    }
+
+    // configured when we start tethering
+    private synchronized String enableTethering(String iface) {
+        log("updateTetherState:" + iface);
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+        ConnectivityManager cm =
+            (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
+
+        // bring toggle the interfaces
+        String[] currentIfaces = new String[0];
+        try {
+            currentIfaces = service.listInterfaces();
+        } catch (Exception e) {
+            Log.e(TAG, "Error listing Interfaces :" + e);
+            return null;
+        }
+
+        boolean found = false;
+        for (String currIface: currentIfaces) {
+            if (currIface.equals(iface)) {
+                found = true;
+                break;
+            }
+        }
+
+        if (!found) return null;
+
+        String address = createNewTetheringAddressLocked();
+        if (address == null) return null;
+
+        InterfaceConfiguration ifcg = null;
+        try {
+            ifcg = service.getInterfaceConfig(iface);
+            if (ifcg != null) {
+                String[] addr = BLUETOOTH_NETMASK.split("\\.");
+                ifcg.netmask = (Integer.parseInt(addr[0]) << 24) +
+                        (Integer.parseInt(addr[1]) << 16) +
+                        (Integer.parseInt(addr[2]) << 8) +
+                        (Integer.parseInt(addr[3]));
+                if (ifcg.ipAddr == 0) {
+                    addr = address.split("\\.");
+
+                    ifcg.ipAddr = (Integer.parseInt(addr[0]) << 24) +
+                            (Integer.parseInt(addr[1]) << 16) +
+                            (Integer.parseInt(addr[2]) << 8) +
+                            (Integer.parseInt(addr[3]));
+                    ifcg.interfaceFlags =
+                        ifcg.interfaceFlags.replace("down", "up");
+                }
+                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", "");
+                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("  "," ");
+                service.setInterfaceConfig(iface, ifcg);
+                if (cm.tether(iface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    Log.e(TAG, "Error tethering "+iface);
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error configuring interface " + iface + ", :" + e);
+            return null;
+        }
+        return 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);
     }
@@ -1384,7 +1831,7 @@
             if (updateRemoteDevicePropertiesCache(address))
                 return getRemoteDeviceProperty(address, property);
         }
-        Log.e(TAG, "getRemoteDeviceProperty: " + property + "not present:" + address);
+        Log.e(TAG, "getRemoteDeviceProperty: " + property + " not present: " + address);
         return null;
     }
 
@@ -1989,7 +2436,8 @@
         pw.println("Local name = " + getName());
         pw.println("isDiscovering() = " + isDiscovering());
 
-        BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
+        mAdapter.getProfileProxy(mContext,
+                                 mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
 
         pw.println("\n--Known devices--");
         for (String address : mDeviceProperties.keySet()) {
@@ -2034,24 +2482,48 @@
         // Rather not do this from here, but no-where else and I need this
         // dump
         pw.println("\n--Headset Service--");
-        switch (headset.getState(headset.getCurrentHeadset())) {
-        case BluetoothHeadset.STATE_DISCONNECTED:
-            pw.println("getState() = STATE_DISCONNECTED");
-            break;
-        case BluetoothHeadset.STATE_CONNECTING:
-            pw.println("getState() = STATE_CONNECTING");
-            break;
-        case BluetoothHeadset.STATE_CONNECTED:
-            pw.println("getState() = STATE_CONNECTED");
-            break;
-        case BluetoothHeadset.STATE_ERROR:
-            pw.println("getState() = STATE_ERROR");
-            break;
+        if (mBluetoothHeadset != null) {
+           Set<BluetoothDevice> deviceSet = mBluetoothHeadset.getConnectedDevices();
+           if (deviceSet.size() == 0) {
+              pw.println("\n--No headsets connected--");
+           }
+           BluetoothDevice device = (BluetoothDevice) deviceSet.toArray()[0];
+
+            switch (mBluetoothHeadset.getConnectionState(device)) {
+                case BluetoothHeadset.STATE_DISCONNECTED:
+                    pw.println("getConnectionState() = STATE_DISCONNECTED");
+                    break;
+                case BluetoothHeadset.STATE_CONNECTING:
+                    pw.println("getConnectionState() = STATE_CONNECTING");
+                    break;
+                case BluetoothHeadset.STATE_CONNECTED:
+                    pw.println("getConnectionState() = STATE_CONNECTED");
+                    break;
+                case BluetoothHeadset.STATE_DISCONNECTING:
+                    pw.println("getConnectionState() = STATE_DISCONNECTING");
+                    break;
+                case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                    pw.println("getConnectionState() = STATE_AUDIO_CONNECTED");
+                    break;
+            }
+
+            deviceSet.clear();
+            deviceSet = mBluetoothHeadset.getDevicesMatchingConnectionStates(new int[] {
+                     BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED});
+            pw.println("\n--Connected and Disconnected Headsets");
+            for (BluetoothDevice dev: deviceSet) {
+                pw.println(device);
+                if (mBluetoothHeadset.isAudioConnected(device)) {
+                    pw.println("SCO audio connected to device:" + device);
+                }
+            }
+
+            pw.println("\ngetCurrentHeadset() = " + device);
+            pw.println("getBatteryUsageHint() = " +
+                       mBluetoothHeadset.getBatteryUsageHint(device));
+            mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
         }
 
-        pw.println("\ngetCurrentHeadset() = " + headset.getCurrentHeadset());
-        pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint());
-        headset.close();
         pw.println("\n--Application Service Records--");
         for (Integer handle : mServiceRecordToPid.keySet()) {
             Integer pid = mServiceRecordToPid.get(handle);
@@ -2059,6 +2531,16 @@
         }
     }
 
+    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+        new BluetoothProfile.ServiceListener() {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mBluetoothHeadset = (BluetoothHeadset) proxy;
+    }
+        public void onServiceDisconnected(int profile) {
+            mBluetoothHeadset = null;
+        }
+    };
+
     /* package */ static int bluezStringToScanMode(boolean pairable, boolean discoverable) {
         if (pairable && discoverable)
             return BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
@@ -2118,6 +2600,8 @@
     }
 
     public boolean connectHeadset(String address) {
+        if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
+
         BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
         if (state != null) {
             Message msg = new Message();
@@ -2130,6 +2614,8 @@
     }
 
     public boolean disconnectHeadset(String address) {
+        if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
+
         BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
         if (state != null) {
             Message msg = new Message();
@@ -2142,6 +2628,8 @@
     }
 
     public boolean connectSink(String address) {
+        if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
+
         BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
         if (state != null) {
             Message msg = new Message();
@@ -2154,6 +2642,8 @@
     }
 
     public boolean disconnectSink(String address) {
+        if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
+
         BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
         if (state != null) {
             Message msg = new Message();
@@ -2277,4 +2767,10 @@
             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);
+
+    private native boolean setBluetoothTetheringNative(boolean value, String nap, String bridge);
+    private native boolean connectPanDeviceNative(String path, String srcRole, String dstRole);
+    private native boolean disconnectPanDeviceNative(String path);
 }
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 8fa0d59..cd73ba8 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -50,7 +50,7 @@
     private static final String TAG = "SpeechRecognizer";
 
     /**
-     * Used to retrieve an {@code ArrayList&lt;String&gt;} from the {@link Bundle} passed to the
+     * Used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
      * {@link RecognitionListener#onResults(Bundle)} and
      * {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
      * recognition results, where the first element is the most likely candidate.
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
old mode 100755
new mode 100644
index 4e197cd..54ac906
--- 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 = getParagraphSpans(sp, 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 = getParagraphSpans(sp, 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 = getParagraphSpans(spanned, 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 = getParagraphSpans((Spanned) mText, 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 = getParagraphSpans((Spanned) mText, 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) {
@@ -749,20 +916,16 @@
         if (line == getLineCount() - 1)
             max++;
 
-        if (line != getLineCount() - 1)
-            max = TextUtils.getOffsetBefore(mText, getLineEnd(line));
-
         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) {
@@ -795,7 +958,7 @@
 
                 if (dist < bestdist) {
                     bestdist = dist;
-                    best = low;   
+                    best = low;
                 }
             }
 
@@ -805,8 +968,6 @@
                 bestdist = dist;
                 best = here;
             }
-
-            here = there;
         }
 
         float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
@@ -826,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) {
@@ -885,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 = TextUtils.getOffsetBefore(mText, 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 = TextUtils.getOffsetBefore(mText, 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;
 
@@ -1118,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,
@@ -1130,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) |
@@ -1207,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;
 
@@ -1218,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;
         }
     }
 
@@ -1260,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);
@@ -1296,7 +1308,7 @@
 
         if (mSpannedText) {
             Spanned sp = (Spanned) mText;
-            AlignmentSpan[] spans = sp.getSpans(getLineStart(line),
+            AlignmentSpan[] spans = getParagraphSpans(sp, getLineStart(line),
                                                 getLineEnd(line),
                                                 AlignmentSpan.class);
 
@@ -1313,423 +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 (j != there && bm != null) {
-                        if (offset == start + j) return h;
-                        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 = getParagraphSpans(spanned, 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 = getParagraphSpans(spanned, 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;
+        }
     }
 
     /**
@@ -1751,7 +1513,7 @@
 
         if (text instanceof Spanned) {
             if (tabs == null) {
-                tabs = ((Spanned) text).getSpans(start, end, TabStopSpan.class);
+                tabs = getParagraphSpans((Spanned) text, start, end, TabStopSpan.class);
                 alltabs = true;
             }
 
@@ -1778,6 +1540,38 @@
         return mSpannedText;
     }
 
+    /**
+     * Returns the same as <code>text.getSpans()</code>, except where
+     * <code>start</code> and <code>end</code> are the same and are not
+     * at the very beginning of the text, in which case an empty array
+     * is returned instead.
+     * <p>
+     * This is needed because of the special case that <code>getSpans()</code>
+     * on an empty range returns the spans adjacent to that range, which is
+     * primarily for the sake of <code>TextWatchers</code> so they will get
+     * notifications when text goes from empty to non-empty.  But it also
+     * has the unfortunate side effect that if the text ends with an empty
+     * paragraph, that paragraph accidentally picks up the styles of the
+     * preceding paragraph (even though those styles will not be picked up
+     * by new text that is inserted into the empty paragraph).
+     * <p>
+     * The reason it just checks whether <code>start</code> and <code>end</code>
+     * is the same is that the only time a line can contain 0 characters
+     * is if it is the final paragraph of the Layout; otherwise any line will
+     * contain at least one printing or newline character.  The reason for the
+     * additional check if <code>start</code> is greater than 0 is that
+     * if the empty paragraph is the entire content of the buffer, paragraph
+     * styles that are already applied to the buffer will apply to text that
+     * is inserted into it.
+     */
+    /* package */ static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) {
+        if (start == end && start > 0) {
+            return (T[]) ArrayUtils.emptyArray(type);
+        }
+
+        return text.getSpans(start, end, type);
+    }
+
     private void ellipsize(int start, int end, int line,
                            char[] dest, int destoff) {
         int ellipsisCount = getEllipsisCount(line);
@@ -1808,23 +1602,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;
         }
     }
@@ -1835,6 +1628,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.
@@ -1874,7 +1668,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);
@@ -1935,17 +1729,22 @@
     private Alignment mAlignment = Alignment.ALIGN_NORMAL;
     private float mSpacingMult;
     private float mSpacingAdd;
-    private static Rect sTempRect = new Rect();
+    private static final Rect sTempRect = new Rect();
     private boolean mSpannedText;
 
     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,
@@ -1957,8 +1756,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/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
old mode 100755
new mode 100644
index d0d2482..cc969cb
--- 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 = getParagraphSpans(spanned, 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 = getParagraphSpans(spanned, 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,166 +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) {
-                    final int actualNum = paint.getTextWidths(sub, i, next, widths);
-                    if (next - i > actualNum)
-                        adjustTextWidths(widths, sub, i, next, actualNum);
-                    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);
 
-                    final int actualNum = Styled.getTextWidths(paint, mWorkPaint,
-                            spanned, i, next,
-                            widths, fm);
-                    if (next - i > actualNum)
-                        adjustTextWidths(widths, spanned, i, next, actualNum);
-                    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 = getParagraphSpans(spanned, 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.
@@ -380,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);
@@ -415,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
@@ -430,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;
 
@@ -452,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++;
                             }
 
@@ -461,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);
 
@@ -488,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++;
                             }
 
@@ -497,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);
 
@@ -513,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,
@@ -533,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
                         }
@@ -555,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);
 
@@ -575,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;
         }
 
@@ -603,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
@@ -948,53 +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 static void adjustTextWidths(float[] widths, CharSequence text,
-                              int curPos, int nextPos, int actualNum) {
-        try {
-            int dstIndex = nextPos - curPos - 1;
-            for (int srcIndex = actualNum - 1; srcIndex >= 0; srcIndex--) {
-                final char c = text.charAt(dstIndex + curPos);
-                if (c >= 0xD800 && c <= 0xDFFF) {
-                    widths[dstIndex--] = 0.0f;
-                }
-                widths[dstIndex--] = widths[srcIndex];
-            }
-        } catch (IndexOutOfBoundsException e) {
-            Log.e("text", "adjust text widths failed");
-        }
-    }
-
     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;
@@ -1002,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];
@@ -1079,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);
             }
@@ -1142,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;
@@ -1162,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;
@@ -1178,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;
@@ -1195,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;
@@ -1206,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;
@@ -1223,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.
@@ -1252,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) {
@@ -1329,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 100755
index 13cc42c..0000000
--- a/core/java/android/text/Styled.java
+++ /dev/null
@@ -1,436 +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);
-			}
-		}
-	
-        int result;
-        if (replacement == null) {
-            workPaint.getFontMetricsInt(fmi);
-            result = 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;
-            }
-            result = end - start;
-        }
-        return result;
-    }
-
-    /**
-     * 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..2f7482c
--- /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.v("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 8675d05..7748265 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
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index a19a78e..3b98fc3 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -44,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));
@@ -51,6 +52,7 @@
 
             if (a == null) {
                 a = layout.getParagraphAlignment(i);
+                ltr = layout.getParagraphDirection(i) > 0;
             }
         }
 
@@ -58,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);
             }
         }
diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java
index a767ea1..dfe00c9b 100644
--- a/core/java/android/util/AndroidException.java
+++ b/core/java/android/util/AndroidException.java
@@ -27,6 +27,10 @@
         super(name);
     }
 
+    public AndroidException(String name, Throwable cause) {
+        super(name, cause);
+    }
+
     public AndroidException(Exception cause) {
         super(cause);
     }
diff --git a/core/java/android/util/AndroidRuntimeException.java b/core/java/android/util/AndroidRuntimeException.java
index 4ed17bc..2b824bf 100644
--- a/core/java/android/util/AndroidRuntimeException.java
+++ b/core/java/android/util/AndroidRuntimeException.java
@@ -27,6 +27,10 @@
         super(name);
     }
 
+    public AndroidRuntimeException(String name, Throwable cause) {
+        super(name, cause);
+    }
+
     public AndroidRuntimeException(Exception cause) {
         super(cause);
     }
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/Finalizers.java b/core/java/android/util/Finalizers.java
new file mode 100644
index 0000000..671f2d4
--- /dev/null
+++ b/core/java/android/util/Finalizers.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.util;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+
+/**
+ * This class can be used to implement reliable finalizers.
+ * 
+ * @hide
+ */
+public final class Finalizers {
+    private static final String LOG_TAG = "Finalizers";
+    
+    private static final Object[] sLock = new Object[0];
+    private static boolean sInit;
+    private static Reclaimer sReclaimer;
+
+    /**
+     * Subclass of PhantomReference used to reclaim resources.
+     */
+    public static abstract class ReclaimableReference<T> extends PhantomReference<T> {
+        public ReclaimableReference(T r, ReferenceQueue<Object> q) {
+            super(r, q);
+        }
+        
+        public abstract void reclaim();
+    }
+
+    /**
+     * Returns the queue used to reclaim ReclaimableReferences.
+     * 
+     * @return A reference queue or null before initialization
+     */
+    public static ReferenceQueue<Object> getQueue() {
+        synchronized (sLock) {
+            if (!sInit) {
+                return null;
+            }
+            if (!sReclaimer.isRunning()) {
+                sReclaimer = new Reclaimer(sReclaimer.mQueue);
+                sReclaimer.start();
+            }
+            return sReclaimer.mQueue;
+        }
+    }
+
+    /**
+     * Invoked by Zygote. Don't touch!
+     */
+    public static void init() {
+        synchronized (sLock) {
+            if (!sInit && sReclaimer == null) {
+                sReclaimer = new Reclaimer();
+                sReclaimer.start();
+                sInit = true;
+            }
+        }
+    }
+    
+    private static class Reclaimer extends Thread {
+        ReferenceQueue<Object> mQueue;
+
+        private volatile boolean mRunning = false;
+
+        Reclaimer() {
+            this(new ReferenceQueue<Object>());
+        }
+
+        Reclaimer(ReferenceQueue<Object> queue) {
+            super("Reclaimer");
+            setDaemon(true);
+            mQueue = queue;            
+        }
+
+        @Override
+        public void start() {
+            mRunning = true;
+            super.start();
+        }
+
+        boolean isRunning() {
+            return mRunning;
+        }
+
+        @SuppressWarnings({"InfiniteLoopStatement"})
+        @Override
+        public void run() {
+            try {
+                while (true) {
+                    try {
+                        cleanUp(mQueue.remove());
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Reclaimer thread exiting: ", e);
+            } finally {
+                mRunning = false;
+            }
+        }
+
+        private void cleanUp(Reference<?> reference) {
+            do {
+                reference.clear();
+                ((ReclaimableReference<?>) reference).reclaim();
+            } while ((reference = mQueue.poll()) != null);
+        }
+    }
+}
diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java
new file mode 100644
index 0000000..3345bfa
--- /dev/null
+++ b/core/java/android/util/JsonReader.java
@@ -0,0 +1,1058 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value as a stream of tokens. This stream includes both literal
+ * values (strings, numbers, booleans, and nulls) as well as the begin and
+ * end delimiters of objects and arrays. The tokens are traversed in
+ * depth-first order, the same order that they appear in the JSON document.
+ * Within JSON objects, name/value pairs are represented by a single token.
+ *
+ * <h3>Parsing JSON</h3>
+ * To create a recursive descent parser your own JSON streams, first create an
+ * entry point method that creates a {@code JsonReader}.
+ *
+ * <p>Next, create handler methods for each structure in your JSON text. You'll
+ * need a method for each object type and for each array type.
+ * <ul>
+ *   <li>Within <strong>array handling</strong> methods, first call {@link
+ *       #beginArray} to consume the array's opening bracket. Then create a
+ *       while loop that accumulates values, terminating when {@link #hasNext}
+ *       is false. Finally, read the array's closing bracket by calling {@link
+ *       #endArray}.
+ *   <li>Within <strong>object handling</strong> methods, first call {@link
+ *       #beginObject} to consume the object's opening brace. Then create a
+ *       while loop that assigns values to local variables based on their name.
+ *       This loop should terminate when {@link #hasNext} is false. Finally,
+ *       read the object's closing brace by calling {@link #endObject}.
+ * </ul>
+ * <p>When a nested object or array is encountered, delegate to the
+ * corresponding handler method.
+ *
+ * <p>When an unknown name is encountered, strict parsers should fail with an
+ * exception. Lenient parsers should call {@link #skipValue()} to recursively
+ * skip the value's nested tokens, which may otherwise conflict.
+ *
+ * <p>If a value may be null, you should first check using {@link #peek()}.
+ * Null literals can be consumed using either {@link #nextNull()} or {@link
+ * #skipValue()}.
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code
+ * [
+ *   {
+ *     "id": 912345678901,
+ *     "text": "How do I read JSON on Android?",
+ *     "geo": null,
+ *     "user": {
+ *       "name": "android_newb",
+ *       "followers_count": 41
+ *      }
+ *   },
+ *   {
+ *     "id": 912345678902,
+ *     "text": "@android_newb just use android.util.JsonReader!",
+ *     "geo": [50.454722, -104.606667],
+ *     "user": {
+ *       "name": "jesse",
+ *       "followers_count": 2
+ *     }
+ *   }
+ * ]}</pre>
+ * This code implements the parser for the above structure: <pre>   {@code
+ *
+ *   public List<Message> readJsonStream(InputStream in) throws IOException {
+ *     JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+ *     return readMessagesArray(reader);
+ *   }
+ *
+ *   public List<Message> readMessagesArray(JsonReader reader) throws IOException {
+ *     List<Message> messages = new ArrayList<Message>();
+ *
+ *     reader.beginArray();
+ *     while (reader.hasNext()) {
+ *       messages.add(readMessage(reader));
+ *     }
+ *     reader.endArray();
+ *     return messages;
+ *   }
+ *
+ *   public Message readMessage(JsonReader reader) throws IOException {
+ *     long id = -1;
+ *     String text = null;
+ *     User user = null;
+ *     List<Double> geo = null;
+ *
+ *     reader.beginObject();
+ *     while (reader.hasNext()) {
+ *       String name = reader.nextName();
+ *       if (name.equals("id")) {
+ *         id = reader.nextLong();
+ *       } else if (name.equals("text")) {
+ *         text = reader.nextString();
+ *       } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
+ *         geo = readDoublesArray(reader);
+ *       } else if (name.equals("user")) {
+ *         user = readUser(reader);
+ *       } else {
+ *         reader.skipValue();
+ *       }
+ *     }
+ *     reader.endObject();
+ *     return new Message(id, text, user, geo);
+ *   }
+ *
+ *   public List<Double> readDoublesArray(JsonReader reader) throws IOException {
+ *     List<Double> doubles = new ArrayList<Double>();
+ *
+ *     reader.beginArray();
+ *     while (reader.hasNext()) {
+ *       doubles.add(reader.nextDouble());
+ *     }
+ *     reader.endArray();
+ *     return doubles;
+ *   }
+ *
+ *   public User readUser(JsonReader reader) throws IOException {
+ *     String username = null;
+ *     int followersCount = -1;
+ *
+ *     reader.beginObject();
+ *     while (reader.hasNext()) {
+ *       String name = reader.nextName();
+ *       if (name.equals("name")) {
+ *         username = reader.nextString();
+ *       } else if (name.equals("followers_count")) {
+ *         followersCount = reader.nextInt();
+ *       } else {
+ *         reader.skipValue();
+ *       }
+ *     }
+ *     reader.endObject();
+ *     return new User(username, followersCount);
+ *   }}</pre>
+ *
+ * <h3>Number Handling</h3>
+ * This reader permits numeric values to be read as strings and string values to
+ * be read as numbers. For example, both elements of the JSON array {@code
+ * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}.
+ * This behavior is intended to prevent lossy numeric conversions: double is
+ * JavaScript's only numeric type and very large values like {@code
+ * 9007199254740993} cannot be represented exactly on that platform. To minimize
+ * precision loss, extremely large values should be written and read as strings
+ * in JSON.
+ *
+ * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
+ * of this class are not thread safe.
+ */
+public final class JsonReader implements Closeable {
+
+    /** The input JSON. */
+    private final Reader in;
+
+    /** True to accept non-spec compliant JSON */
+    private boolean lenient = false;
+
+    /**
+     * Use a manual buffer to easily read and unread upcoming characters, and
+     * also so we can create strings without an intermediate StringBuilder.
+     */
+    private final char[] buffer = new char[1024];
+    private int pos = 0;
+    private int limit = 0;
+
+    private final List<JsonScope> stack = new ArrayList<JsonScope>();
+    {
+        push(JsonScope.EMPTY_DOCUMENT);
+    }
+
+    /**
+     * True if we've already read the next token. If we have, the string value
+     * for that token will be assigned to {@code value} if such a string value
+     * exists. And the token type will be assigned to {@code token} if the token
+     * type is known. The token type may be null for literals, since we derive
+     * that lazily.
+     */
+    private boolean hasToken;
+
+    /**
+     * The type of the next token to be returned by {@link #peek} and {@link
+     * #advance}, or {@code null} if it is unknown and must first be derived
+     * from {@code value}. This value is undefined if {@code hasToken} is false.
+     */
+    private JsonToken token;
+
+    /** The text of the next name. */
+    private String name;
+
+    /** The text of the next literal value. */
+    private String value;
+
+    /** True if we're currently handling a skipValue() call. */
+    private boolean skipping = false;
+
+    /**
+     * Creates a new instance that reads a JSON-encoded stream from {@code in}.
+     */
+    public JsonReader(Reader in) {
+        if (in == null) {
+            throw new NullPointerException("in == null");
+        }
+        this.in = in;
+    }
+
+    /**
+     * Configure this parser to be  be liberal in what it accepts. By default,
+     * this parser is strict and only accepts JSON as specified by <a
+     * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
+     * parser to lenient causes it to ignore the following syntax errors:
+     *
+     * <ul>
+     *   <li>End of line comments starting with {@code //} or {@code #} and
+     *       ending with a newline character.
+     *   <li>C-style comments starting with {@code /*} and ending with
+     *       {@code *}{@code /}. Such comments may not be nested.
+     *   <li>Names that are unquoted or {@code 'single quoted'}.
+     *   <li>Strings that are unquoted or {@code 'single quoted'}.
+     *   <li>Array elements separated by {@code ;} instead of {@code ,}.
+     *   <li>Unnecessary array separators. These are interpreted as if null
+     *       was the omitted value.
+     *   <li>Names and values separated by {@code =} or {@code =>} instead of
+     *       {@code :}.
+     *   <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
+     * </ul>
+     */
+    public void setLenient(boolean lenient) {
+        this.lenient = lenient;
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * beginning of a new array.
+     */
+    public void beginArray() throws IOException {
+        expect(JsonToken.BEGIN_ARRAY);
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * end of the current array.
+     */
+    public void endArray() throws IOException {
+        expect(JsonToken.END_ARRAY);
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * beginning of a new object.
+     */
+    public void beginObject() throws IOException {
+        expect(JsonToken.BEGIN_OBJECT);
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * end of the current array.
+     */
+    public void endObject() throws IOException {
+        expect(JsonToken.END_OBJECT);
+    }
+
+    /**
+     * Consumes {@code expected}.
+     */
+    private void expect(JsonToken expected) throws IOException {
+        quickPeek();
+        if (token != expected) {
+            throw new IllegalStateException("Expected " + expected + " but was " + peek());
+        }
+        advance();
+    }
+
+    /**
+     * Returns true if the current array or object has another element.
+     */
+    public boolean hasNext() throws IOException {
+        quickPeek();
+        return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
+    }
+
+    /**
+     * Returns the type of the next token without consuming it.
+     */
+    public JsonToken peek() throws IOException {
+        quickPeek();
+
+        if (token == null) {
+            decodeLiteral();
+        }
+
+        return token;
+    }
+
+    /**
+     * Ensures that a token is ready. After this call either {@code token} or
+     * {@code value} will be non-null. To ensure {@code token} has a definitive
+     * value, use {@link #peek()}
+     */
+    private JsonToken quickPeek() throws IOException {
+        if (hasToken) {
+            return token;
+        }
+
+        switch (peekStack()) {
+            case EMPTY_DOCUMENT:
+                replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+                JsonToken firstToken = nextValue();
+                if (token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
+                    throw new IOException(
+                            "Expected JSON document to start with '[' or '{' but was " + token);
+                }
+                return firstToken;
+            case EMPTY_ARRAY:
+                return nextInArray(true);
+            case NONEMPTY_ARRAY:
+                return nextInArray(false);
+            case EMPTY_OBJECT:
+                return nextInObject(true);
+            case DANGLING_NAME:
+                return objectValue();
+            case NONEMPTY_OBJECT:
+                return nextInObject(false);
+            case NONEMPTY_DOCUMENT:
+                hasToken = true;
+                return token = JsonToken.END_DOCUMENT;
+            case CLOSED:
+                throw new IllegalStateException("JsonReader is closed");
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    /**
+     * Advances the cursor in the JSON stream to the next token.
+     */
+    private JsonToken advance() throws IOException {
+        quickPeek();
+
+        JsonToken result = token;
+        hasToken = false;
+        token = null;
+        value = null;
+        name = null;
+        return result;
+    }
+
+    /**
+     * Returns the next token, a {@link JsonToken#NAME property name}, and
+     * consumes it.
+     *
+     * @throws IOException if the next token in the stream is not a property
+     *     name.
+     */
+    public String nextName() throws IOException {
+        quickPeek();
+        if (token != JsonToken.NAME) {
+            throw new IllegalStateException("Expected a name but was " + peek());
+        }
+        String result = name;
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#STRING string} value of the next token,
+     * consuming it. If the next token is a number, this method will return its
+     * string form.
+     *
+     * @throws IllegalStateException if the next token is not a string or if
+     *     this reader is closed.
+     */
+    public String nextString() throws IOException {
+        peek();
+        if (value == null || (token != JsonToken.STRING && token != JsonToken.NUMBER)) {
+            throw new IllegalStateException("Expected a string but was " + peek());
+        }
+
+        String result = value;
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token,
+     * consuming it.
+     *
+     * @throws IllegalStateException if the next token is not a boolean or if
+     *     this reader is closed.
+     */
+    public boolean nextBoolean() throws IOException {
+        quickPeek();
+        if (value == null || token == JsonToken.STRING) {
+            throw new IllegalStateException("Expected a boolean but was " + peek());
+        }
+
+        boolean result;
+        if (value.equalsIgnoreCase("true")) {
+            result = true;
+        } else if (value.equalsIgnoreCase("false")) {
+            result = false;
+        } else {
+            throw new IllegalStateException("Not a boolean: " + value);
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is a
+     * literal null.
+     *
+     * @throws IllegalStateException if the next token is not null or if this
+     *     reader is closed.
+     */
+    public void nextNull() throws IOException {
+        quickPeek();
+        if (value == null || token == JsonToken.STRING) {
+            throw new IllegalStateException("Expected null but was " + peek());
+        }
+
+        if (!value.equalsIgnoreCase("null")) {
+            throw new IllegalStateException("Not a null: " + value);
+        }
+
+        advance();
+    }
+
+    /**
+     * Returns the {@link JsonToken#NUMBER double} value of the next token,
+     * consuming it. If the next token is a string, this method will attempt to
+     * parse it as a double.
+     *
+     * @throws IllegalStateException if the next token is not a literal value.
+     * @throws NumberFormatException if the next literal value cannot be parsed
+     *     as a double, or is non-finite.
+     */
+    public double nextDouble() throws IOException {
+        quickPeek();
+        if (value == null) {
+            throw new IllegalStateException("Expected a double but was " + peek());
+        }
+
+        double result = Double.parseDouble(value);
+
+        if ((result >= 1.0d && value.startsWith("0"))
+                || Double.isNaN(result)
+                || Double.isInfinite(result)) {
+            throw new NumberFormatException(
+                    "JSON forbids octal prefixes, NaN and infinities: " + value);
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#NUMBER long} value of the next token,
+     * consuming it. If the next token is a string, this method will attempt to
+     * parse it as a long. If the next token's numeric value cannot be exactly
+     * represented by a Java {@code long}, this method throws.
+     *
+     * @throws IllegalStateException if the next token is not a literal value.
+     * @throws NumberFormatException if the next literal value cannot be parsed
+     *     as a number, or exactly represented as a long.
+     */
+    public long nextLong() throws IOException {
+        quickPeek();
+        if (value == null) {
+            throw new IllegalStateException("Expected a long but was " + peek());
+        }
+
+        long result;
+        try {
+            result = Long.parseLong(value);
+        } catch (NumberFormatException ignored) {
+            double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+            result = (long) asDouble;
+            if ((double) result != asDouble) {
+                throw new NumberFormatException(value);
+            }
+        }
+
+        if (result >= 1L && value.startsWith("0")) {
+            throw new NumberFormatException("JSON forbids octal prefixes: " + value);
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#NUMBER int} value of the next token,
+     * consuming it. If the next token is a string, this method will attempt to
+     * parse it as an int. If the next token's numeric value cannot be exactly
+     * represented by a Java {@code int}, this method throws.
+     *
+     * @throws IllegalStateException if the next token is not a literal value.
+     * @throws NumberFormatException if the next literal value cannot be parsed
+     *     as a number, or exactly represented as an int.
+     */
+    public int nextInt() throws IOException {
+        quickPeek();
+        if (value == null) {
+            throw new IllegalStateException("Expected an int but was " + peek());
+        }
+
+        int result;
+        try {
+            result = Integer.parseInt(value);
+        } catch (NumberFormatException ignored) {
+            double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+            result = (int) asDouble;
+            if ((double) result != asDouble) {
+                throw new NumberFormatException(value);
+            }
+        }
+
+        if (result >= 1L && value.startsWith("0")) {
+            throw new NumberFormatException("JSON forbids octal prefixes: " + value);
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Closes this JSON reader and the underlying {@link Reader}.
+     */
+    public void close() throws IOException {
+        hasToken = false;
+        value = null;
+        token = null;
+        stack.clear();
+        stack.add(JsonScope.CLOSED);
+        in.close();
+    }
+
+    /**
+     * Skips the next value recursively. If it is an object or array, all nested
+     * elements are skipped. This method is intended for use when the JSON token
+     * stream contains unrecognized or unhandled values.
+     */
+    public void skipValue() throws IOException {
+        skipping = true;
+        try {
+            int count = 0;
+            do {
+                JsonToken token = advance();
+                if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) {
+                    count++;
+                } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) {
+                    count--;
+                }
+            } while (count != 0);
+        } finally {
+            skipping = false;
+        }
+    }
+
+    private JsonScope peekStack() {
+        return stack.get(stack.size() - 1);
+    }
+
+    private JsonScope pop() {
+        return stack.remove(stack.size() - 1);
+    }
+
+    private void push(JsonScope newTop) {
+        stack.add(newTop);
+    }
+
+    /**
+     * Replace the value on the top of the stack with the given value.
+     */
+    private void replaceTop(JsonScope newTop) {
+        stack.set(stack.size() - 1, newTop);
+    }
+
+    private JsonToken nextInArray(boolean firstElement) throws IOException {
+        if (firstElement) {
+            replaceTop(JsonScope.NONEMPTY_ARRAY);
+        } else {
+            /* Look for a comma before each element after the first element. */
+            switch (nextNonWhitespace()) {
+                case ']':
+                    pop();
+                    hasToken = true;
+                    return token = JsonToken.END_ARRAY;
+                case ';':
+                    checkLenient(); // fall-through
+                case ',':
+                    break;
+                default:
+                    throw syntaxError("Unterminated array");
+            }
+        }
+
+        switch (nextNonWhitespace()) {
+            case ']':
+                if (firstElement) {
+                    pop();
+                    hasToken = true;
+                    return token = JsonToken.END_ARRAY;
+                }
+                // fall-through to handle ",]"
+            case ';':
+            case ',':
+                /* In lenient mode, a 0-length literal means 'null' */
+                checkLenient();
+                pos--;
+                hasToken = true;
+                value = "null";
+                return token = JsonToken.NULL;
+            default:
+                pos--;
+                return nextValue();
+        }
+    }
+
+    private JsonToken nextInObject(boolean firstElement) throws IOException {
+        /*
+         * Read delimiters. Either a comma/semicolon separating this and the
+         * previous name-value pair, or a close brace to denote the end of the
+         * object.
+         */
+        if (firstElement) {
+            /* Peek to see if this is the empty object. */
+            switch (nextNonWhitespace()) {
+                case '}':
+                    pop();
+                    hasToken = true;
+                    return token = JsonToken.END_OBJECT;
+                default:
+                    pos--;
+            }
+        } else {
+            switch (nextNonWhitespace()) {
+                case '}':
+                    pop();
+                    hasToken = true;
+                    return token = JsonToken.END_OBJECT;
+                case ';':
+                case ',':
+                    break;
+                default:
+                    throw syntaxError("Unterminated object");
+            }
+        }
+
+        /* Read the name. */
+        int quote = nextNonWhitespace();
+        switch (quote) {
+            case '\'':
+                checkLenient(); // fall-through
+            case '"':
+                name = nextString((char) quote);
+                break;
+            default:
+                checkLenient();
+                pos--;
+                name = nextLiteral();
+                if (name.isEmpty()) {
+                    throw syntaxError("Expected name");
+                }
+        }
+
+        replaceTop(JsonScope.DANGLING_NAME);
+        hasToken = true;
+        return token = JsonToken.NAME;
+    }
+
+    private JsonToken objectValue() throws IOException {
+        /*
+         * Read the name/value separator. Usually a colon ':'. In lenient mode
+         * we also accept an equals sign '=', or an arrow "=>".
+         */
+        switch (nextNonWhitespace()) {
+            case ':':
+                break;
+            case '=':
+                checkLenient();
+                if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
+                    pos++;
+                }
+                break;
+            default:
+                throw syntaxError("Expected ':'");
+        }
+
+        replaceTop(JsonScope.NONEMPTY_OBJECT);
+        return nextValue();
+    }
+
+    private JsonToken nextValue() throws IOException {
+        int c = nextNonWhitespace();
+        switch (c) {
+            case '{':
+                push(JsonScope.EMPTY_OBJECT);
+                hasToken = true;
+                return token = JsonToken.BEGIN_OBJECT;
+
+            case '[':
+                push(JsonScope.EMPTY_ARRAY);
+                hasToken = true;
+                return token = JsonToken.BEGIN_ARRAY;
+
+            case '\'':
+                checkLenient(); // fall-through
+            case '"':
+                value = nextString((char) c);
+                hasToken = true;
+                return token = JsonToken.STRING;
+
+            default:
+                pos--;
+                return readLiteral();
+        }
+    }
+
+    /**
+     * Returns true once {@code limit - pos >= minimum}. If the data is
+     * exhausted before that many characters are available, this returns
+     * false.
+     */
+    private boolean fillBuffer(int minimum) throws IOException {
+        if (limit != pos) {
+            limit -= pos;
+            System.arraycopy(buffer, pos, buffer, 0, limit);
+        } else {
+            limit = 0;
+        }
+
+        pos = 0;
+        int total;
+        while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
+            limit += total;
+            if (limit >= minimum) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int nextNonWhitespace() throws IOException {
+        while (pos < limit || fillBuffer(1)) {
+            int c = buffer[pos++];
+            switch (c) {
+                case '\t':
+                case ' ':
+                case '\n':
+                case '\r':
+                    continue;
+
+                case '/':
+                    if (pos == limit && !fillBuffer(1)) {
+                        return c;
+                    }
+
+                    checkLenient();
+                    char peek = buffer[pos];
+                    switch (peek) {
+                        case '*':
+                            // skip a /* c-style comment */
+                            pos++;
+                            if (!skipTo("*/")) {
+                                throw syntaxError("Unterminated comment");
+                            }
+                            pos += 2;
+                            continue;
+
+                        case '/':
+                            // skip a // end-of-line comment
+                            pos++;
+                            skipToEndOfLine();
+                            continue;
+
+                        default:
+                            return c;
+                    }
+
+                case '#':
+                    /*
+                     * Skip a # hash end-of-line comment. The JSON RFC doesn't
+                     * specify this behaviour, but it's required to parse
+                     * existing documents. See http://b/2571423.
+                     */
+                    checkLenient();
+                    skipToEndOfLine();
+                    continue;
+
+                default:
+                    return c;
+            }
+        }
+
+        throw syntaxError("End of input");
+    }
+
+    private void checkLenient() throws IOException {
+        if (!lenient) {
+            throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
+        }
+    }
+
+    /**
+     * Advances the position until after the next newline character. If the line
+     * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
+     * caller.
+     */
+    private void skipToEndOfLine() throws IOException {
+        while (pos < limit || fillBuffer(1)) {
+            char c = buffer[pos++];
+            if (c == '\r' || c == '\n') {
+                break;
+            }
+        }
+    }
+
+    private boolean skipTo(String toFind) throws IOException {
+        outer:
+        for (; pos + toFind.length() < limit || fillBuffer(toFind.length()); pos++) {
+            for (int c = 0; c < toFind.length(); c++) {
+                if (buffer[pos + c] != toFind.charAt(c)) {
+                    continue outer;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the string up to but not including {@code quote}, unescaping any
+     * character escape sequences encountered along the way. The opening quote
+     * should have already been read. This consumes the closing quote, but does
+     * not include it in the returned string.
+     *
+     * @param quote either ' or ".
+     * @throws NumberFormatException if any unicode escape sequences are
+     *     malformed.
+     */
+    private String nextString(char quote) throws IOException {
+        StringBuilder builder = null;
+        do {
+            /* the index of the first character not yet appended to the builder. */
+            int start = pos;
+            while (pos < limit) {
+                int c = buffer[pos++];
+
+                if (c == quote) {
+                    if (skipping) {
+                        return "skipped!";
+                    } else if (builder == null) {
+                        return new String(buffer, start, pos - start - 1);
+                    } else {
+                        builder.append(buffer, start, pos - start - 1);
+                        return builder.toString();
+                    }
+
+                } else if (c == '\\') {
+                    if (builder == null) {
+                        builder = new StringBuilder();
+                    }
+                    builder.append(buffer, start, pos - start - 1);
+                    builder.append(readEscapeCharacter());
+                    start = pos;
+                }
+            }
+
+            if (builder == null) {
+                builder = new StringBuilder();
+            }
+            builder.append(buffer, start, pos - start);
+        } while (fillBuffer(1));
+
+        throw syntaxError("Unterminated string");
+    }
+
+    /**
+     * Returns the string up to but not including any delimiter characters. This
+     * does not consume the delimiter character.
+     */
+    private String nextLiteral() throws IOException {
+        StringBuilder builder = null;
+        do {
+            /* the index of the first character not yet appended to the builder. */
+            int start = pos;
+            while (pos < limit) {
+                int c = buffer[pos++];
+                switch (c) {
+                    case '/':
+                    case '\\':
+                    case ';':
+                    case '#':
+                    case '=':
+                        checkLenient(); // fall-through
+
+                    case '{':
+                    case '}':
+                    case '[':
+                    case ']':
+                    case ':':
+                    case ',':
+                    case ' ':
+                    case '\t':
+                    case '\f':
+                    case '\r':
+                    case '\n':
+                        pos--;
+                        if (skipping) {
+                            return "skipped!";
+                        } else if (builder == null) {
+                            return new String(buffer, start, pos - start);
+                        } else {
+                            builder.append(buffer, start, pos - start);
+                            return builder.toString();
+                        }
+                }
+            }
+
+            if (builder == null) {
+                builder = new StringBuilder();
+            }
+            builder.append(buffer, start, pos - start);
+        } while (fillBuffer(1));
+
+        return builder.toString();
+    }
+
+    @Override public String toString() {
+        return getClass().getSimpleName() + " near " + getSnippet();
+    }
+
+    /**
+     * Unescapes the character identified by the character or characters that
+     * immediately follow a backslash. The backslash '\' should have already
+     * been read. This supports both unicode escapes "u000A" and two-character
+     * escapes "\n".
+     *
+     * @throws NumberFormatException if any unicode escape sequences are
+     *     malformed.
+     */
+    private char readEscapeCharacter() throws IOException {
+        if (pos == limit && !fillBuffer(1)) {
+            throw syntaxError("Unterminated escape sequence");
+        }
+
+        char escaped = buffer[pos++];
+        switch (escaped) {
+            case 'u':
+                if (pos + 4 > limit && !fillBuffer(4)) {
+                    throw syntaxError("Unterminated escape sequence");
+                }
+                String hex = new String(buffer, pos, 4);
+                pos += 4;
+                return (char) Integer.parseInt(hex, 16);
+
+            case 't':
+                return '\t';
+
+            case 'b':
+                return '\b';
+
+            case 'n':
+                return '\n';
+
+            case 'r':
+                return '\r';
+
+            case 'f':
+                return '\f';
+
+            case '\'':
+            case '"':
+            case '\\':
+            default:
+                return escaped;
+        }
+    }
+
+    /**
+     * Reads a null, boolean, numeric or unquoted string literal value.
+     */
+    private JsonToken readLiteral() throws IOException {
+        String literal = nextLiteral();
+        if (literal.isEmpty()) {
+            throw syntaxError("Expected literal value");
+        }
+        value = literal;
+        hasToken = true;
+        return token = null; // use decodeLiteral() to get the token type
+    }
+
+    /**
+     * Assigns {@code nextToken} based on the value of {@code nextValue}.
+     */
+    private void decodeLiteral() throws IOException {
+        if (value.equalsIgnoreCase("null")) {
+            token = JsonToken.NULL;
+        } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+            token = JsonToken.BOOLEAN;
+        } else {
+            try {
+                Double.parseDouble(value); // this work could potentially be cached
+                token = JsonToken.NUMBER;
+            } catch (NumberFormatException ignored) {
+                // this must be an unquoted string
+                checkLenient();
+                token = JsonToken.STRING;
+            }
+        }
+    }
+
+    /**
+     * Throws a new IO exception with the given message and a context snippet
+     * with this reader's content.
+     */
+    public IOException syntaxError(String message) throws IOException {
+        throw new JsonSyntaxException(message + " near " + getSnippet());
+    }
+
+    private CharSequence getSnippet() {
+        StringBuilder snippet = new StringBuilder();
+        int beforePos = Math.min(pos, 20);
+        snippet.append(buffer, pos - beforePos, beforePos);
+        int afterPos = Math.min(limit - pos, 20);
+        snippet.append(buffer, pos, afterPos);
+        return snippet;
+    }
+
+    private static class JsonSyntaxException extends IOException {
+        private JsonSyntaxException(String s) {
+            super(s);
+        }
+    }
+}
diff --git a/core/java/android/util/JsonScope.java b/core/java/android/util/JsonScope.java
new file mode 100644
index 0000000..ca534e9
--- /dev/null
+++ b/core/java/android/util/JsonScope.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * Lexical scoping elements within a JSON reader or writer.
+ */
+enum JsonScope {
+
+    /**
+     * An array with no elements requires no separators or newlines before
+     * it is closed.
+     */
+    EMPTY_ARRAY,
+
+    /**
+     * A array with at least one value requires a comma and newline before
+     * the next element.
+     */
+    NONEMPTY_ARRAY,
+
+    /**
+     * An object with no name/value pairs requires no separators or newlines
+     * before it is closed.
+     */
+    EMPTY_OBJECT,
+
+    /**
+     * An object whose most recent element is a key. The next element must
+     * be a value.
+     */
+    DANGLING_NAME,
+
+    /**
+     * An object with at least one name/value pair requires a comma and
+     * newline before the next element.
+     */
+    NONEMPTY_OBJECT,
+
+    /**
+     * No object or array has been started.
+     */
+    EMPTY_DOCUMENT,
+
+    /**
+     * A document with at an array or object.
+     */
+    NONEMPTY_DOCUMENT,
+
+    /**
+     * A document that's been closed and cannot be accessed.
+     */
+    CLOSED,
+}
diff --git a/core/java/android/util/JsonToken.java b/core/java/android/util/JsonToken.java
new file mode 100644
index 0000000..45bc6ca
--- /dev/null
+++ b/core/java/android/util/JsonToken.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * A structure, name or value type in a JSON-encoded string.
+ */
+public enum JsonToken {
+
+    /**
+     * The opening of a JSON array. Written using {@link JsonWriter#beginObject}
+     * and read using {@link JsonReader#beginObject}.
+     */
+    BEGIN_ARRAY,
+
+    /**
+     * The closing of a JSON array. Written using {@link JsonWriter#endArray}
+     * and read using {@link JsonReader#endArray}.
+     */
+    END_ARRAY,
+
+    /**
+     * The opening of a JSON object. Written using {@link JsonWriter#beginObject}
+     * and read using {@link JsonReader#beginObject}.
+     */
+    BEGIN_OBJECT,
+
+    /**
+     * The closing of a JSON object. Written using {@link JsonWriter#endObject}
+     * and read using {@link JsonReader#endObject}.
+     */
+    END_OBJECT,
+
+    /**
+     * A JSON property name. Within objects, tokens alternate between names and
+     * their values. Written using {@link JsonWriter#name} and read using {@link
+     * JsonReader#nextName}
+     */
+    NAME,
+
+    /**
+     * A JSON string.
+     */
+    STRING,
+
+    /**
+     * A JSON number represented in this API by a Java {@code double}, {@code
+     * long}, or {@code int}.
+     */
+    NUMBER,
+
+    /**
+     * A JSON {@code true} or {@code false}.
+     */
+    BOOLEAN,
+
+    /**
+     * A JSON {@code null}.
+     */
+    NULL,
+
+    /**
+     * The end of the JSON stream. This sentinel value is returned by {@link
+     * JsonReader#peek()} to signal that the JSON-encoded value has no more
+     * tokens.
+     */
+    END_DOCUMENT
+}
diff --git a/core/java/android/util/JsonWriter.java b/core/java/android/util/JsonWriter.java
new file mode 100644
index 0000000..fecc1c8
--- /dev/null
+++ b/core/java/android/util/JsonWriter.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value to a stream, one token at a time. The stream includes both
+ * literal values (strings, numbers, booleans and nulls) as well as the begin
+ * and end delimiters of objects and arrays.
+ *
+ * <h3>Encoding JSON</h3>
+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
+ * document must contain one top-level array or object. Call methods on the
+ * writer as you walk the structure's contents, nesting arrays and objects as
+ * necessary:
+ * <ul>
+ *   <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
+ *       Write each of the array's elements with the appropriate {@link #value}
+ *       methods or by nesting other arrays and objects. Finally close the array
+ *       using {@link #endArray()}.
+ *   <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
+ *       Write each of the object's properties by alternating calls to
+ *       {@link #name} with the property's value. Write property values with the
+ *       appropriate {@link #value} method or by nesting other objects or arrays.
+ *       Finally close the object using {@link #endObject()}.
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
+ * [
+ *   {
+ *     "id": 912345678901,
+ *     "text": "How do I write JSON on Android?",
+ *     "geo": null,
+ *     "user": {
+ *       "name": "android_newb",
+ *       "followers_count": 41
+ *      }
+ *   },
+ *   {
+ *     "id": 912345678902,
+ *     "text": "@android_newb just use android.util.JsonWriter!",
+ *     "geo": [50.454722, -104.606667],
+ *     "user": {
+ *       "name": "jesse",
+ *       "followers_count": 2
+ *     }
+ *   }
+ * ]}</pre>
+ * This code encodes the above structure: <pre>   {@code
+ *   public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
+ *     JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ *     writer.setIndentSpaces(4);
+ *     writeMessagesArray(writer, messages);
+ *     writer.close();
+ *   }
+ *
+ *   public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
+ *     writer.beginArray();
+ *     for (Message message : messages) {
+ *       writeMessage(writer, message);
+ *     }
+ *     writer.endArray();
+ *   }
+ *
+ *   public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ *     writer.beginObject();
+ *     writer.name("id").value(message.getId());
+ *     writer.name("text").value(message.getText());
+ *     if (message.getGeo() != null) {
+ *       writer.name("geo");
+ *       writeDoublesArray(writer, message.getGeo());
+ *     } else {
+ *       writer.name("geo").nullValue();
+ *     }
+ *     writer.name("user");
+ *     writeUser(writer, message.getUser());
+ *     writer.endObject();
+ *   }
+ *
+ *   public void writeUser(JsonWriter writer, User user) throws IOException {
+ *     writer.beginObject();
+ *     writer.name("name").value(user.getName());
+ *     writer.name("followers_count").value(user.getFollowersCount());
+ *     writer.endObject();
+ *   }
+ *
+ *   public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
+ *     writer.beginArray();
+ *     for (Double value : doubles) {
+ *       writer.value(value);
+ *     }
+ *     writer.endArray();
+ *   }}</pre>
+ *
+ * <p>Each {@code JsonWriter} may be used to write a single JSON stream.
+ * Instances of this class are not thread safe. Calls that would result in a
+ * malformed JSON string will fail with an {@link IllegalStateException}.
+ */
+public final class JsonWriter implements Closeable {
+
+    /** The output data, containing at most one top-level array or object. */
+    private final Writer out;
+
+    private final List<JsonScope> stack = new ArrayList<JsonScope>();
+    {
+        stack.add(JsonScope.EMPTY_DOCUMENT);
+    }
+
+    /**
+     * A string containing a full set of spaces for a single level of
+     * indentation, or null for no pretty printing.
+     */
+    private String indent;
+
+    /**
+     * The name/value separator; either ":" or ": ".
+     */
+    private String separator = ":";
+
+    /**
+     * Creates a new instance that writes a JSON-encoded stream to {@code out}.
+     * For best performance, ensure {@link Writer} is buffered; wrapping in
+     * {@link java.io.BufferedWriter BufferedWriter} if necessary.
+     */
+    public JsonWriter(Writer out) {
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+        this.out = out;
+    }
+
+    /**
+     * Sets the indentation string to be repeated for each level of indentation
+     * in the encoded document. If {@code indent.isEmpty()} the encoded document
+     * will be compact. Otherwise the encoded document will be more
+     * human-readable.
+     *
+     * @param indent a string containing only whitespace.
+     */
+    public void setIndent(String indent) {
+        if (indent.isEmpty()) {
+            this.indent = null;
+            this.separator = ":";
+        } else {
+            this.indent = indent;
+            this.separator = ": ";
+        }
+    }
+
+    /**
+     * Begins encoding a new array. Each call to this method must be paired with
+     * a call to {@link #endArray}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter beginArray() throws IOException {
+        return open(JsonScope.EMPTY_ARRAY, "[");
+    }
+
+    /**
+     * Ends encoding the current array.
+     *
+     * @return this writer.
+     */
+    public JsonWriter endArray() throws IOException {
+        return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]");
+    }
+
+    /**
+     * Begins encoding a new object. Each call to this method must be paired
+     * with a call to {@link #endObject}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter beginObject() throws IOException {
+        return open(JsonScope.EMPTY_OBJECT, "{");
+    }
+
+    /**
+     * Ends encoding the current object.
+     *
+     * @return this writer.
+     */
+    public JsonWriter endObject() throws IOException {
+        return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}");
+    }
+
+    /**
+     * Enters a new scope by appending any necessary whitespace and the given
+     * bracket.
+     */
+    private JsonWriter open(JsonScope empty, String openBracket) throws IOException {
+        beforeValue(true);
+        stack.add(empty);
+        out.write(openBracket);
+        return this;
+    }
+
+    /**
+     * Closes the current scope by appending any necessary whitespace and the
+     * given bracket.
+     */
+    private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket)
+            throws IOException {
+        JsonScope context = peek();
+        if (context != nonempty && context != empty) {
+            throw new IllegalStateException("Nesting problem: " + stack);
+        }
+
+        stack.remove(stack.size() - 1);
+        if (context == nonempty) {
+            newline();
+        }
+        out.write(closeBracket);
+        return this;
+    }
+
+    /**
+     * Returns the value on the top of the stack.
+     */
+    private JsonScope peek() {
+        return stack.get(stack.size() - 1);
+    }
+
+    /**
+     * Replace the value on the top of the stack with the given value.
+     */
+    private void replaceTop(JsonScope topOfStack) {
+        stack.set(stack.size() - 1, topOfStack);
+    }
+
+    /**
+     * Encodes the property name.
+     *
+     * @param name the name of the forthcoming value. May not be null.
+     * @return this writer.
+     */
+    public JsonWriter name(String name) throws IOException {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+        beforeName();
+        string(name);
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @param value the literal string value, or null to encode a null literal.
+     * @return this writer.
+     */
+    public JsonWriter value(String value) throws IOException {
+        if (value == null) {
+            return nullValue();
+        }
+        beforeValue(false);
+        string(value);
+        return this;
+    }
+
+    /**
+     * Encodes {@code null}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter nullValue() throws IOException {
+        beforeValue(false);
+        out.write("null");
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter value(boolean value) throws IOException {
+        beforeValue(false);
+        out.write(value ? "true" : "false");
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities}.
+     * @return this writer.
+     */
+    public JsonWriter value(double value) throws IOException {
+        if (Double.isNaN(value) || Double.isInfinite(value)) {
+            throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+        }
+        beforeValue(false);
+        out.append(Double.toString(value));
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter value(long value) throws IOException {
+        beforeValue(false);
+        out.write(Long.toString(value));
+        return this;
+    }
+
+    /**
+     * Ensures all buffered data is written to the underlying {@link Writer}
+     * and flushes that writer.
+     */
+    public void flush() throws IOException {
+        out.flush();
+    }
+
+    /**
+     * Flushes and closes this writer and the underlying {@link Writer}.
+     *
+     * @throws IOException if the JSON document is incomplete.
+     */
+    public void close() throws IOException {
+        out.close();
+
+        if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
+            throw new IOException("Incomplete document");
+        }
+    }
+
+    private void string(String value) throws IOException {
+        out.write("\"");
+        for (int i = 0, length = value.length(); i < length; i++) {
+            char c = value.charAt(i);
+
+            /*
+             * From RFC 4627, "All Unicode characters may be placed within the
+             * quotation marks except for the characters that must be escaped:
+             * quotation mark, reverse solidus, and the control characters
+             * (U+0000 through U+001F)."
+             */
+            switch (c) {
+                case '"':
+                case '\\':
+                case '/':
+                    out.write('\\');
+                    out.write(c);
+                    break;
+
+                case '\t':
+                    out.write("\\t");
+                    break;
+
+                case '\b':
+                    out.write("\\b");
+                    break;
+
+                case '\n':
+                    out.write("\\n");
+                    break;
+
+                case '\r':
+                    out.write("\\r");
+                    break;
+
+                case '\f':
+                    out.write("\\f");
+                    break;
+
+                default:
+                    if (c <= 0x1F) {
+                        out.write(String.format("\\u%04x", (int) c));
+                    } else {
+                        out.write(c);
+                    }
+                    break;
+            }
+
+        }
+        out.write("\"");
+    }
+
+    private void newline() throws IOException {
+        if (indent == null) {
+            return;
+        }
+
+        out.write("\n");
+        for (int i = 1; i < stack.size(); i++) {
+            out.write(indent);
+        }
+    }
+
+    /**
+     * Inserts any necessary separators and whitespace before a name. Also
+     * adjusts the stack to expect the name's value.
+     */
+    private void beforeName() throws IOException {
+        JsonScope context = peek();
+        if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
+            out.write(',');
+        } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
+            throw new IllegalStateException("Nesting problem: " + stack);
+        }
+        newline();
+        replaceTop(JsonScope.DANGLING_NAME);
+    }
+
+    /**
+     * Inserts any necessary separators and whitespace before a literal value,
+     * inline array, or inline object. Also adjusts the stack to expect either a
+     * closing bracket or another element.
+     *
+     * @param root true if the value is a new array or object, the two values
+     *     permitted as top-level elements.
+     */
+    private void beforeValue(boolean root) throws IOException {
+        switch (peek()) {
+            case EMPTY_DOCUMENT: // first in document
+                if (!root) {
+                    throw new IllegalStateException(
+                            "JSON must start with an array or an object.");
+                }
+                replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+                break;
+
+            case EMPTY_ARRAY: // first in array
+                replaceTop(JsonScope.NONEMPTY_ARRAY);
+                newline();
+                break;
+
+            case NONEMPTY_ARRAY: // another in array
+                out.append(',');
+                newline();
+                break;
+
+            case DANGLING_NAME: // value for name
+                out.append(separator);
+                replaceTop(JsonScope.NONEMPTY_OBJECT);
+                break;
+
+            case NONEMPTY_DOCUMENT:
+                throw new IllegalStateException(
+                        "JSON must have only one top-level value.");
+
+            default:
+                throw new IllegalStateException("Nesting problem: " + stack);
+        }
+    }
+}
diff --git a/core/java/android/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/util/TimingLogger.java b/core/java/android/util/TimingLogger.java
index 0f39c97..be442da 100644
--- a/core/java/android/util/TimingLogger.java
+++ b/core/java/android/util/TimingLogger.java
@@ -24,22 +24,26 @@
  * A utility class to help log timings splits throughout a method call.
  * Typical usage is:
  *
- * TimingLogger timings = new TimingLogger(TAG, "methodA");
- * ... do some work A ...
- * timings.addSplit("work A");
- * ... do some work B ...
- * timings.addSplit("work B");
- * ... do some work C ...
- * timings.addSplit("work C");
- * timings.dumpToLog();
+ * <pre>
+ *     TimingLogger timings = new TimingLogger(TAG, "methodA");
+ *     // ... do some work A ...
+ *     timings.addSplit("work A");
+ *     // ... do some work B ...
+ *     timings.addSplit("work B");
+ *     // ... do some work C ...
+ *     timings.addSplit("work C");
+ *     timings.dumpToLog();
+ * </pre>
  *
- * The dumpToLog call would add the following to the log:
+ * <p>The dumpToLog call would add the following to the log:</p>
  *
- * D/TAG     ( 3459): methodA: begin
- * D/TAG     ( 3459): methodA:      9 ms, work A
- * D/TAG     ( 3459): methodA:      1 ms, work B
- * D/TAG     ( 3459): methodA:      6 ms, work C
- * D/TAG     ( 3459): methodA: end, 16 ms
+ * <pre>
+ *     D/TAG     ( 3459): methodA: begin
+ *     D/TAG     ( 3459): methodA:      9 ms, work A
+ *     D/TAG     ( 3459): methodA:      1 ms, work B
+ *     D/TAG     ( 3459): methodA:      6 ms, work C
+ *     D/TAG     ( 3459): methodA: end, 16 ms
+ * </pre>
  */
 public class TimingLogger {
 
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/DisplayList.java b/core/java/android/view/DisplayList.java
new file mode 100644
index 0000000..b1160f0
--- /dev/null
+++ b/core/java/android/view/DisplayList.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+/**
+ * A display lists records a series of graphics related operation and can replay
+ * them later. Display lists are usually built by recording operations on a
+ * {@link android.graphics.Canvas}. Replaying the operations from a display list
+ * avoids executing views drawing code on every frame, and is thus much more
+ * efficient. 
+ */
+abstract class DisplayList {
+    /**
+     * Starts recording the display list. All operations performed on the
+     * returned canvas are recorded and stored in this display list.
+     * 
+     * @return A canvas to record drawing operations.
+     */
+    abstract HardwareCanvas start();
+
+    /**
+     * Ends the recording for this display list. A display list cannot be
+     * replayed if recording is not finished. 
+     */
+    abstract void end();
+
+    /**
+     * Frees resources taken by this display list. This method must be called
+     * before releasing all references.
+     */
+    abstract void destroy();
+
+    /**
+     * Indicates whether this display list can be replayed or not.
+     * 
+     * @return True if the display list can be replayed, false otherwise.
+     * 
+     * @see android.view.HardwareCanvas#drawDisplayList(DisplayList) 
+     */
+    abstract boolean isReady();
+}
diff --git a/core/java/android/view/DragEvent.aidl b/core/java/android/view/DragEvent.aidl
new file mode 100644
index 0000000..f08943f
--- /dev/null
+++ b/core/java/android/view/DragEvent.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+package android.view;
+
+parcelable DragEvent;
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
new file mode 100644
index 0000000..93598cd
--- /dev/null
+++ b/core/java/android/view/DragEvent.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** !!! TODO: real docs */
+public class DragEvent implements Parcelable {
+    private static final boolean TRACK_RECYCLED_LOCATION = false;
+
+    int mAction;
+    float mX, mY;
+    ClipDescription mClipDescription;
+    ClipData mClipData;
+
+    private DragEvent mNext;
+    private RuntimeException mRecycledLocation;
+    private boolean mRecycled;
+
+    private static final int MAX_RECYCLED = 10;
+    private static final Object gRecyclerLock = new Object();
+    private static int gRecyclerUsed = 0;
+    private static DragEvent gRecyclerTop = null;
+
+    /**
+     * action constants for DragEvent dispatch
+     */
+    public static final int ACTION_DRAG_STARTED = 1;
+    public static final int ACTION_DRAG_LOCATION = 2;
+    public static final int ACTION_DROP = 3;
+    public static final int ACTION_DRAG_ENDED = 4;
+    public static final int ACTION_DRAG_ENTERED = 5;
+    public static final int ACTION_DRAG_EXITED = 6;
+
+    /* hide the constructor behind package scope */
+    private DragEvent() {
+    }
+
+    static DragEvent obtain() {
+        return DragEvent.obtain(0, 0f, 0f, null, null);
+    }
+
+    public static DragEvent obtain(int action, float x, float y,
+            ClipDescription description, ClipData data) {
+        final DragEvent ev;
+        synchronized (gRecyclerLock) {
+            if (gRecyclerTop == null) {
+                return new DragEvent();
+            }
+            ev = gRecyclerTop;
+            gRecyclerTop = ev.mNext;
+            gRecyclerUsed -= 1;
+        }
+        ev.mRecycledLocation = null;
+        ev.mRecycled = false;
+        ev.mNext = null;
+
+        ev.mAction = action;
+        ev.mX = x;
+        ev.mY = y;
+        ev.mClipDescription = description;
+        ev.mClipData = data;
+
+        return ev;
+    }
+
+    public static DragEvent obtain(DragEvent source) {
+        return obtain(source.mAction, source.mX, source.mY,
+                source.mClipDescription, source.mClipData);
+    }
+
+    public int getAction() {
+        return mAction;
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public ClipData getClipData() {
+        return mClipData;
+    }
+
+    public ClipDescription getClipDescription() {
+        return mClipDescription;
+    }
+
+    /**
+     * Recycle the DragEvent, to be re-used by a later caller.  After calling
+     * this function you must never touch the event again.
+     */
+    public final void recycle() {
+        // Ensure recycle is only called once!
+        if (TRACK_RECYCLED_LOCATION) {
+            if (mRecycledLocation != null) {
+                throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+            }
+            mRecycledLocation = new RuntimeException("Last recycled here");
+        } else {
+            if (mRecycled) {
+                throw new RuntimeException(toString() + " recycled twice!");
+            }
+            mRecycled = true;
+        }
+
+        mClipData = null;
+        mClipDescription = null;
+
+        synchronized (gRecyclerLock) {
+            if (gRecyclerUsed < MAX_RECYCLED) {
+                gRecyclerUsed++;
+                mNext = gRecyclerTop;
+                gRecyclerTop = this;
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "DragEvent{" + Integer.toHexString(System.identityHashCode(this))
+        + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription
+        + " data=" + mClipData
+        + "}";
+    }
+
+    /* Parcelable interface */
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mAction);
+        dest.writeFloat(mX);
+        dest.writeFloat(mY);
+        if (mClipData == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mClipData.writeToParcel(dest, flags);
+        }
+        if (mClipDescription == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mClipDescription.writeToParcel(dest, flags);
+        }
+    }
+
+    public static final Parcelable.Creator<DragEvent> CREATOR =
+        new Parcelable.Creator<DragEvent>() {
+        public DragEvent createFromParcel(Parcel in) {
+            DragEvent event = DragEvent.obtain();
+            event.mAction = in.readInt();
+            event.mX = in.readFloat();
+            event.mY = in.readFloat();
+            if (in.readInt() != 0) {
+                event.mClipData = ClipData.CREATOR.createFromParcel(in);
+            }
+            if (in.readInt() != 0) {
+                event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in);
+            }
+            return event;
+        }
+
+        public DragEvent[] newArray(int size) {
+            return new DragEvent[size];
+        }
+    };
+}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
new file mode 100644
index 0000000..f0b00dd
--- /dev/null
+++ b/core/java/android/view/GLES20Canvas.java
@@ -0,0 +1,852 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+/**
+ * An implementation of Canvas on top of OpenGL ES 2.0.
+ */
+class GLES20Canvas extends HardwareCanvas {
+    private final boolean mOpaque;
+    private 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;
+
+    private boolean mContextLocked;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // JNI
+    ///////////////////////////////////////////////////////////////////////////
+
+    private static native boolean nIsAvailable();
+    private static boolean sIsAvailable = nIsAvailable();
+
+    static boolean isAvailable() {
+        return sIsAvailable;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors
+    ///////////////////////////////////////////////////////////////////////////
+
+    GLES20Canvas(boolean translucent) {
+        this(false, translucent);
+    }
+    
+    GLES20Canvas(boolean record, boolean translucent) {
+        mOpaque = !translucent;
+
+        if (record) {
+            mRenderer = nCreateDisplayListRenderer();
+        } else {
+            mRenderer = nCreateRenderer();
+        }
+       
+        if (mRenderer == 0) {
+            throw new IllegalStateException("Could not create GLES20Canvas renderer");
+        }
+    }
+
+    private native int nCreateRenderer();    
+    private native int nCreateDisplayListRenderer();    
+
+    @Override
+    public synchronized void destroy() {
+        if (mRenderer != 0) {
+            nDestroyRenderer(mRenderer);
+            mRenderer = 0;
+        }
+    }
+
+    private native void nDestroyRenderer(int renderer);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Canvas management
+    ///////////////////////////////////////////////////////////////////////////
+
+    @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);
+
+    @Override
+    void onPreDraw() {
+        nPrepare(mRenderer);
+    }
+
+    private native void nPrepare(int renderer);
+
+    @Override
+    void onPostDraw() {
+        nFinish(mRenderer);
+    }
+    
+    private native void nFinish(int renderer);
+
+    @Override
+    public boolean acquireContext() {
+        if (!mContextLocked) {
+            nAcquireContext(mRenderer);
+            mContextLocked = true;
+        }
+        return mContextLocked;
+    }
+
+    private native void nAcquireContext(int renderer);
+
+    @Override
+    public void releaseContext() {
+        if (mContextLocked) {
+            nReleaseContext(mRenderer);
+            mContextLocked = false;
+        }
+    }
+
+    private native void nReleaseContext(int renderer);
+    
+    ///////////////////////////////////////////////////////////////////////////
+    // Display list
+    ///////////////////////////////////////////////////////////////////////////
+
+    int getDisplayList() {
+        return nCreateDisplayList(mRenderer);
+    }
+
+    private native int nCreateDisplayList(int renderer);
+    
+    void destroyDisplayList(int displayList) {
+        nDestroyDisplayList(displayList);
+    }
+
+    private native void nDestroyDisplayList(int displayList);
+
+    @Override
+    public void drawDisplayList(DisplayList displayList) {
+        nDrawDisplayList(mRenderer, ((GLES20DisplayList) displayList).mNativeDisplayList);
+    }
+
+    private native void nDrawDisplayList(int renderer, int displayList);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // 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, Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
+    }
+    
+    @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) {
+        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, 4, paint);
+    }
+
+    @Override
+    public void drawLines(float[] pts, int offset, int count, Paint paint) {
+        if ((offset | count) < 0 || offset + count > pts.length) {
+            throw new IllegalArgumentException("The lines array must contain 4 elements per line.");
+        }
+        boolean hasModifier = setupModifiers(paint);
+        nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
+        if (hasModifier) nResetModifiers(mRenderer);
+    }
+
+    private native void nDrawLines(int renderer, float[] points, int offset, int count, int paint);
+
+    @Override
+    public void drawLines(float[] pts, Paint paint) {
+        drawLines(pts, 0, pts.length, 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) {
+        boolean hasModifier = setupModifiers(paint);
+        if (path.isSimplePath) {
+            if (path.rects != null) {
+                nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
+            }
+        } else {
+            nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
+        }
+        if (hasModifier) nResetModifiers(mRenderer);
+    }
+
+    private native void nDrawPath(int renderer, int path, int paint);
+    private native void nDrawRects(int renderer, int region, int paint);
+
+    @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);
+        try {
+            nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
+        } finally {
+            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);
+        try {
+            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);
+            }
+        } finally {
+            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);
+        try {
+            nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
+        } finally {
+            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);
+        try {
+            nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags,
+                    paint.mNativePaint);
+        } finally {
+            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) {
+        if ((index | count | text.length - index - count) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
+            throw new IllegalArgumentException("Unknown direction: " + dir);
+        }
+
+        boolean hasModifier = setupModifiers(paint);
+        try {
+            nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
+                    paint.mNativePaint);
+        } finally {
+            if (hasModifier) nResetModifiers(mRenderer);
+        }
+    }
+
+    private native void nDrawTextRun(int renderer, char[] text, int index, int count,
+            int contextIndex, int contextCount, float x, float y, int dir, int nativePaint);
+
+    @Override
+    public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
+            float x, float y, int dir, Paint paint) {
+        if ((start | end | end - start | text.length() - end) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        boolean hasModifier = setupModifiers(paint);
+        try {
+            int flags = dir == 0 ? 0 : 1;
+            if (text instanceof String || text instanceof SpannedString ||
+                    text instanceof SpannableString) {
+                nDrawTextRun(mRenderer, 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);
+                nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
+                        x, y, flags, paint.mNativePaint);
+                TemporaryBuffer.recycle(buf);
+            }
+        } finally {
+            if (hasModifier) nResetModifiers(mRenderer);
+        }
+    }
+
+    private native void nDrawTextRun(int renderer, String text, int start, int end,
+            int contextStart, int contextEnd, float x, float y, int flags, int nativePaint);
+
+    @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;
+
+        if (paint.hasShadow) {
+            nSetupShadow(mRenderer, paint.shadowRadius, paint.shadowDx, paint.shadowDy,
+                    paint.shadowColor);
+            hasModifier = true;
+        }
+
+        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 nSetupShadow(int renderer, float radius, float dx, float dy, int color);
+
+    private native void nResetModifiers(int renderer);
+}
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
new file mode 100644
index 0000000..2886bf3
--- /dev/null
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+/**
+ * An implementation of display list for OpenGL ES 2.0.
+ */
+class GLES20DisplayList extends DisplayList {
+    private GLES20Canvas mCanvas;
+
+    private boolean mStarted = false;
+    private boolean mRecorded = false;
+
+    int mNativeDisplayList;
+
+    @Override
+    HardwareCanvas start() {
+        if (mStarted) {
+            throw new IllegalStateException("Recording has already started");
+        }
+
+        destroyCanvas();
+
+        mCanvas = new GLES20Canvas(true, true);
+        mStarted = true;
+        mRecorded = false;
+
+        return mCanvas;
+    }
+
+    private void destroyCanvas() {
+        if (mCanvas != null) {
+            mCanvas.destroyDisplayList(mNativeDisplayList);
+            mCanvas.destroy();
+
+            mCanvas = null;
+            mNativeDisplayList = 0;
+        }
+    }
+
+    @Override
+    void end() {
+        if (mCanvas != null) {
+            mStarted = false;
+            mRecorded = true;
+
+            mNativeDisplayList = mCanvas.getDisplayList();
+        }
+    }
+
+    @Override
+    void destroy() {
+        destroyCanvas();
+    }
+
+    @Override
+    boolean isReady() {
+        return !mStarted && mRecorded;
+    }
+}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
new file mode 100644
index 0000000..22d2fe6
--- /dev/null
+++ b/core/java/android/view/HardwareCanvas.java
@@ -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.
+ */
+
+package android.view;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+
+/**
+ * Hardware accelerated canvas. 
+ */
+abstract class HardwareCanvas extends Canvas {
+    @Override
+    public boolean isHardwareAccelerated() {
+        return true;
+    }
+
+    @Override
+    public void setBitmap(Bitmap bitmap) {
+        throw new UnsupportedOperationException();
+    }
+    
+    /**
+     * This method <strong>must</strong> be called before releasing a
+     * reference to a hardware canvas. This method is responsible for
+     * freeing native resources associated with the hardware. Not
+     * invoking this method properly can result in memory leaks.
+     */    
+    public abstract void destroy();
+
+    /**
+     * Invoked before any drawing operation is performed in this canvas.
+     */
+    abstract void onPreDraw();
+
+    /**
+     * Invoked after all drawing operation have been performed.
+     */
+    abstract void onPostDraw();
+    
+    /**
+     * Draws the specified display list onto this canvas.
+     * 
+     * @param displayList The display list to replay.
+     */
+    public abstract void drawDisplayList(DisplayList displayList);
+}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
new file mode 100644
index 0000000..2cc4052
--- /dev/null
+++ b/core/java/android/view/HardwareRenderer.java
@@ -0,0 +1,613 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.EventLog;
+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;
+
+/**
+ * Interface for rendering a ViewRoot using hardware acceleration.
+ * 
+ * @hide
+ */
+public abstract class HardwareRenderer {
+    private static final String LOG_TAG = "HardwareRenderer";
+
+    /**
+     * A process can set this flag to false to prevent the use of hardware
+     * rendering.
+     * 
+     * @hide
+     */
+    public static boolean sRendererDisabled = false;
+
+    private boolean mEnabled;
+    private boolean mRequested = true;
+
+    /**
+     * Indicates that the current process cannot use hardware rendering.
+     * 
+     * @hide
+     */
+    public static void disable() {
+        sRendererDisabled = true;
+    }
+
+    /**
+     * Indicates whether hardware acceleration is available under any form for
+     * the view hierarchy.
+     * 
+     * @return True if the view hierarchy can potentially be hardware accelerated,
+     *         false otherwise
+     */
+    public static boolean isAvailable() {
+        return GLES20Canvas.isAvailable();
+    }
+
+    /**
+     * Destroys the hardware rendering context.
+     * 
+     * @param full If true, destroys all associated resources.
+     */
+    abstract void destroy(boolean full);
+
+    /**
+     * 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.
+     */
+    abstract void setup(int width, int height);
+
+    /**
+     * 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);
+
+    /**
+     * Creates a new canvas that can be used to record drawing operations
+     * in the specified display list.
+     * 
+     * @return A new recording canvas.
+     */
+    abstract DisplayList createDisplayList();
+
+    /**
+     * 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);
+                }
+            }
+        }        
+    }
+
+    /**
+     * 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 2:
+                return Gl20Renderer.create(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;
+
+        static EGLContext sEglContext;
+        static EGL10 sEgl;
+        static EGLDisplay sEglDisplay;
+        static EGLConfig sEglConfig;
+
+        private static Thread sEglThread;        
+
+        EGLSurface mEglSurface;
+        
+        GL mGl;
+        GLES20Canvas mCanvas;
+
+        final int mGlVersion;
+        final boolean mTranslucent;
+
+        private boolean mDestroyed;
+
+        GlRenderer(int glVersion, boolean translucent) {
+            mGlVersion = glVersion;
+            mTranslucent = translucent;
+        }
+
+        /**
+         * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)}
+         * is invoked and the requested flag is turned off. The error code is
+         * also logged as a warning.
+         */
+        void checkEglErrors() {
+            if (isEnabled()) {
+                int error = sEgl.eglGetError();
+                if (error != EGL10.EGL_SUCCESS) {
+                    // something bad has happened revert to
+                    // normal rendering.
+                    destroy(true);
+                    if (error != EGL11.EGL_CONTEXT_LOST) {
+                        // we'll try again if it was context lost
+                        setRequested(false);
+                    }
+                    Log.w(LOG_TAG, "EGL error: " + Integer.toHexString(error));
+                }
+            }
+        }
+        
+        @Override
+        boolean initialize(SurfaceHolder holder) {
+            if (isRequested() && !isEnabled()) {
+                initializeEgl();
+                mGl = createEglSurface(holder);
+                mDestroyed = false;
+
+                if (mGl != null) {
+                    int err = sEgl.eglGetError();
+                    if (err != EGL10.EGL_SUCCESS) {
+                        destroy(true);
+                        setRequested(false);
+                    } else {
+                        if (mCanvas == null) {
+                            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 GLES20Canvas createCanvas();
+
+        void initializeEgl() {
+            if (sEglContext != null) return;
+
+            sEglThread = Thread.currentThread();
+            sEgl = (EGL10) EGLContext.getEGL();
+            
+            // Get to the default display.
+            sEglDisplay = sEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+            
+            if (sEglDisplay == EGL10.EGL_NO_DISPLAY) {
+                throw new RuntimeException("eglGetDisplay failed");
+            }
+            
+            // We can now initialize EGL for that display
+            int[] version = new int[2];
+            if (!sEgl.eglInitialize(sEglDisplay, version)) {
+                throw new RuntimeException("eglInitialize failed");
+            }
+            sEglConfig = getConfigChooser(mGlVersion).chooseConfig(sEgl, sEglDisplay);
+            
+            /*
+            * Create an EGL context. We want to do this as rarely as we can, because an
+            * EGL context is a somewhat heavy object.
+            */
+            sEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
+        }
+
+        GL createEglSurface(SurfaceHolder holder) {
+            // Check preconditions.
+            if (sEgl == null) {
+                throw new RuntimeException("egl not initialized");
+            }
+            if (sEglDisplay == null) {
+                throw new RuntimeException("eglDisplay not initialized");
+            }
+            if (sEglConfig == null) {
+                throw new RuntimeException("mEglConfig not initialized");
+            }
+            if (Thread.currentThread() != sEglThread) {
+                throw new IllegalStateException("HardwareRenderer cannot be used " 
+                        + "from multiple threads");
+            }
+
+            /*
+             *  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.
+                 */
+                sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
+                        EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
+                sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
+            }
+
+            // Create an EGL surface we can render into.
+            mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
+
+            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+                int error = sEgl.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 (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
+                throw new RuntimeException("eglMakeCurrent failed");
+            }
+
+            return sEglContext.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()) {
+                checkEglErrors();
+                super.initializeIfNeeded(width, height, attachInfo, holder);
+            }
+        }
+        
+        @Override
+        void destroy(boolean full) {
+            if (full && mCanvas != null) {
+                mCanvas.destroy();
+                mCanvas = null;
+            }
+            
+            if (!isEnabled() || mDestroyed) return;
+
+            mDestroyed = true;
+
+            sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE,
+                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
+            sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
+
+            mEglSurface = null;
+            mGl = null;
+
+            setEnabled(false);
+        }
+
+        @Override
+        void setup(int width, int height) {
+            mCanvas.setViewport(width, height);
+        }
+        
+        boolean canDraw() {
+            return mGl != null && mCanvas != null;
+        }        
+        
+        void onPreDraw() {
+        }
+
+        void onPostDraw() {
+        }
+        
+        /**
+         * 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, 8, 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;
+                
+                long startTime;
+                if (ViewDebug.DEBUG_PROFILE_DRAWING) {
+                    startTime = SystemClock.elapsedRealtime();
+                }
+
+                checkCurrent();
+
+                onPreDraw();
+
+                Canvas canvas = mCanvas;
+                int saveCount = canvas.save();
+                canvas.translate(0, -yOffset);
+
+                try {
+                    view.draw(canvas);
+                } finally {
+                    canvas.restoreToCount(saveCount);
+                }
+
+                onPostDraw();
+
+                if (ViewDebug.DEBUG_PROFILE_DRAWING) {
+                    EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
+                }
+
+                attachInfo.mIgnoreDirtyState = false;
+
+                sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
+                checkEglErrors();
+            }
+        }
+
+        private void checkCurrent() {
+            // TODO: Don't check the current context when we have one per UI thread
+            // TODO: Use a threadlocal flag to know whether the surface has changed
+            if (sEgl.eglGetCurrentContext() != sEglContext ||
+                    sEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) {
+                if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
+                    throw new RuntimeException("eglMakeCurrent failed");
+                }
+            }
+        }
+
+        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
+        GLES20Canvas createCanvas() {
+            return mGlCanvas = new GLES20Canvas(true);
+        }
+
+        @Override
+        void onPreDraw() {
+            mGlCanvas.onPreDraw();
+        }
+
+        @Override
+        void onPostDraw() {
+            mGlCanvas.onPostDraw();
+        }
+
+        @Override
+        DisplayList createDisplayList() {
+            return new GLES20DisplayList();
+        }
+
+        static HardwareRenderer create(boolean translucent) {
+            if (GLES20Canvas.isAvailable()) {
+                return new Gl20Renderer(translucent);
+            }
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 921018a..85a8c1a 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -64,4 +65,9 @@
     
     void dispatchWallpaperCommand(String action, int x, int y,
             int z, in Bundle extras, boolean sync);
+
+    /**
+     * Drag/drop events
+     */
+     void dispatchDragEvent(in DragEvent event);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 7f10b76..79ea5b6 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -17,6 +17,7 @@
 
 package android.view;
 
+import android.content.ClipData;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -116,6 +117,30 @@
     boolean performHapticFeedback(IWindow window, int effectId, boolean always);
     
     /**
+     * Allocate the drag's thumbnail surface.  Also assigns a token that identifies
+     * the drag to the OS and passes that as the return value.  A return value of
+     * null indicates failure.
+     */
+    IBinder prepareDrag(IWindow window, boolean localOnly,
+            int thumbnailWidth, int thumbnailHeight, out Surface outSurface);
+
+    /**
+     * Initiate the drag operation itself
+     */
+    boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,
+            float thumbCenterX, float thumbCenterY, in ClipData data);
+
+    /**
+     * Tell the OS that we've just dragged into a View that is willing to accept the drop
+     */
+    void dragRecipientEntered(IWindow window);
+
+    /**
+     * Tell the OS that we've just dragged *off* of a View that was willing to accept the drop
+     */
+    void dragRecipientExited(IWindow window);
+
+    /**
      * For windows with the wallpaper behind them, and the wallpaper is
      * larger than the screen, set the offset within the screen.
      * For multi screen launcher type applications, xstep and ystep indicate
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 194c013..d24af52 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;
@@ -67,15 +67,16 @@
     // these are optional, set by the caller
     private boolean mFactorySet;
     private Factory mFactory;
+    private Factory2 mFactory2;
     private Filter mFilter;
 
     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 +98,7 @@
          * 
          * @return True if this class is allowed to be inflated, or false otherwise
          */
+        @SuppressWarnings("unchecked")
         boolean onLoadClass(Class clazz);
     }
     
@@ -121,12 +123,33 @@
         public View onCreateView(String name, Context context, AttributeSet attrs);
     }
 
-    private static class FactoryMerger implements Factory {
+    public interface Factory2 extends Factory {
+        /**
+         * Version of {@link #onCreateView(String, Context, AttributeSet)}
+         * that also supplies the parent that the view created view will be
+         * placed in.
+         *
+         * @param parent The parent that the created view will be placed
+         * in; <em>note that this may be null</em>.
+         * @param name Tag name to be inflated.
+         * @param context The context the view is being created in.
+         * @param attrs Inflation attributes as specified in XML file.
+         *
+         * @return View Newly created view. Return null for the default
+         *         behavior.
+         */
+        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
+    }
+
+    private static class FactoryMerger implements Factory2 {
         private final Factory mF1, mF2;
+        private final Factory2 mF12, mF22;
         
-        FactoryMerger(Factory f1, Factory f2) {
+        FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
             mF1 = f1;
             mF2 = f2;
+            mF12 = f12;
+            mF22 = f22;
         }
         
         public View onCreateView(String name, Context context, AttributeSet attrs) {
@@ -134,6 +157,14 @@
             if (v != null) return v;
             return mF2.onCreateView(name, context, attrs);
         }
+
+        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+            View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
+                    : mF1.onCreateView(name, context, attrs);
+            if (v != null) return v;
+            return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
+                    : mF2.onCreateView(name, context, attrs);
+        }
     }
     
     /**
@@ -161,6 +192,7 @@
     protected LayoutInflater(LayoutInflater original, Context newContext) {
         mContext = newContext;
         mFactory = original.mFactory;
+        mFactory2 = original.mFactory2;
         mFilter = original.mFilter;
     }
     
@@ -199,7 +231,7 @@
     }
 
     /**
-     * Return the current factory (or null). This is called on each element
+     * Return the current {@link Factory} (or null). This is called on each element
      * name. If the factory returns a View, add that to the hierarchy. If it
      * returns null, proceed to call onCreateView(name).
      */
@@ -208,6 +240,17 @@
     }
 
     /**
+     * Return the current {@link Factory2}.  Returns null if no factory is set
+     * or the set factory does not implement the {@link Factory2} interface.
+     * This is called on each element
+     * name. If the factory returns a View, add that to the hierarchy. If it
+     * returns null, proceed to call onCreateView(name).
+     */
+    public final Factory2 getFactory2() {
+        return mFactory2;
+    }
+
+    /**
      * Attach a custom Factory interface for creating views while using
      * this LayoutInflater.  This must not be null, and can only be set once;
      * after setting, you can not change the factory.  This is
@@ -233,7 +276,26 @@
         if (mFactory == null) {
             mFactory = factory;
         } else {
-            mFactory = new FactoryMerger(factory, mFactory);
+            mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
+        }
+    }
+
+    /**
+     * Like {@link #setFactory}, but allows you to set a {@link Factory2}
+     * interface.
+     */
+    public void setFactory2(Factory2 factory) {
+        if (mFactorySet) {
+            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
+        }
+        if (factory == null) {
+            throw new NullPointerException("Given factory can not be null");
+        }
+        mFactorySet = true;
+        if (mFactory == null) {
+            mFactory = mFactory2 = factory;
+        } else {
+            mFactory = new FactoryMerger(factory, factory, mFactory, mFactory2);
         }
     }
 
@@ -380,10 +442,10 @@
                                 + "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);
+                    View temp = createViewFromTag(root, name, attrs);
 
                     ViewGroup.LayoutParams params = null;
 
@@ -405,7 +467,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");
                     }
@@ -458,18 +520,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);
@@ -487,7 +549,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);
@@ -502,7 +564,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()
@@ -511,6 +573,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;
@@ -524,7 +593,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()
@@ -549,10 +618,27 @@
         return createView(name, "android.view.", attrs);
     }
 
+    /**
+     * Version of {@link #onCreateView(String, AttributeSet)} that also
+     * takes the future parent of the view being constructure.  The default
+     * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
+     *
+     * @param parent The future parent of the returned view.  <em>Note that
+     * this may be null.</em>
+     * @param name The fully qualified class name of the View to be create.
+     * @param attrs An AttributeSet of attributes to apply to the View.
+     *
+     * @return View The View created.
+     */
+    protected View onCreateView(View parent, String name, AttributeSet attrs)
+            throws ClassNotFoundException {
+        return onCreateView(name, attrs);
+    }
+
     /*
      * default visibility so the BridgeInflater can override it.
      */
-    View createViewFromTag(String name, AttributeSet attrs) {
+    View createViewFromTag(View parent, String name, AttributeSet attrs) {
         if (name.equals("view")) {
             name = attrs.getAttributeValue(null, "class");
         }
@@ -560,12 +646,14 @@
         if (DEBUG) System.out.println("******** Creating view: " + name);
 
         try {
-            View view = (mFactory == null) ? null : mFactory.onCreateView(name,
-                    mContext, attrs);
+            View view;
+            if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);
+            else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);
+            else view = null;
 
             if (view == null) {
                 if (-1 == name.indexOf('.')) {
-                    view = onCreateView(name, attrs);
+                    view = onCreateView(parent, name, attrs);
                 } else {
                     view = createView(name, null, attrs);
                 }
@@ -595,8 +683,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;
@@ -620,15 +708,15 @@
             } else if (TAG_MERGE.equals(name)) {
                 throw new InflateException("<merge /> must be the root element");
             } else {
-                final View view = createViewFromTag(name, attrs);
+                final View view = createViewFromTag(parent, 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)
@@ -679,9 +767,9 @@
 
                     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 View view = createViewFromTag(parent, childName, childAttrs);
                         final ViewGroup group = (ViewGroup) parent;
 
                         // We try to load the layout params set in the <include /> tag. If
@@ -704,7 +792,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..7d5dcd8 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -18,8 +18,6 @@
 
 import com.android.internal.view.menu.MenuItemImpl;
 
-import java.io.IOException;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -30,6 +28,10 @@
 import android.util.AttributeSet;
 import android.util.Xml;
 
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
 /**
  * This class is used to instantiate menu XML files into Menu objects.
  * <p>
@@ -51,6 +53,8 @@
 
     private static final int NO_ID = 0;
     
+    private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[]{Context.class};
+
     private Context mContext;
     
     /**
@@ -166,6 +170,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 +244,20 @@
         private boolean itemVisible;
         private boolean itemEnabled;
         
+        /**
+         * Sync to attrs.xml enum, values in MenuItem:
+         * - 0: never
+         * - 1: ifRoom
+         * - 2: always
+         * - -1: Safe sentinel for "no value".
+         */
+        private int itemShowAsAction;
+
+        private int itemActionViewLayout;
+        private String itemActionViewClassName;
+        
+        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 +329,10 @@
             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, -1);
+            itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick);
+            itemActionViewLayout = a.getResourceId(com.android.internal.R.styleable.MenuItem_actionLayout, 0);
+            itemActionViewClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionViewClass);
             
             a.recycle();
             
@@ -299,9 +356,38 @@
                 .setIcon(itemIconResId)
                 .setAlphabeticShortcut(itemAlphabeticShortcut)
                 .setNumericShortcut(itemNumericShortcut);
+            
+            if (itemShowAsAction >= 0) {
+                item.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);
+                }
+            }
+
+            if (itemActionViewClassName != null) {
+                try {
+                    final Class<?> clazz = Class.forName(itemActionViewClassName);
+                    Constructor<?> c = clazz.getConstructor(ACTION_VIEW_CONSTRUCTOR_SIGNATURE);
+                    item.setActionView((View) c.newInstance(mContext));
+                } catch (Exception e) {
+                    throw new InflateException(e);
+                }
+            } else if (itemActionViewLayout > 0) {
+                final LayoutInflater inflater = LayoutInflater.from(mContext);
+                item.setActionView(inflater.inflate(itemActionViewLayout, null));
             }
         }
         
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index fcebec5..8b9d659 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,38 @@
      *         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
+     * @see #setActionView(View)
+     */
+    public void setShowAsAction(int actionEnum);
+
+    /**
+     * Set an action view for this menu item. An action view will be displayed in place
+     * of an automatically generated menu item element in the UI when this item is shown
+     * as an action within a parent.
+     *
+     * @param view View to use for presenting this item to the user.
+     * @return This Item so additional setters can be called.
+     *
+     * @see #setShowAsAction(int)
+     */
+    public MenuItem setActionView(View view);
+
+    /**
+     * Returns the currently set action view for this menu item.
+     *
+     * @return This item's action view
+     *
+     * @see #setActionView(View)
+     * @see #setShowAsAction(int)
+     */
+    public View getActionView();
 }
\ No newline at end of file
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 6705596..dfbe65c 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.graphics.Matrix;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -347,6 +348,8 @@
     private RuntimeException mRecycledLocation;
     private boolean mRecycled;
 
+    private native void nativeTransform(Matrix matrix);
+
     private MotionEvent(int pointerCount, int sampleCount) {
         mPointerIdentifiers = new int[pointerCount];
         mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA];
@@ -1413,6 +1416,19 @@
         mYOffset = y - dataSamples[lastDataSampleIndex + SAMPLE_Y];
     }
     
+    /**
+     * Applies a transformation matrix to all of the points in the event.
+     *
+     * @param matrix The transformation matrix to apply.
+     */
+    public final void transform(Matrix matrix) {
+        if (matrix == null) {
+            throw new IllegalArgumentException("matrix must not be null");
+        }
+
+        nativeTransform(matrix);
+    }
+
     private final void getPointerCoordsAtSampleIndex(int sampleIndex,
             PointerCoords outPointerCoords) {
         final float[] dataSamples = mDataSamples;
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 0999598..17b5dd7 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -25,7 +25,7 @@
  * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
  * callback will notify users when a particular gesture event has occurred.
  * This class should only be used with {@link MotionEvent}s reported via touch.
- * 
+ *
  * To use this class:
  * <ul>
  *  <li>Create an instance of the {@code ScaleGestureDetector} for your
@@ -41,7 +41,7 @@
      * If you want to listen for all the different gestures then implement
      * this interface. If you only want to listen for a subset it might
      * be easier to extend {@link SimpleOnScaleGestureListener}.
-     * 
+     *
      * An application will receive events in the following order:
      * <ul>
      *  <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
@@ -53,7 +53,7 @@
         /**
          * Responds to scaling events for a gesture in progress.
          * Reported by pointer motion.
-         * 
+         *
          * @param detector The detector reporting the event - use this to
          *          retrieve extended info about event state.
          * @return Whether or not the detector should consider this event
@@ -68,7 +68,7 @@
         /**
          * Responds to the beginning of a scaling gesture. Reported by
          * new pointers going down.
-         * 
+         *
          * @param detector The detector reporting the event - use this to
          *          retrieve extended info about event state.
          * @return Whether or not the detector should continue recognizing
@@ -82,17 +82,17 @@
         /**
          * Responds to the end of a scale gesture. Reported by existing
          * pointers going up.
-         * 
+         *
          * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
          * and {@link ScaleGestureDetector#getFocusY()} will return the location
          * of the pointer remaining on the screen.
-         * 
+         *
          * @param detector The detector reporting the event - use this to
          *          retrieve extended info about event state.
          */
         public void onScaleEnd(ScaleGestureDetector detector);
     }
-    
+
     /**
      * A convenience class to extend when you only want to listen for a subset
      * of scaling-related events. This implements all methods in
@@ -101,7 +101,7 @@
      * {@code false} so that a subclass can retrieve the accumulated scale
      * factor in an overridden onScaleEnd.
      * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
-     * {@code true}. 
+     * {@code true}.
      */
     public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
 
@@ -148,7 +148,7 @@
     private float mCurrPressure;
     private float mPrevPressure;
     private long mTimeDelta;
-    
+
     private final float mEdgeSlop;
     private float mRightSlopEdge;
     private float mBottomSlopEdge;
@@ -217,7 +217,7 @@
                 }
             }
             break;
-            
+
             case MotionEvent.ACTION_MOVE:
                 if (mSloppyGesture) {
                     // Initiate sloppy gestures if we've moved outside of the slop area.
@@ -249,7 +249,7 @@
                     }
                 }
                 break;
-                
+
             case MotionEvent.ACTION_POINTER_UP:
                 if (mSloppyGesture) {
                     // Set focus point to the remaining finger
@@ -307,17 +307,17 @@
         }
         return handled;
     }
-    
+
     /**
-     * MotionEvent has no getRawX(int) method; simulate it pending future API approval. 
+     * MotionEvent has no getRawX(int) method; simulate it pending future API approval.
      */
     private static float getRawX(MotionEvent event, int pointerIndex) {
         float offset = event.getRawX() - event.getX();
         return event.getX(pointerIndex) + offset;
     }
-    
+
     /**
-     * MotionEvent has no getRawY(int) method; simulate it pending future API approval. 
+     * MotionEvent has no getRawY(int) method; simulate it pending future API approval.
      */
     private static float getRawY(MotionEvent event, int pointerIndex) {
         float offset = event.getRawY() - event.getY();
@@ -390,7 +390,7 @@
      * remaining pointer on the screen.
      * If {@link #isInProgress()} would return false, the result of this
      * function is undefined.
-     * 
+     *
      * @return X coordinate of the focal point in pixels.
      */
     public float getFocusX() {
@@ -405,7 +405,7 @@
      * remaining pointer on the screen.
      * If {@link #isInProgress()} would return false, the result of this
      * function is undefined.
-     * 
+     *
      * @return Y coordinate of the focal point in pixels.
      */
     public float getFocusY() {
@@ -415,7 +415,7 @@
     /**
      * Return the current distance between the two pointers forming the
      * gesture in progress.
-     * 
+     *
      * @return Distance between pointers in pixels.
      */
     public float getCurrentSpan() {
@@ -428,9 +428,29 @@
     }
 
     /**
+     * Return the current x distance between the two pointers forming the
+     * gesture in progress.
+     *
+     * @return Distance between pointers in pixels.
+     */
+    public float getCurrentSpanX() {
+        return mCurrFingerDiffX;
+    }
+
+    /**
+     * Return the current y distance between the two pointers forming the
+     * gesture in progress.
+     *
+     * @return Distance between pointers in pixels.
+     */
+    public float getCurrentSpanY() {
+        return mCurrFingerDiffY;
+    }
+
+    /**
      * Return the previous distance between the two pointers forming the
      * gesture in progress.
-     * 
+     *
      * @return Previous distance between pointers in pixels.
      */
     public float getPreviousSpan() {
@@ -443,10 +463,30 @@
     }
 
     /**
+     * Return the previous x distance between the two pointers forming the
+     * gesture in progress.
+     *
+     * @return Previous distance between pointers in pixels.
+     */
+    public float getPreviousSpanX() {
+        return mPrevFingerDiffX;
+    }
+
+    /**
+     * Return the previous y distance between the two pointers forming the
+     * gesture in progress.
+     *
+     * @return Previous distance between pointers in pixels.
+     */
+    public float getPreviousSpanY() {
+        return mPrevFingerDiffY;
+    }
+
+    /**
      * Return the scaling factor from the previous scale event to the current
      * event. This value is defined as
      * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
-     * 
+     *
      * @return The current scaling factor.
      */
     public float getScaleFactor() {
@@ -455,20 +495,20 @@
         }
         return mScaleFactor;
     }
-    
+
     /**
      * Return the time difference in milliseconds between the previous
      * accepted scaling event and the current scaling event.
-     * 
+     *
      * @return Time difference since the last scaling event in milliseconds.
      */
     public long getTimeDelta() {
         return mTimeDelta;
     }
-    
+
     /**
      * Return the event time of the current event being processed.
-     * 
+     *
      * @return Current event time in milliseconds.
      */
     public long getEventTime() {
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index fb88c71..b1fdec8 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -41,6 +41,7 @@
     private static final int MAX_AGE_MILLISECONDS = 200;
     
     private static final int POINTER_POOL_CAPACITY = 20;
+    private static final int INVALID_POINTER = -1;
 
     private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
             Pools.finitePool(new PoolableManager<VelocityTracker>() {
@@ -76,6 +77,7 @@
     private Pointer mPointerListHead; // sorted by id in increasing order
     private int mLastTouchIndex;
     private int mGeneration;
+    private int mActivePointerId;
 
     private VelocityTracker mNext;
 
@@ -125,6 +127,7 @@
         
         mPointerListHead = null;
         mLastTouchIndex = 0;
+        mActivePointerId = INVALID_POINTER;
     }
     
     /**
@@ -180,6 +183,10 @@
                 // Pointer went down.  Add it to the list.
                 // Write a sentinel at the end of the pastTime trace so we will be able to
                 // tell when the trace started.
+                if (mActivePointerId == INVALID_POINTER) {
+                    // Congratulations! You're the new active pointer!
+                    mActivePointerId = pointerId;
+                }
                 pointer = obtainPointer();
                 pointer.id = pointerId;
                 pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
@@ -214,6 +221,7 @@
         previousPointer = null;
         for (Pointer pointer = mPointerListHead; pointer != null; ) {
             final Pointer nextPointer = pointer.next;
+            final int pointerId = pointer.id;
             if (pointer.generation != generation) {
                 // Pointer went up.  Remove it from the list.
                 if (previousPointer == null) {
@@ -222,6 +230,12 @@
                     previousPointer.next = nextPointer;
                 }
                 releasePointer(pointer);
+
+                if (pointerId == mActivePointerId) {
+                    // Pick a new active pointer. How is arbitrary.
+                    mActivePointerId = mPointerListHead != null ?
+                            mPointerListHead.id : INVALID_POINTER;
+                }
             } else {
                 previousPointer = pointer;
             }
@@ -334,7 +348,7 @@
      * @return The previously computed X velocity.
      */
     public float getXVelocity() {
-        Pointer pointer = getPointer(0);
+        Pointer pointer = getPointer(mActivePointerId);
         return pointer != null ? pointer.xVelocity : 0;
     }
     
@@ -345,7 +359,7 @@
      * @return The previously computed Y velocity.
      */
     public float getYVelocity() {
-        Pointer pointer = getPointer(0);
+        Pointer pointer = getPointer(mActivePointerId);
         return pointer != null ? pointer.yVelocity : 0;
     }
     
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3be18ab..35b806a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,14 +16,13 @@
 
 package android.view;
 
-import com.android.internal.R;
-import com.android.internal.view.menu.MenuBuilder;
-
+import android.content.ClipData;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.graphics.Camera;
 import android.graphics.Canvas;
 import android.graphics.Interpolator;
 import android.graphics.LinearGradient;
@@ -34,6 +33,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;
@@ -47,8 +47,6 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.AttributeSet;
-import android.util.Config;
-import android.util.EventLog;
 import android.util.Log;
 import android.util.Pool;
 import android.util.Poolable;
@@ -65,12 +63,15 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.ScrollBarDrawable;
+import com.android.internal.R;
+import com.android.internal.view.menu.MenuBuilder;
 
-import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.WeakHashMap;
 
 /**
@@ -260,7 +261,8 @@
  * <ul>
  * <li>Define a Button in the layout file and assign it a unique ID.
  * <pre>
- * &lt;Button id="@+id/my_button"
+ * &lt;Button
+ *     android:id="@+id/my_button"
  *     android:layout_width="wrap_content"
  *     android:layout_height="wrap_content"
  *     android:text="@string/my_button_text"/&gt;
@@ -917,6 +919,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.
      */
@@ -972,7 +988,7 @@
      * @see android.graphics.drawable.Drawable
      * @see #getDrawableState()
      */
-    protected static final int[] EMPTY_STATE_SET = {};
+    protected static final int[] EMPTY_STATE_SET;
     /**
      * Indicates the view is enabled. States are used with
      * {@link android.graphics.drawable.Drawable} to change the drawing of the
@@ -981,7 +997,7 @@
      * @see android.graphics.drawable.Drawable
      * @see #getDrawableState()
      */
-    protected static final int[] ENABLED_STATE_SET = {R.attr.state_enabled};
+    protected static final int[] ENABLED_STATE_SET;
     /**
      * Indicates the view is focused. States are used with
      * {@link android.graphics.drawable.Drawable} to change the drawing of the
@@ -990,7 +1006,7 @@
      * @see android.graphics.drawable.Drawable
      * @see #getDrawableState()
      */
-    protected static final int[] FOCUSED_STATE_SET = {R.attr.state_focused};
+    protected static final int[] FOCUSED_STATE_SET;
     /**
      * Indicates the view is selected. States are used with
      * {@link android.graphics.drawable.Drawable} to change the drawing of the
@@ -999,7 +1015,7 @@
      * @see android.graphics.drawable.Drawable
      * @see #getDrawableState()
      */
-    protected static final int[] SELECTED_STATE_SET = {R.attr.state_selected};
+    protected static final int[] SELECTED_STATE_SET;
     /**
      * Indicates the view is pressed. States are used with
      * {@link android.graphics.drawable.Drawable} to change the drawing of the
@@ -1009,7 +1025,7 @@
      * @see #getDrawableState()
      * @hide
      */
-    protected static final int[] PRESSED_STATE_SET = {R.attr.state_pressed};
+    protected static final int[] PRESSED_STATE_SET;
     /**
      * Indicates the view's window has focus. States are used with
      * {@link android.graphics.drawable.Drawable} to change the drawing of the
@@ -1018,8 +1034,7 @@
      * @see android.graphics.drawable.Drawable
      * @see #getDrawableState()
      */
-    protected static final int[] WINDOW_FOCUSED_STATE_SET =
-            {R.attr.state_window_focused};
+    protected static final int[] WINDOW_FOCUSED_STATE_SET;
     // Doubles
     /**
      * Indicates the view is enabled and has the focus.
@@ -1027,48 +1042,42 @@
      * @see #ENABLED_STATE_SET
      * @see #FOCUSED_STATE_SET
      */
-    protected static final int[] ENABLED_FOCUSED_STATE_SET =
-            stateSetUnion(ENABLED_STATE_SET, FOCUSED_STATE_SET);
+    protected static final int[] ENABLED_FOCUSED_STATE_SET;
     /**
      * Indicates the view is enabled and selected.
      *
      * @see #ENABLED_STATE_SET
      * @see #SELECTED_STATE_SET
      */
-    protected static final int[] ENABLED_SELECTED_STATE_SET =
-            stateSetUnion(ENABLED_STATE_SET, SELECTED_STATE_SET);
+    protected static final int[] ENABLED_SELECTED_STATE_SET;
     /**
      * Indicates the view is enabled and that its window has focus.
      *
      * @see #ENABLED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(ENABLED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is focused and selected.
      *
      * @see #FOCUSED_STATE_SET
      * @see #SELECTED_STATE_SET
      */
-    protected static final int[] FOCUSED_SELECTED_STATE_SET =
-            stateSetUnion(FOCUSED_STATE_SET, SELECTED_STATE_SET);
+    protected static final int[] FOCUSED_SELECTED_STATE_SET;
     /**
      * Indicates the view has the focus and that its window has the focus.
      *
      * @see #FOCUSED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] FOCUSED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    protected static final int[] FOCUSED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is selected and that its window has the focus.
      *
      * @see #SELECTED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] SELECTED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    protected static final int[] SELECTED_WINDOW_FOCUSED_STATE_SET;
     // Triples
     /**
      * Indicates the view is enabled, focused and selected.
@@ -1077,8 +1086,7 @@
      * @see #FOCUSED_STATE_SET
      * @see #SELECTED_STATE_SET
      */
-    protected static final int[] ENABLED_FOCUSED_SELECTED_STATE_SET =
-            stateSetUnion(ENABLED_FOCUSED_STATE_SET, SELECTED_STATE_SET);
+    protected static final int[] ENABLED_FOCUSED_SELECTED_STATE_SET;
     /**
      * Indicates the view is enabled, focused and its window has the focus.
      *
@@ -1086,8 +1094,7 @@
      * @see #FOCUSED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(ENABLED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    protected static final int[] ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is enabled, selected and its window has the focus.
      *
@@ -1095,8 +1102,7 @@
      * @see #SELECTED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(ENABLED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    protected static final int[] ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is focused, selected and its window has the focus.
      *
@@ -1104,8 +1110,7 @@
      * @see #SELECTED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is enabled, focused, selected and its window
      * has the focus.
@@ -1115,28 +1120,21 @@
      * @see #SELECTED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(ENABLED_FOCUSED_SELECTED_STATE_SET,
-                          WINDOW_FOCUSED_STATE_SET);
-
+    protected static final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed and its window has the focus.
      *
      * @see #PRESSED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
-
+    protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed and selected.
      *
      * @see #PRESSED_STATE_SET
      * @see #SELECTED_STATE_SET
      */
-    protected static final int[] PRESSED_SELECTED_STATE_SET =
-            stateSetUnion(PRESSED_STATE_SET, SELECTED_STATE_SET);
-
+    protected static final int[] PRESSED_SELECTED_STATE_SET;
     /**
      * Indicates the view is pressed, selected and its window has the focus.
      *
@@ -1144,18 +1142,14 @@
      * @see #SELECTED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
-
+    protected static final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed and focused.
      *
      * @see #PRESSED_STATE_SET
      * @see #FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_STATE_SET, FOCUSED_STATE_SET);
-
+    protected static final int[] PRESSED_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed, focused and its window has the focus.
      *
@@ -1163,9 +1157,7 @@
      * @see #FOCUSED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
-
+    protected static final int[] PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed, focused and selected.
      *
@@ -1173,9 +1165,7 @@
      * @see #SELECTED_STATE_SET
      * @see #FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_FOCUSED_SELECTED_STATE_SET =
-            stateSetUnion(PRESSED_FOCUSED_STATE_SET, SELECTED_STATE_SET);
-
+    protected static final int[] PRESSED_FOCUSED_SELECTED_STATE_SET;
     /**
      * Indicates the view is pressed, focused, selected and its window has the focus.
      *
@@ -1184,18 +1174,14 @@
      * @see #SELECTED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
-
+    protected static final int[] PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed and enabled.
      *
      * @see #PRESSED_STATE_SET
      * @see #ENABLED_STATE_SET
      */
-    protected static final int[] PRESSED_ENABLED_STATE_SET =
-            stateSetUnion(PRESSED_STATE_SET, ENABLED_STATE_SET);
-
+    protected static final int[] PRESSED_ENABLED_STATE_SET;
     /**
      * Indicates the view is pressed, enabled and its window has the focus.
      *
@@ -1203,9 +1189,7 @@
      * @see #ENABLED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_ENABLED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
-
+    protected static final int[] PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed, enabled and selected.
      *
@@ -1213,9 +1197,7 @@
      * @see #ENABLED_STATE_SET
      * @see #SELECTED_STATE_SET
      */
-    protected static final int[] PRESSED_ENABLED_SELECTED_STATE_SET =
-            stateSetUnion(PRESSED_ENABLED_STATE_SET, SELECTED_STATE_SET);
-
+    protected static final int[] PRESSED_ENABLED_SELECTED_STATE_SET;
     /**
      * Indicates the view is pressed, enabled, selected and its window has the
      * focus.
@@ -1225,9 +1207,7 @@
      * @see #SELECTED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_ENABLED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
-
+    protected static final int[] PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed, enabled and focused.
      *
@@ -1235,9 +1215,7 @@
      * @see #ENABLED_STATE_SET
      * @see #FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_ENABLED_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_ENABLED_STATE_SET, FOCUSED_STATE_SET);
-
+    protected static final int[] PRESSED_ENABLED_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed, enabled, focused and its window has the
      * focus.
@@ -1247,9 +1225,7 @@
      * @see #FOCUSED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_ENABLED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
-
+    protected static final int[] PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET;
     /**
      * Indicates the view is pressed, enabled, focused and selected.
      *
@@ -1258,9 +1234,7 @@
      * @see #SELECTED_STATE_SET
      * @see #FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET =
-            stateSetUnion(PRESSED_ENABLED_FOCUSED_STATE_SET, SELECTED_STATE_SET);
-
+    protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET;
     /**
      * Indicates the view is pressed, enabled, focused, selected and its window
      * has the focus.
@@ -1271,47 +1245,132 @@
      * @see #FOCUSED_STATE_SET
      * @see #WINDOW_FOCUSED_STATE_SET
      */
-    protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET =
-            stateSetUnion(PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET);
+    protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
 
     /**
      * The order here is very important to {@link #getDrawableState()}
      */
-    private static final int[][] VIEW_STATE_SETS = {
-        EMPTY_STATE_SET,                                           // 0 0 0 0 0
-        WINDOW_FOCUSED_STATE_SET,                                  // 0 0 0 0 1
-        SELECTED_STATE_SET,                                        // 0 0 0 1 0
-        SELECTED_WINDOW_FOCUSED_STATE_SET,                         // 0 0 0 1 1
-        FOCUSED_STATE_SET,                                         // 0 0 1 0 0
-        FOCUSED_WINDOW_FOCUSED_STATE_SET,                          // 0 0 1 0 1
-        FOCUSED_SELECTED_STATE_SET,                                // 0 0 1 1 0
-        FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET,                 // 0 0 1 1 1
-        ENABLED_STATE_SET,                                         // 0 1 0 0 0
-        ENABLED_WINDOW_FOCUSED_STATE_SET,                          // 0 1 0 0 1
-        ENABLED_SELECTED_STATE_SET,                                // 0 1 0 1 0
-        ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET,                 // 0 1 0 1 1
-        ENABLED_FOCUSED_STATE_SET,                                 // 0 1 1 0 0
-        ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET,                  // 0 1 1 0 1
-        ENABLED_FOCUSED_SELECTED_STATE_SET,                        // 0 1 1 1 0
-        ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET,         // 0 1 1 1 1
-        PRESSED_STATE_SET,                                         // 1 0 0 0 0
-        PRESSED_WINDOW_FOCUSED_STATE_SET,                          // 1 0 0 0 1
-        PRESSED_SELECTED_STATE_SET,                                // 1 0 0 1 0
-        PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET,                 // 1 0 0 1 1
-        PRESSED_FOCUSED_STATE_SET,                                 // 1 0 1 0 0
-        PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET,                  // 1 0 1 0 1
-        PRESSED_FOCUSED_SELECTED_STATE_SET,                        // 1 0 1 1 0
-        PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET,         // 1 0 1 1 1
-        PRESSED_ENABLED_STATE_SET,                                 // 1 1 0 0 0
-        PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET,                  // 1 1 0 0 1
-        PRESSED_ENABLED_SELECTED_STATE_SET,                        // 1 1 0 1 0
-        PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET,         // 1 1 0 1 1
-        PRESSED_ENABLED_FOCUSED_STATE_SET,                         // 1 1 1 0 0
-        PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET,          // 1 1 1 0 1
-        PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET,                // 1 1 1 1 0
-        PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 1 1 1 1
+    private static final int[][] VIEW_STATE_SETS;
+
+    static final int VIEW_STATE_WINDOW_FOCUSED = 1;
+    static final int VIEW_STATE_SELECTED = 1 << 1;
+    static final int VIEW_STATE_FOCUSED = 1 << 2;
+    static final int VIEW_STATE_ENABLED = 1 << 3;
+    static final int VIEW_STATE_PRESSED = 1 << 4;
+    static final int VIEW_STATE_ACTIVATED = 1 << 5;
+
+    static final int[] VIEW_STATE_IDS = new int[] {
+        R.attr.state_window_focused,    VIEW_STATE_WINDOW_FOCUSED,
+        R.attr.state_selected,          VIEW_STATE_SELECTED,
+        R.attr.state_focused,           VIEW_STATE_FOCUSED,
+        R.attr.state_enabled,           VIEW_STATE_ENABLED,
+        R.attr.state_pressed,           VIEW_STATE_PRESSED,
+        R.attr.state_activated,         VIEW_STATE_ACTIVATED,
     };
 
+    static {
+        int[] orderedIds = new int[VIEW_STATE_IDS.length];
+        for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) {
+            int viewState = R.styleable.ViewDrawableStates[i];
+            for (int j = 0; j<VIEW_STATE_IDS.length; j += 2) {
+                if (VIEW_STATE_IDS[j] == viewState) {
+                    orderedIds[i * 2] = viewState;
+                    orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1];
+                }
+            }
+        }
+        final int NUM_BITS = VIEW_STATE_IDS.length / 2;
+        VIEW_STATE_SETS = new int[1 << NUM_BITS][];
+        for (int i = 0; i < VIEW_STATE_SETS.length; i++) {
+            int numBits = Integer.bitCount(i);
+            int[] set = new int[numBits];
+            int pos = 0;
+            for (int j = 0; j < orderedIds.length; j += 2) {
+                if ((i & orderedIds[j+1]) != 0) {
+                    set[pos++] = orderedIds[j];
+                }
+            }
+            VIEW_STATE_SETS[i] = set;
+        }
+
+        EMPTY_STATE_SET = VIEW_STATE_SETS[0];
+        WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_WINDOW_FOCUSED];
+        SELECTED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_SELECTED];
+        SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED];
+        FOCUSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_FOCUSED];
+        FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED];
+        FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED];
+        FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
+                | VIEW_STATE_FOCUSED];
+        ENABLED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_ENABLED];
+        ENABLED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_ENABLED];
+        ENABLED_SELECTED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_SELECTED | VIEW_STATE_ENABLED];
+        ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
+                | VIEW_STATE_ENABLED];
+        ENABLED_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_FOCUSED | VIEW_STATE_ENABLED];
+        ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED
+                | VIEW_STATE_ENABLED];
+        ENABLED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED
+                | VIEW_STATE_ENABLED];
+        ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
+                | VIEW_STATE_FOCUSED| VIEW_STATE_ENABLED];
+
+        PRESSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_PRESSED];
+        PRESSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_PRESSED];
+        PRESSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_SELECTED | VIEW_STATE_PRESSED];
+        PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
+                | VIEW_STATE_PRESSED];
+        PRESSED_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_FOCUSED | VIEW_STATE_PRESSED];
+        PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED
+                | VIEW_STATE_PRESSED];
+        PRESSED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED
+                | VIEW_STATE_PRESSED];
+        PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
+                | VIEW_STATE_FOCUSED | VIEW_STATE_PRESSED];
+        PRESSED_ENABLED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
+        PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_ENABLED
+                | VIEW_STATE_PRESSED];
+        PRESSED_ENABLED_SELECTED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_SELECTED | VIEW_STATE_ENABLED
+                | VIEW_STATE_PRESSED];
+        PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
+                | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
+        PRESSED_ENABLED_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_FOCUSED | VIEW_STATE_ENABLED
+                | VIEW_STATE_PRESSED];
+        PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED
+                | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
+        PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED
+                | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
+        PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
+                VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
+                | VIEW_STATE_FOCUSED| VIEW_STATE_ENABLED
+                | VIEW_STATE_PRESSED];
+    }
+
     /**
      * Used by views that contain lists of items. This state indicates that
      * the view is showing the last item.
@@ -1551,38 +1610,15 @@
     private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
 
     /**
-     * Always allow a user to overscroll this view, provided it is a
-     * view that can scroll.
+     * Indicates that pivotX or pivotY were explicitly set and we should not assume the center
+     * for transform operations
      *
-     * @see #getOverscrollMode()
-     * @see #setOverscrollMode(int)
+     * @hide
      */
-    public static final int OVERSCROLL_ALWAYS = 0;
+    private static final int PIVOT_EXPLICITLY_SET = 0x20000000;
 
-    /**
-     * Allow a user to overscroll this view only if the content is large
-     * enough to meaningfully scroll, provided it is a view that can scroll.
-     *
-     * @see #getOverscrollMode()
-     * @see #setOverscrollMode(int)
-     */
-    public static final int OVERSCROLL_IF_CONTENT_SCROLLS = 1;
-
-    /**
-     * Never allow a user to overscroll this view.
-     *
-     * @see #getOverscrollMode()
-     * @see #setOverscrollMode(int)
-     */
-    public static final int OVERSCROLL_NEVER = 2;
-
-    /**
-     * Controls the overscroll mode for this view.
-     * See {@link #overscrollBy(int, int, int, int, int, int, int, int, boolean)},
-     * {@link #OVERSCROLL_ALWAYS}, {@link #OVERSCROLL_IF_CONTENT_SCROLLS},
-     * and {@link #OVERSCROLL_NEVER}.
-     */
-    private int mOverscrollMode;
+    /** {@hide} */
+    static final int ACTIVATED                    = 0x40000000;
 
     /**
      * The parent this view is attached to.
@@ -1635,6 +1671,136 @@
     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
+     * getInverseMatrix(), which will automatically recalculate the matrix if necessary
+     * to get the correct matrix based on the latest rotation and scale properties.
+     */
+    private Matrix mInverseMatrix;
+
+    /**
+     * 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 updateMatrix() or to a function that
+     * calls it such as getMatrix(), hasIdentityMatrix() and getInverseMatrix().
+     */
+    private boolean mMatrixIsIdentity = true;
+
+    /**
+     * The Camera object is used to compute a 3D matrix when rotationX or rotationY are set.
+     */
+    private Camera mCamera = null;
+
+    /**
+     * This matrix is used when computing the matrix for 3D rotations.
+     */
+    private Matrix matrix3D = null;
+
+    /**
+     * These prev values are used to recalculate a centered pivot point when necessary. The
+     * pivot point is only used in matrix operations (when rotation, scale, or translation are
+     * set), so thes values are only used then as well.
+     */
+    private int mPrevWidth = -1;
+    private int mPrevHeight = -1;
+
+    /**
+     * Convenience value to check for float values that are close enough to zero to be considered
+     * zero.
+     */
+    private static final float NONZERO_EPSILON = .001f;
+
+    /**
+     * The degrees rotation around the vertical axis through the pivot point.
+     */
+    @ViewDebug.ExportedProperty
+    private float mRotationY = 0f;
+
+    /**
+     * The degrees rotation around the horizontal axis through the pivot point.
+     */
+    @ViewDebug.ExportedProperty
+    private float mRotationX = 0f;
+
+    /**
+     * 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}
@@ -1748,6 +1914,11 @@
     protected OnFocusChangeListener mOnFocusChangeListener;
 
     /**
+     * Listeners for layout change events.
+     */
+    private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
+
+    /**
      * Listener used to dispatch click events.
      * This field should be made private, so it is hidden from the SDK.
      * {@hide}
@@ -1783,8 +1954,9 @@
 
     private int[] mDrawableState = null;
 
-    private SoftReference<Bitmap> mDrawingCache;
-    private SoftReference<Bitmap> mUnscaledDrawingCache;
+    private Bitmap mDrawingCache;
+    private Bitmap mUnscaledDrawingCache;
+    private DisplayList mDisplayList;
 
     /**
      * When this view has focus and the next focus is {@link #FOCUS_LEFT},
@@ -1860,8 +2032,13 @@
      */
     private int mTouchSlop;
 
-    // Used for debug only
-    static long sInstanceCount = 0;
+    /**
+     * Cache drag/drop state
+     *
+     */
+    boolean mCanAcceptDrop;
+    private int mThumbnailWidth;
+    private int mThumbnailHeight;
 
     /**
      * Simple constructor to use when creating a view from code.
@@ -1873,10 +2050,7 @@
         mContext = context;
         mResources = context != null ? context.getResources() : null;
         mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
-        // Used for debug only
-        //++sInstanceCount;
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
-        setOverscrollMode(OVERSCROLL_ALWAYS);
     }
 
     /**
@@ -1942,7 +2116,6 @@
 
         int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
 
-        int overscrollMode = mOverscrollMode;
         final int N = a.getIndexCount();
         for (int i = 0; i < N; i++) {
             int attr = a.getIndex(i);
@@ -2148,14 +2321,9 @@
                         });
                     }
                     break;
-                case R.styleable.View_overscrollMode:
-                    overscrollMode = a.getInt(attr, OVERSCROLL_ALWAYS);
-                    break;
             }
         }
 
-        setOverscrollMode(overscrollMode);
-
         if (background != null) {
             setBackgroundDrawable(background);
         }
@@ -2204,15 +2372,6 @@
     View() {
     }
 
-    // Used for debug only
-    /*
-    @Override
-    protected void finalize() throws Throwable {
-        super.finalize();
-        --sInstanceCount;
-    }
-    */
-
     /**
      * <p>
      * Initializes the fading edges from a given set of styled attributes. This
@@ -2419,6 +2578,39 @@
     }
 
     /**
+     * Add a listener that will be called when the bounds of the view change due to
+     * layout processing.
+     *
+     * @param listener The listener that will be called when layout bounds change.
+     */
+    public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
+        if (mOnLayoutChangeListeners == null) {
+            mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>();
+        }
+        mOnLayoutChangeListeners.add(listener);
+    }
+
+    /**
+     * Remove a listener for layout changes.
+     *
+     * @param listener The listener for layout bounds change.
+     */
+    public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) {
+        if (mOnLayoutChangeListeners == null) {
+            return;
+        }
+        mOnLayoutChangeListeners.remove(listener);
+    }
+
+    /**
+     * Gets the current list of listeners for layout changes.
+     * @return
+     */
+    public List<OnLayoutChangeListener> getOnLayoutChangeListeners() {
+        return mOnLayoutChangeListeners;
+    }
+
+    /**
      * Returns the focus-change callback registered for this view.
      *
      * @return The callback, or null if one is not registered.
@@ -2521,6 +2713,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
      */
@@ -3350,7 +3554,7 @@
     }
 
     /**
-     * Sets the pressed that for this view.
+     * Sets the pressed state for this view.
      *
      * @see #isClickable()
      * @see #setClickable(boolean)
@@ -3457,6 +3661,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.
@@ -3721,6 +3957,12 @@
         return true;
     }
 
+    /** Gets the ViewRoot, or null if not attached. */
+    /*package*/ ViewRoot getViewRoot() {
+        View root = getRootView();
+        return root != null ? (ViewRoot)root.getParent() : null;
+    }
+
     /**
      * Call this to try to give focus to a specific view or to one of its descendants. This is a
      * special variant of {@link #requestFocus() } that will allow views that are not focuable in
@@ -3734,12 +3976,9 @@
     public final boolean requestFocusFromTouch() {
         // Leave touch mode if we need to
         if (isInTouchMode()) {
-            View root = getRootView();
-            if (root != null) {
-               ViewRoot viewRoot = (ViewRoot)root.getParent();
-               if (viewRoot != null) {
-                   viewRoot.ensureTouchMode(false);
-               }
+            ViewRoot viewRoot = getViewRoot();
+            if (viewRoot != null) {
+                viewRoot.ensureTouchMode(false);
             }
         }
         return requestFocus(View.FOCUS_DOWN);
@@ -3841,12 +4080,13 @@
      */
     public boolean dispatchKeyEvent(KeyEvent event) {
         // If any attached key listener a first crack at the event.
-        //noinspection SimplifiableIfStatement
 
+        //noinspection SimplifiableIfStatement,deprecation
         if (android.util.Config.LOGV) {
             captureViewInfo("captureViewKeyEvent", this);
         }
 
+        //noinspection SimplifiableIfStatement
         if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                 && mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
             return true;
@@ -3878,6 +4118,7 @@
             return false;
         }
 
+        //noinspection SimplifiableIfStatement
         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                 mOnTouchListener.onTouch(this, event)) {
             return true;
@@ -3894,6 +4135,7 @@
      * @see #getFilterTouchesWhenObscured
      */
     public boolean onFilterTouchEventForSecurity(MotionEvent event) {
+        //noinspection RedundantIfStatement
         if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
             // Window is obscured, drop this touch.
@@ -4501,9 +4743,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) {
@@ -4668,6 +4908,9 @@
         }
 
         if ((changed & VISIBILITY_MASK) != 0) {
+            if (mParent instanceof ViewGroup) {
+                ((ViewGroup)mParent).onChildVisibilityChanged(this, (flags & VISIBILITY_MASK));
+            }
             dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK));
         }
 
@@ -4738,6 +4981,28 @@
     }
 
     /**
+     * Interface definition for a callback to be invoked when the layout bounds of a view
+     * changes due to layout processing.
+     */
+    public interface OnLayoutChangeListener {
+        /**
+         * Called when the focus state of a view has changed.
+         *
+         * @param v The view whose state has changed.
+         * @param left The new value of the view's left property.
+         * @param top The new value of the view's top property.
+         * @param right The new value of the view's right property.
+         * @param bottom The new value of the view's bottom property.
+         * @param oldLeft The previous value of the view's left property.
+         * @param oldTop The previous value of the view's top property.
+         * @param oldRight The previous value of the view's right property.
+         * @param oldBottom The previous value of the view's bottom property.
+         */
+        void onLayoutChange(View v, int left, int top, int right, int bottom,
+            int oldLeft, int oldTop, int oldRight, int oldBottom);
+    }
+
+    /**
      * This is called during layout when the size of this view has changed. If
      * you were just added to the view hierarchy, you're called with the old
      * values of 0.
@@ -4849,6 +5114,368 @@
     }
 
     /**
+     * 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() {
+        updateMatrix();
+        return mMatrix;
+    }
+
+    /**
+     * Utility function to determine if the value is far enough away from zero to be
+     * considered non-zero.
+     * @param value A floating point value to check for zero-ness
+     * @return whether the passed-in value is far enough away from zero to be considered non-zero
+     */
+    private static boolean nonzero(float value) {
+        return (value < -NONZERO_EPSILON || value > NONZERO_EPSILON);
+    }
+
+    /**
+     * Returns true if the transform matrix is the identity matrix.
+     * Recomputes the matrix if necessary.
+     * 
+     * @return True if the transform matrix is the identity matrix, false otherwise.
+     */
+    final boolean hasIdentityMatrix() {
+        updateMatrix();
+        return mMatrixIsIdentity;
+    }
+
+    /**
+     * Recomputes the transform matrix if necessary.
+     */
+    private void updateMatrix() {
+        if (mMatrixDirty) {
+            // transform-related properties have changed since the last time someone
+            // asked for the matrix; recalculate it with the current values
+
+            // Figure out if we need to update the pivot point
+            if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
+                if ((mRight - mLeft) != mPrevWidth && (mBottom - mTop) != mPrevHeight) {
+                    mPrevWidth = mRight - mLeft;
+                    mPrevHeight = mBottom - mTop;
+                    mPivotX = (float) mPrevWidth / 2f;
+                    mPivotY = (float) mPrevHeight / 2f;
+                }
+            }
+            mMatrix.reset();
+            if (!nonzero(mRotationX) && !nonzero(mRotationY)) {
+                mMatrix.setTranslate(mTranslationX, mTranslationY);
+                mMatrix.preRotate(mRotation, mPivotX, mPivotY);
+                mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY);
+            } else {
+                if (mCamera == null) {
+                    mCamera = new Camera();
+                    matrix3D = new Matrix();
+                }
+                mCamera.save();
+                mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY);
+                mCamera.rotateX(mRotationX);
+                mCamera.rotateY(mRotationY);
+                mCamera.rotateZ(-mRotation);
+                mCamera.getMatrix(matrix3D);
+                matrix3D.preTranslate(-mPivotX, -mPivotY);
+                matrix3D.postTranslate(mPivotX + mTranslationX, mPivotY + mTranslationY);
+                mMatrix.postConcat(matrix3D);
+                mCamera.restore();
+            }
+            mMatrixDirty = false;
+            mMatrixIsIdentity = mMatrix.isIdentity();
+            mInverseMatrixDirty = true;
+        }
+    }
+
+    /**
+     * 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.
+     */
+    final Matrix getInverseMatrix() {
+        updateMatrix();
+        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. Increasing values
+     * result in clockwise rotation.
+     *
+     * @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(false);
+            mRotation = rotation;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate(false);
+        }
+    }
+
+    /**
+     * The degrees that the view is rotated around the vertical axis through the pivot point.
+     *
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The degrees of Y rotation.
+     */
+    public float getRotationY() {
+        return mRotationY;
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the vertical axis through the pivot point.
+     * Increasing values result in counter-clockwise rotation from the viewpoint of looking
+     * down the y axis.
+     *
+     * @param rotationY The degrees of Y rotation.
+     * @see #getPivotX()
+     * @see #getPivotY()
+     */
+    public void setRotationY(float rotationY) {
+        if (mRotationY != rotationY) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate(false);
+            mRotationY = rotationY;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate(false);
+        }
+    }
+
+    /**
+     * The degrees that the view is rotated around the horizontal axis through the pivot point.
+     *
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The degrees of X rotation.
+     */
+    public float getRotationX() {
+        return mRotationX;
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the horizontal axis through the pivot point.
+     * Increasing values result in clockwise rotation from the viewpoint of looking down the
+     * x axis.
+     *
+     * @param rotationX The degrees of X rotation.
+     * @see #getPivotX()
+     * @see #getPivotY()
+     */
+    public void setRotationX(float rotationX) {
+        if (mRotationX != rotationX) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate(false);
+            mRotationX = rotationX;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate(false);
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * <p>By default, this is 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(false);
+            mScaleX = scaleX;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate(false);
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * <p>By default, this is 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(false);
+            mScaleY = scaleY;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate(false);
+        }
+    }
+
+    /**
+     * 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}.
+     * By default, the pivot point is centered on the object.
+     * Setting this property disables this behavior and causes the view to use only the
+     * explicitly set pivotX and pivotY values.
+     *
+     * @param pivotX The x location of the pivot point.
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     */
+    public void setPivotX(float pivotX) {
+        mPrivateFlags |= PIVOT_EXPLICITLY_SET;
+        if (mPivotX != pivotX) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate(false);
+            mPivotX = pivotX;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate(false);
+        }
+    }
+
+    /**
+     * 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}. By default, the pivot point is centered on the object.
+     * Setting this property disables this behavior and causes the view to use only the
+     * explicitly set pivotX and pivotY values.
+     *
+     * @param pivotY The y location of the pivot point.
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     */
+    public void setPivotY(float pivotY) {
+        mPrivateFlags |= PIVOT_EXPLICITLY_SET;
+        if (mPivotY != pivotY) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate(false);
+            mPivotY = pivotY;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate(false);
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * <p>By default this is 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;
+        if (onSetAlpha((int) (alpha * 255))) {
+            // subclass is handling alpha - don't optimize rendering cache invalidation
+            invalidate();
+        } else {
+            invalidate(false);
+        }
+    }
+
+    /**
      * Top position of this view relative to its parent.
      *
      * @return The top of this view, in pixels.
@@ -4859,6 +5486,51 @@
     }
 
     /**
+     * Sets the top position of this view relative to its parent. This method is meant to be called
+     * by the layout system and should not generally be called otherwise, because the property
+     * may be changed at any time by the layout.
+     *
+     * @param top The top of this view, in pixels.
+     */
+    public final void setTop(int top) {
+        if (top != mTop) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
+                final ViewParent p = mParent;
+                if (p != null && mAttachInfo != null) {
+                    final Rect r = mAttachInfo.mTmpInvalRect;
+                    int minTop;
+                    int yLoc;
+                    if (top < mTop) {
+                        minTop = top;
+                        yLoc = top - mTop;
+                    } else {
+                        minTop = mTop;
+                        yLoc = 0;
+                    }
+                    r.set(0, yLoc, mRight - mLeft, mBottom - minTop);
+                    p.invalidateChild(this, r);
+                }
+            } else {
+                // Double-invalidation is necessary to capture view's old and new areas
+                invalidate();
+            }
+
+            int width = mRight - mLeft;
+            int oldHeight = mBottom - mTop;
+
+            mTop = top;
+
+            onSizeChanged(width, mBottom - mTop, width, oldHeight);
+
+            if (!mMatrixIsIdentity) {
+                mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+                invalidate();
+            }
+        }
+    }
+
+    /**
      * Bottom position of this view relative to its parent.
      *
      * @return The bottom of this view, in pixels.
@@ -4869,6 +5541,48 @@
     }
 
     /**
+     * Sets the bottom position of this view relative to its parent. This method is meant to be
+     * called by the layout system and should not generally be called otherwise, because the
+     * property may be changed at any time by the layout.
+     *
+     * @param bottom The bottom of this view, in pixels.
+     */
+    public final void setBottom(int bottom) {
+        if (bottom != mBottom) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
+                final ViewParent p = mParent;
+                if (p != null && mAttachInfo != null) {
+                    final Rect r = mAttachInfo.mTmpInvalRect;
+                    int maxBottom;
+                    if (bottom < mBottom) {
+                        maxBottom = mBottom;
+                    } else {
+                        maxBottom = bottom;
+                    }
+                    r.set(0, 0, mRight - mLeft, maxBottom - mTop);
+                    p.invalidateChild(this, r);
+                }
+            } else {
+                // Double-invalidation is necessary to capture view's old and new areas
+                invalidate();
+            }
+
+            int width = mRight - mLeft;
+            int oldHeight = mBottom - mTop;
+
+            mBottom = bottom;
+
+            onSizeChanged(width, mBottom - mTop, width, oldHeight);
+
+            if (!mMatrixIsIdentity) {
+                mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+                invalidate();
+            }
+        }
+    }
+
+    /**
      * Left position of this view relative to its parent.
      *
      * @return The left edge of this view, in pixels.
@@ -4879,6 +5593,52 @@
     }
 
     /**
+     * Sets the left position of this view relative to its parent. This method is meant to be called
+     * by the layout system and should not generally be called otherwise, because the property
+     * may be changed at any time by the layout.
+     *
+     * @param left The bottom of this view, in pixels.
+     */
+    public final void setLeft(int left) {
+        if (left != mLeft) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
+                final ViewParent p = mParent;
+                if (p != null && mAttachInfo != null) {
+                    final Rect r = mAttachInfo.mTmpInvalRect;
+                    int minLeft;
+                    int xLoc;
+                    if (left < mLeft) {
+                        minLeft = left;
+                        xLoc = left - mLeft;
+                    } else {
+                        minLeft = mLeft;
+                        xLoc = 0;
+                    }
+                    r.set(xLoc, 0, mRight - minLeft, mBottom - mTop);
+                    p.invalidateChild(this, r);
+                }
+            } else {
+                // Double-invalidation is necessary to capture view's old and new areas
+                invalidate();
+            }
+
+            int oldWidth = mRight - mLeft;
+            int height = mBottom - mTop;
+
+            mLeft = left;
+
+            onSizeChanged(mRight - mLeft, height, oldWidth, height);
+
+            if (!mMatrixIsIdentity) {
+                mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+                invalidate();
+            }
+
+        }
+    }
+
+    /**
      * Right position of this view relative to its parent.
      *
      * @return The right edge of this view, in pixels.
@@ -4889,12 +5649,188 @@
     }
 
     /**
+     * Sets the right position of this view relative to its parent. This method is meant to be called
+     * by the layout system and should not generally be called otherwise, because the property
+     * may be changed at any time by the layout.
+     *
+     * @param right The bottom of this view, in pixels.
+     */
+    public final void setRight(int right) {
+        if (right != mRight) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
+                final ViewParent p = mParent;
+                if (p != null && mAttachInfo != null) {
+                    final Rect r = mAttachInfo.mTmpInvalRect;
+                    int maxRight;
+                    if (right < mRight) {
+                        maxRight = mRight;
+                    } else {
+                        maxRight = right;
+                    }
+                    r.set(0, 0, maxRight - mLeft, mBottom - mTop);
+                    p.invalidateChild(this, r);
+                }
+            } else {
+                // Double-invalidation is necessary to capture view's old and new areas
+                invalidate();
+            }
+
+            int oldWidth = mRight - mLeft;
+            int height = mBottom - mTop;
+
+            mRight = right;
+
+            onSizeChanged(mRight - mLeft, height, oldWidth, height);
+
+            if (!mMatrixIsIdentity) {
+                mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+                invalidate();
+            }
+        }
+    }
+
+    /**
+     * 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(false);
+            mTranslationX = translationX;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate(false);
+        }
+    }
+
+    /**
+     * 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(false);
+            mTranslationY = translationY;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate(false);
+        }
+    }
+
+    /**
      * 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);
+        updateMatrix();
+        if (mMatrixIsIdentity || mAttachInfo == null) {
+            outRect.set(mLeft, mTop, mRight, mBottom);
+        } else {
+            final RectF tmpRect = mAttachInfo.mTmpTransformRect;
+            tmpRect.set(-mPivotX, -mPivotY, getWidth() - mPivotX, getHeight() - mPivotY);
+            mMatrix.mapRect(tmpRect);
+            outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
+                    (int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
+        }
+    }
+
+    /**
+     * Determines whether the given point, in local coordinates is inside the view.
+     */
+    /*package*/ final boolean pointInView(float localX, float localY) {
+        return localX >= 0 && localX < (mRight - mLeft)
+                && localY >= 0 && localY < (mBottom - mTop);
+    }
+
+    /**
+     * Utility method to determine whether the given point, in local coordinates,
+     * is inside the view, where the area of the view is expanded by the slop factor.
+     * This method is called while processing touch-move events to determine if the event
+     * is still within the view.
+     */
+    private boolean pointInView(float localX, float localY, float slop) {
+        return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
+                localY < ((mBottom - mTop) + slop);
     }
 
     /**
@@ -4957,8 +5893,39 @@
      * @param offset the number of pixels to offset the view by
      */
     public void offsetTopAndBottom(int offset) {
-        mTop += offset;
-        mBottom += offset;
+        if (offset != 0) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
+                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(false);
+            }
+
+            mTop += offset;
+            mBottom += offset;
+
+            if (!mMatrixIsIdentity) {
+                mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+                invalidate(false);
+            }
+        }
     }
 
     /**
@@ -4967,8 +5934,36 @@
      * @param offset the numer of pixels to offset the view by
      */
     public void offsetLeftAndRight(int offset) {
-        mLeft += offset;
-        mRight += offset;
+        if (offset != 0) {
+            updateMatrix();
+            if (mMatrixIsIdentity) {
+                final ViewParent p = mParent;
+                if (p != null && mAttachInfo != null) {
+                    final Rect r = mAttachInfo.mTmpInvalRect;
+                    int minLeft;
+                    int maxRight;
+                    if (offset < 0) {
+                        minLeft = mLeft + offset;
+                        maxRight = mRight;
+                    } else {
+                        minLeft = mLeft;
+                        maxRight = mRight + offset;
+                    }
+                    r.set(0, 0, maxRight - minLeft, mBottom - mTop);
+                    p.invalidateChild(this, r);
+                }
+            } else {
+                invalidate(false);
+            }
+
+            mLeft += offset;
+            mRight += offset;
+
+            if (!mMatrixIsIdentity) {
+                mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+                invalidate(false);
+            }
+        }
     }
 
     /**
@@ -5213,7 +6208,8 @@
             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
         }
 
-        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
+                (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
             mPrivateFlags &= ~DRAWING_CACHE_VALID;
             final ViewParent p = mParent;
             final AttachInfo ai = mAttachInfo;
@@ -5244,7 +6240,8 @@
             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
         }
 
-        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
+                (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
             mPrivateFlags &= ~DRAWING_CACHE_VALID;
             final ViewParent p = mParent;
             final AttachInfo ai = mAttachInfo;
@@ -5264,12 +6261,31 @@
      * UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
      */
     public void invalidate() {
+        invalidate(true);
+    }
+
+    /**
+     * This is where the invalidate() work actually happens. A full invalidate()
+     * causes the drawing cache to be invalidated, but this function can be called with
+     * invalidateCache set to false to skip that invalidation step for cases that do not
+     * need it (for example, a component that remains at the same dimensions with the same
+     * content).
+     *
+     * @param invalidateCache Whether the drawing cache for this view should be invalidated as
+     * well. This is usually true for a full invalidate, but may be set to false if the
+     * View's contents or dimensions have not changed.
+     */
+    private void invalidate(boolean invalidateCache) {
         if (ViewDebug.TRACE_HIERARCHY) {
             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
         }
 
-        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
-            mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
+        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
+                (invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID)) {
+            mPrivateFlags &= ~DRAWN;
+            if (invalidateCache) {
+                mPrivateFlags &= ~DRAWING_CACHE_VALID;
+            }
             final ViewParent p = mParent;
             final AttachInfo ai = mAttachInfo;
             if (p != null && ai != null) {
@@ -6007,9 +7023,8 @@
      * @see android.widget.ScrollBarDrawable
      * @hide
      */
-    protected void onDrawHorizontalScrollBar(Canvas canvas,
-                                             Drawable scrollBar,
-                                             int l, int t, int r, int b) {
+    protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar,
+            int l, int t, int r, int b) {
         scrollBar.setBounds(l, t, r, b);
         scrollBar.draw(canvas);
     }
@@ -6028,9 +7043,8 @@
      * @see android.widget.ScrollBarDrawable
      * @hide
      */
-    protected void onDrawVerticalScrollBar(Canvas canvas,
-                                           Drawable scrollBar,
-                                           int l, int t, int r, int b) {
+    protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+            int l, int t, int r, int b) {
         scrollBar.setBounds(l, t, r, b);
         scrollBar.draw(canvas);
     }
@@ -6384,6 +7398,63 @@
     }
 
     /**
+     * <p>Returns a display list that can be used to draw this view again
+     * without executing its draw method.</p>
+     * 
+     * @return A DisplayList ready to replay, or null if caching is not enabled.
+     */
+    DisplayList getDisplayList() {
+        if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
+            return null;
+        }
+        
+        if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+            return null;
+        }
+
+        if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED &&
+                ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDisplayList == null)) {
+
+            if (mDisplayList != null) {
+                mDisplayList.destroy();
+            }
+
+            mDisplayList = mAttachInfo.mHardwareRenderer.createDisplayList();
+
+            final HardwareCanvas canvas = mDisplayList.start();
+            try {
+                int width = mRight - mLeft;
+                int height = mBottom - mTop;
+
+                canvas.setViewport(width, height);
+                canvas.onPreDraw();
+
+                final int restoreCount = canvas.save();
+
+                mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
+    
+                // Fast path for layouts with no backgrounds
+                if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+                    mPrivateFlags &= ~DIRTY_MASK;
+                    dispatchDraw(canvas);
+                } else {
+                    draw(canvas);
+                }
+    
+                canvas.restoreToCount(restoreCount);
+            } finally {
+                canvas.onPostDraw();
+
+                mDisplayList.end();
+
+                canvas.destroy();                
+            }
+        }
+
+        return mDisplayList;
+    }
+
+    /**
      * <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p>
      * 
      * @return A non-scaled bitmap representing this view or null if cache is disabled.
@@ -6428,8 +7499,7 @@
         if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
             buildDrawingCache(autoScale);
         }
-        return autoScale ? (mDrawingCache == null ? null : mDrawingCache.get()) :
-                (mUnscaledDrawingCache == null ? null : mUnscaledDrawingCache.get());
+        return autoScale ? mDrawingCache : mUnscaledDrawingCache;
     }
 
     /**
@@ -6444,15 +7514,17 @@
      */
     public void destroyDrawingCache() {
         if (mDrawingCache != null) {
-            final Bitmap bitmap = mDrawingCache.get();
-            if (bitmap != null) bitmap.recycle();
+            mDrawingCache.recycle();
             mDrawingCache = null;
         }
         if (mUnscaledDrawingCache != null) {
-            final Bitmap bitmap = mUnscaledDrawingCache.get();
-            if (bitmap != null) bitmap.recycle();
+            mUnscaledDrawingCache.recycle();
             mUnscaledDrawingCache = null;
         }
+        if (mDisplayList != null) {
+            mDisplayList.destroy();
+            mDisplayList = null;
+        }
     }
 
     /**
@@ -6511,15 +7583,11 @@
      */
     public void buildDrawingCache(boolean autoScale) {
         if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || (autoScale ?
-                (mDrawingCache == null || mDrawingCache.get() == null) :
-                (mUnscaledDrawingCache == null || mUnscaledDrawingCache.get() == null))) {
+                mDrawingCache == null : mUnscaledDrawingCache == null)) {
 
             if (ViewDebug.TRACE_HIERARCHY) {
                 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE);
             }
-            if (Config.DEBUG && ViewDebug.profileDrawing) {
-                EventLog.writeEvent(60002, hashCode());
-            }
 
             int width = mRight - mLeft;
             int height = mBottom - mTop;
@@ -6545,8 +7613,7 @@
             }
 
             boolean clear = true;
-            Bitmap bitmap = autoScale ? (mDrawingCache == null ? null : mDrawingCache.get()) :
-                    (mUnscaledDrawingCache == null ? null : mUnscaledDrawingCache.get());
+            Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
 
             if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
                 Bitmap.Config quality;
@@ -6578,9 +7645,9 @@
                     bitmap = Bitmap.createBitmap(width, height, quality);
                     bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
                     if (autoScale) {
-                        mDrawingCache = new SoftReference<Bitmap>(bitmap);
+                        mDrawingCache = bitmap;
                     } else {
-                        mUnscaledDrawingCache = new SoftReference<Bitmap>(bitmap);
+                        mUnscaledDrawingCache = bitmap;
                     }
                     if (opaque && translucentWindow) bitmap.setHasAlpha(false);
                 } catch (OutOfMemoryError e) {
@@ -6819,6 +7886,25 @@
     }
 
     /**
+     * <p>Indicates whether this view is attached to an hardware accelerated
+     * window or not.</p>
+     * 
+     * <p>Even if this method returns true, it does not mean that every call
+     * to {@link #draw(android.graphics.Canvas)} will be made with an hardware
+     * accelerated {@link android.graphics.Canvas}. For instance, if this view
+     * is drawn onto an offscren {@link android.graphics.Bitmap} and its
+     * window is hardware accelerated,
+     * {@link android.graphics.Canvas#isHardwareAccelerated()} will likely
+     * return false, and this method will return true.</p>
+     * 
+     * @return True if the view is attached to a window and the window is
+     *         hardware accelerated; false in any other case.
+     */
+    public boolean isHardwareAccelerated() {
+        return mAttachInfo != null && mAttachInfo.mHardwareAccelerated;
+    }
+    
+    /**
      * Manually render this view (and all of its children) to the given Canvas.
      * The view must have already done a full layout before this function is
      * called.  When implementing a view, do not override this method; instead,
@@ -6943,16 +8029,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();
@@ -7159,14 +8245,19 @@
      *
      * Derived classes with children should override
      * onLayout. In that method, they should
-     * call layout on each of their their children.
+     * call layout on each of their children.
      *
      * @param l Left position, relative to parent
      * @param t Top position, relative to parent
      * @param r Right position, relative to parent
      * @param b Bottom position, relative to parent
      */
+    @SuppressWarnings({"unchecked"})
     public final void layout(int l, int t, int r, int b) {
+        int oldL = mLeft;
+        int oldT = mTop;
+        int oldB = mBottom;
+        int oldR = mRight;
         boolean changed = setFrame(l, t, r, b);
         if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
             if (ViewDebug.TRACE_HIERARCHY) {
@@ -7175,6 +8266,15 @@
 
             onLayout(changed, l, t, r, b);
             mPrivateFlags &= ~LAYOUT_REQUIRED;
+
+            if (mOnLayoutChangeListeners != null) {
+                ArrayList<OnLayoutChangeListener> listenersCopy =
+                        (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
+                int numListeners = listenersCopy.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    listenersCopy.get(i).onLayoutChange(this, l, r, t, b, oldL, oldT, oldR, oldB);
+                }
+            }
         }
         mPrivateFlags &= ~FORCE_LAYOUT;
     }
@@ -7185,7 +8285,7 @@
      *
      * Derived classes with children should override
      * this method and call layout on each of
-     * their their children.
+     * their children.
      * @param changed This is a new size or position for this view
      * @param left Left position, relative to parent
      * @param top Top position, relative to parent
@@ -7439,18 +8539,13 @@
 
         int privateFlags = mPrivateFlags;
 
-        int viewStateIndex = (((privateFlags & PRESSED) != 0) ? 1 : 0);
-
-        viewStateIndex = (viewStateIndex << 1)
-                + (((mViewFlags & ENABLED_MASK) == ENABLED) ? 1 : 0);
-
-        viewStateIndex = (viewStateIndex << 1) + (isFocused() ? 1 : 0);
-
-        viewStateIndex = (viewStateIndex << 1)
-                + (((privateFlags & SELECTED) != 0) ? 1 : 0);
-
-        final boolean hasWindowFocus = hasWindowFocus();
-        viewStateIndex = (viewStateIndex << 1) + (hasWindowFocus ? 1 : 0);
+        int viewStateIndex = 0;
+        if ((privateFlags & PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
+        if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED;
+        if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED;
+        if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
+        if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
+        if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
 
         drawableState = VIEW_STATE_SETS[viewStateIndex];
 
@@ -7462,7 +8557,7 @@
                     + " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
                     + " fo=" + hasFocus()
                     + " sl=" + ((privateFlags & SELECTED) != 0)
-                    + " wf=" + hasWindowFocus
+                    + " wf=" + hasWindowFocus()
                     + ": " + Arrays.toString(drawableState));
         }
 
@@ -7776,6 +8871,48 @@
     }
 
     /**
+     * Changes the activated state of this view. A view can be activated or not.
+     * Note that activation is not the same as selection.  Selection is
+     * a transient property, representing the view (hierarchy) the user is
+     * currently interacting with.  Activation is a longer-term state that the
+     * user can move views in and out of.  For example, in a list view with
+     * single or multiple selection enabled, the views in the current selection
+     * set are activated.  (Um, yeah, we are deeply sorry about the terminology
+     * here.)  The activated state is propagated down to children of the view it
+     * is set on.
+     *
+     * @param activated true if the view must be activated, false otherwise
+     */
+    public void setActivated(boolean activated) {
+        if (((mPrivateFlags & ACTIVATED) != 0) != activated) {
+            mPrivateFlags = (mPrivateFlags & ~ACTIVATED) | (activated ? ACTIVATED : 0);
+            invalidate();
+            refreshDrawableState();
+            dispatchSetActivated(activated);
+        }
+    }
+
+    /**
+     * Dispatch setActivated to all of this View's children.
+     *
+     * @see #setActivated(boolean)
+     *
+     * @param activated The new activated state
+     */
+    protected void dispatchSetActivated(boolean activated) {
+    }
+
+    /**
+     * Indicates the activation state of this view.
+     *
+     * @return true if the view is activated, false otherwise
+     */
+    @ViewDebug.ExportedProperty
+    public boolean isActivated() {
+        return (mPrivateFlags & ACTIVATED) != 0;
+    }
+
+    /**
      * Returns the ViewTreeObserver for this view's hierarchy. The view tree
      * observer can be used to get notifications when global events, like
      * layout, happen.
@@ -8691,13 +9828,216 @@
         if (mAttachInfo == null) {
             return false;
         }
-        if ((flags&HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
+        //noinspection SimplifiableIfStatement
+        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);
+    }
+
+    /**
+     * !!! TODO: real docs
+     *
+     * The base class implementation makes the thumbnail the same size and appearance
+     * as the view itself, and positions it with its center at the touch point.
+     */
+    public static class DragThumbnailBuilder {
+        private final WeakReference<View> mView;
+
+        /**
+         * Construct a thumbnail builder object for use with the given view.
+         * @param view
+         */
+        public DragThumbnailBuilder(View view) {
+            mView = new WeakReference<View>(view);
+        }
+
+        /**
+         * Provide the draggable-thumbnail metrics for the operation: the dimensions of
+         * the thumbnail image itself, and the point within that thumbnail that should
+         * be centered under the touch location while dragging.
+         * <p>
+         * The default implementation sets the dimensions of the thumbnail to be the
+         * same as the dimensions of the View itself and centers the thumbnail under
+         * the touch point.
+         *
+         * @param thumbnailSize The application should set the {@code x} member of this
+         *        parameter to the desired thumbnail width, and the {@code y} member to
+         *        the desired height.
+         * @param thumbnailTouchPoint The application should set this point to be the
+         *        location within the thumbnail that should track directly underneath
+         *        the touch point on the screen during a drag.
+         */
+        public void onProvideThumbnailMetrics(Point thumbnailSize, Point thumbnailTouchPoint) {
+            final View view = mView.get();
+            if (view != null) {
+                thumbnailSize.set(view.getWidth(), view.getHeight());
+                thumbnailTouchPoint.set(thumbnailSize.x / 2, thumbnailSize.y / 2);
+            } else {
+                Log.e(View.VIEW_LOG_TAG, "Asked for drag thumb metrics but no view");
+            }
+        }
+
+        /**
+         * Draw the thumbnail image for the upcoming drag.  The thumbnail canvas was
+         * created with the dimensions supplied by the onProvideThumbnailMetrics()
+         * callback.
+         *
+         * @param canvas
+         */
+        public void onDrawThumbnail(Canvas canvas) {
+            final View view = mView.get();
+            if (view != null) {
+                view.draw(canvas);
+            } else {
+                Log.e(View.VIEW_LOG_TAG, "Asked to draw drag thumb but no view");
+            }
+        }
+    }
+
+    /**
+     * Drag and drop.  App calls startDrag(), then callbacks to onMeasureDragThumbnail()
+     * and onDrawDragThumbnail() happen, then the drag operation is handed over to the
+     * OS.
+     * !!! TODO: real docs
+     * @hide
+     */
+    public final boolean startDrag(ClipData data, DragThumbnailBuilder thumbBuilder,
+            boolean myWindowOnly) {
+        if (ViewDebug.DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "startDrag: data=" + data + " local=" + myWindowOnly);
+        }
+        boolean okay = false;
+
+        Point thumbSize = new Point();
+        Point thumbTouchPoint = new Point();
+        thumbBuilder.onProvideThumbnailMetrics(thumbSize, thumbTouchPoint);
+
+        if ((thumbSize.x < 0) || (thumbSize.y < 0) ||
+                (thumbTouchPoint.x < 0) || (thumbTouchPoint.y < 0)) {
+            throw new IllegalStateException("Drag thumb dimensions must not be negative");
+        }
+
+        Surface surface = new Surface();
+        try {
+            IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+                    myWindowOnly, mThumbnailWidth, mThumbnailHeight, surface);
+            if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+                    + " surface=" + surface);
+            if (token != null) {
+                Canvas canvas = surface.lockCanvas(null);
+                try {
+                    thumbBuilder.onDrawThumbnail(canvas);
+                } finally {
+                    surface.unlockCanvasAndPost(canvas);
+                }
+
+                // repurpose 'thumbSize' for the last touch point
+                getViewRoot().getLastTouchPoint(thumbSize);
+
+                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
+                        (float) thumbSize.x, (float) thumbSize.y,
+                        (float) thumbTouchPoint.x, (float) thumbTouchPoint.y, data);
+                if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
+            }
+        } catch (Exception e) {
+            Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
+            surface.destroy();
+        }
+
+        return okay;
+    }
+
+    private void measureThumbnail() {
+        mPrivateFlags &= ~MEASURED_DIMENSION_SET;
+
+        onMeasureDragThumbnail();
+
+        // flag not set, setDragThumbnailDimension() was not invoked, we raise
+        // an exception to warn the developer
+        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
+            throw new IllegalStateException("onMeasureDragThumbnail() did not set the"
+                    + " measured dimension by calling setDragThumbnailDimension()");
+        }
+
+        if (ViewDebug.DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "Drag thumb measured: w=" + mThumbnailWidth
+                    + " h=" + mThumbnailHeight);
+        }
+    }
+
+    /**
+     * The View must call this method from onMeasureDragThumbnail() in order to
+     * specify the dimensions of the drag thumbnail image.
+     *
+     * @param width The desired thumbnail width.
+     * @param height The desired thumbnail height.
+     */
+    protected final void setDragThumbnailDimension(int width, int height) {
+        mPrivateFlags |= MEASURED_DIMENSION_SET;
+        mThumbnailWidth = width;
+        mThumbnailHeight = height;
+    }
+
+    /**
+     * The default implementation specifies a drag thumbnail that matches the
+     * View's current size and appearance.
+     */
+    protected void onMeasureDragThumbnail() {
+        setDragThumbnailDimension(getWidth(), getHeight());
+    }
+
+    /**
+     * The default implementation just draws the current View appearance as the thumbnail
+     * @param canvas
+     */
+    protected void onDrawDragThumbnail(Canvas canvas) {
+        draw(canvas);
+    }
+
+    /**
+     * Drag-and-drop event dispatch.  The event.getAction() verb is one of the DragEvent
+     * constants DRAG_STARTED_EVENT, DRAG_EVENT, DROP_EVENT, and DRAG_ENDED_EVENT.
+     *
+     * For DRAG_STARTED_EVENT, event.getClipDescription() describes the content
+     * being dragged.  onDragEvent() should return 'true' if the view can handle
+     * a drop of that content.  A view that returns 'false' here will receive no
+     * further calls to onDragEvent() about the drag/drop operation.
+     *
+     * For DRAG_ENTERED, event.getClipDescription() describes the content being
+     * dragged.  This will be the same content description passed in the
+     * DRAG_STARTED_EVENT invocation.
+     *
+     * For DRAG_EXITED, event.getClipDescription() describes the content being
+     * dragged.  This will be the same content description passed in the
+     * DRAG_STARTED_EVENT invocation.  The view should return to its approriate
+     * drag-acceptance visual state.
+     *
+     * For DRAG_LOCATION_EVENT, event.getX() and event.getY() give the location in View
+     * coordinates of the current drag point.  The view must return 'true' if it
+     * can accept a drop of the current drag content, false otherwise.
+     *
+     * For DROP_EVENT, event.getX() and event.getY() give the location of the drop
+     * within the view; also, event.getClipData() returns the full data payload
+     * being dropped.  The view should return 'true' if it consumed the dropped
+     * content, 'false' if it did not.
+     *
+     * For DRAG_ENDED_EVENT, the 'event' argument may be null.  The view should return
+     * to its normal visual state.
+     */
+    protected boolean onDragEvent(DragEvent event) {
+        return false;
+    }
+
+    /**
+     * Views typically don't need to override dispatchDragEvent(); it just calls
+     * onDragEvent(what, event) and passes the result up appropriately.
+     *
+     */
+    public boolean dispatchDragEvent(DragEvent event) {
+        return onDragEvent(event);
     }
 
     /**
@@ -8768,31 +10108,6 @@
                 ViewConfiguration.getLongPressTimeout() - delayOffset);
     }
 
-    private static int[] stateSetUnion(final int[] stateSet1,
-                                       final int[] stateSet2) {
-        final int stateSet1Length = stateSet1.length;
-        final int stateSet2Length = stateSet2.length;
-        final int[] newSet = new int[stateSet1Length + stateSet2Length];
-        int k = 0;
-        int i = 0;
-        int j = 0;
-        // This is a merge of the two input state sets and assumes that the
-        // input sets are sorted by the order imposed by ViewDrawableStates.
-        for (int viewState : R.styleable.ViewDrawableStates) {
-            if (i < stateSet1Length && stateSet1[i] == viewState) {
-                newSet[k++] = viewState;
-                i++;
-            } else if (j < stateSet2Length && stateSet2[j] == viewState) {
-                newSet[k++] = viewState;
-                j++;
-            }
-            if (k > 1) {
-                assert(newSet[k - 1] > newSet[k - 2]);
-            }
-        }
-        return newSet;
-    }
-
     /**
      * Inflate a view from an XML resource.  This convenience method wraps the {@link
      * LayoutInflater} class, which provides a full range of options for view inflation.
@@ -8809,128 +10124,6 @@
     }
 
     /**
-     * Scroll the view with standard behavior for scrolling beyond the normal
-     * content boundaries. Views that call this method should override
-     * {@link #onOverscrolled(int, int, boolean, boolean)} to respond to the
-     * results of an overscroll operation.
-     *
-     * Views can use this method to handle any touch or fling-based scrolling.
-     *
-     * @param deltaX Change in X in pixels
-     * @param deltaY Change in Y in pixels
-     * @param scrollX Current X scroll value in pixels before applying deltaX
-     * @param scrollY Current Y scroll value in pixels before applying deltaY
-     * @param scrollRangeX Maximum content scroll range along the X axis
-     * @param scrollRangeY Maximum content scroll range along the Y axis
-     * @param maxOverscrollX Number of pixels to overscroll by in either direction
-     *          along the X axis.
-     * @param maxOverscrollY Number of pixels to overscroll by in either direction
-     *          along the Y axis.
-     * @param isTouchEvent true if this scroll operation is the result of a touch event.
-     * @return true if scrolling was clamped to an overscroll boundary along either
-     *          axis, false otherwise.
-     */
-    protected boolean overscrollBy(int deltaX, int deltaY,
-            int scrollX, int scrollY,
-            int scrollRangeX, int scrollRangeY,
-            int maxOverscrollX, int maxOverscrollY,
-            boolean isTouchEvent) {
-        final int overscrollMode = mOverscrollMode;
-        final boolean canScrollHorizontal =
-                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
-        final boolean canScrollVertical =
-                computeVerticalScrollRange() > computeVerticalScrollExtent();
-        final boolean overscrollHorizontal = overscrollMode == OVERSCROLL_ALWAYS ||
-                (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
-        final boolean overscrollVertical = overscrollMode == OVERSCROLL_ALWAYS ||
-                (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
-
-        int newScrollX = scrollX + deltaX;
-        if (!overscrollHorizontal) {
-            maxOverscrollX = 0;
-        }
-
-        int newScrollY = scrollY + deltaY;
-        if (!overscrollVertical) {
-            maxOverscrollY = 0;
-        }
-
-        // Clamp values if at the limits and record
-        final int left = -maxOverscrollX;
-        final int right = maxOverscrollX + scrollRangeX;
-        final int top = -maxOverscrollY;
-        final int bottom = maxOverscrollY + scrollRangeY;
-
-        boolean clampedX = false;
-        if (newScrollX > right) {
-            newScrollX = right;
-            clampedX = true;
-        } else if (newScrollX < left) {
-            newScrollX = left;
-            clampedX = true;
-        }
-
-        boolean clampedY = false;
-        if (newScrollY > bottom) {
-            newScrollY = bottom;
-            clampedY = true;
-        } else if (newScrollY < top) {
-            newScrollY = top;
-            clampedY = true;
-        }
-
-        onOverscrolled(newScrollX, newScrollY, clampedX, clampedY);
-
-        return clampedX || clampedY;
-    }
-
-    /**
-     * Called by {@link #overscrollBy(int, int, int, int, int, int, int, int, boolean)} to
-     * respond to the results of an overscroll operation.
-     *
-     * @param scrollX New X scroll value in pixels
-     * @param scrollY New Y scroll value in pixels
-     * @param clampedX True if scrollX was clamped to an overscroll boundary
-     * @param clampedY True if scrollY was clamped to an overscroll boundary
-     */
-    protected void onOverscrolled(int scrollX, int scrollY,
-            boolean clampedX, boolean clampedY) {
-        // Intentionally empty.
-    }
-
-    /**
-     * Returns the overscroll mode for this view. The result will be
-     * one of {@link #OVERSCROLL_ALWAYS} (default), {@link #OVERSCROLL_IF_CONTENT_SCROLLS}
-     * (allow overscrolling only if the view content is larger than the container),
-     * or {@link #OVERSCROLL_NEVER}.
-     *
-     * @return This view's overscroll mode.
-     */
-    public int getOverscrollMode() {
-        return mOverscrollMode;
-    }
-
-    /**
-     * Set the overscroll mode for this view. Valid overscroll modes are
-     * {@link #OVERSCROLL_ALWAYS} (default), {@link #OVERSCROLL_IF_CONTENT_SCROLLS}
-     * (allow overscrolling only if the view content is larger than the container),
-     * or {@link #OVERSCROLL_NEVER}.
-     *
-     * Setting the overscroll mode of a view will have an effect only if the
-     * view is capable of scrolling.
-     *
-     * @param overscrollMode The new overscroll mode for this view.
-     */
-    public void setOverscrollMode(int overscrollMode) {
-        if (overscrollMode != OVERSCROLL_ALWAYS &&
-                overscrollMode != OVERSCROLL_IF_CONTENT_SCROLLS &&
-                overscrollMode != OVERSCROLL_NEVER) {
-            throw new IllegalArgumentException("Invalid overscroll mode " + overscrollMode);
-        }
-        mOverscrollMode = overscrollMode;
-    }
-
-    /**
      * A MeasureSpec encapsulates the layout requirements passed from parent to child.
      * Each MeasureSpec represents a requirement for either the width or the height.
      * A MeasureSpec is comprised of a size and a mode. There are three possible
@@ -9295,6 +10488,9 @@
         IBinder mPanelParentWindowToken;
         Surface mSurface;
 
+        boolean mHardwareAccelerated;
+        HardwareRenderer mHardwareRenderer;
+        
         /**
          * Scale factor used by the compatibility mode
          */
@@ -9412,6 +10608,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.
@@ -9448,6 +10651,11 @@
         final Rect mTmpInvalRect = new Rect();
 
         /**
+         * Temporary for use in computing hit areas with transformed views
+         */
+        final RectF mTmpTransformRect = new RectF();
+
+        /**
          * Temporary list for use in collecting focusable descendents of a view.
          */
         final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24);
@@ -9507,8 +10715,8 @@
 
         public final Interpolator scrollBarInterpolator = new Interpolator(1, 2);
 
-        private final float[] mOpaque = {255.0f};
-        private final float[] mTransparent = {0.0f};
+        private final float[] mOpaque = { 255.0f };
+        private final float[] mTransparent = { 0.0f };
         
         /**
          * When fading should start. This time moves into the future every time
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 924c9d4..acdfc28 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -140,16 +140,6 @@
      */
     private static float SCROLL_FRICTION = 0.015f;
 
-    /**
-     * Max distance to overscroll for edge effects
-     */
-    private static final int OVERSCROLL_DISTANCE = 0;
-
-    /**
-     * Max distance to overfling for edge effects
-     */
-    private static final int OVERFLING_DISTANCE = 4;
-
     private final int mEdgeSlop;
     private final int mFadingEdgeLength;
     private final int mMinimumFlingVelocity;
@@ -160,8 +150,6 @@
     private final int mDoubleTapSlop;
     private final int mWindowTouchSlop;
     private final int mMaximumDrawingCacheSize;
-    private final int mOverscrollDistance;
-    private final int mOverflingDistance;
 
     private static final SparseArray<ViewConfiguration> sConfigurations =
             new SparseArray<ViewConfiguration>(2);
@@ -182,8 +170,6 @@
         mWindowTouchSlop = WINDOW_TOUCH_SLOP;
         //noinspection deprecation
         mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
-        mOverscrollDistance = OVERSCROLL_DISTANCE;
-        mOverflingDistance = OVERFLING_DISTANCE;
     }
 
     /**
@@ -212,9 +198,6 @@
 
         // Size of the screen in bytes, in ARGB_8888 format
         mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels;
-
-        mOverscrollDistance = (int) (density * OVERSCROLL_DISTANCE + 0.5f);
-        mOverflingDistance = (int) (density * OVERFLING_DISTANCE + 0.5f);
     }
 
     /**
@@ -472,20 +455,6 @@
     }
 
     /**
-     * @return The maximum distance a View should overscroll by when showing edge effects.
-     */
-    public int getScaledOverscrollDistance() {
-        return mOverscrollDistance;
-    }
-
-    /**
-     * @return The maximum distance a View should overfling by when showing edge effects.
-     */
-    public int getScaledOverflingDistance() {
-        return mOverflingDistance;
-    }
-
-    /**
      * The amount of time that the zoom controls should be
      * displayed on the screen expressed in milliseconds.
      * 
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 7b6991f..11e68c5 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -118,24 +118,27 @@
      *
      * @hide
      */
-    @Debug.DebugProperty
-    public static boolean profileDrawing = false;
+    public static final boolean DEBUG_PROFILE_DRAWING = false;
 
     /**
      * Profiles layout times in the events log.
      *
      * @hide
      */
-    @Debug.DebugProperty
-    public static boolean profileLayout = false;
+    public static final boolean DEBUG_PROFILE_LAYOUT = false;
 
     /**
      * Profiles real fps (times between draws) and displays the result.
      *
      * @hide
      */
-    @Debug.DebugProperty
-    public static boolean showFps = false;
+    public static final boolean DEBUG_SHOW_FPS = false;
+
+    /**
+     * Enables detailed logging of drag/drop operations.
+     * @hide
+     */
+    public static final boolean DEBUG_DRAG = true;
 
     /**
      * <p>Enables or disables views consistency check. Even when this property is enabled,
@@ -427,7 +430,7 @@
      * @hide
      */
     public static long getViewInstanceCount() {
-        return View.sInstanceCount;
+        return Debug.countInstancesOfClass(View.class);
     }
 
     /**
@@ -438,7 +441,7 @@
      * @hide
      */
     public static long getViewRootInstanceCount() {
-        return ViewRoot.getInstanceCount();
+        return Debug.countInstancesOfClass(ViewRoot.class);
     }
 
     /**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index b9864ba..5ebc981 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.animation.LayoutTransition;
 import com.android.internal.R;
 
 import android.content.Context;
@@ -23,15 +24,15 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.util.AttributeSet;
-import android.util.Config;
-import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.accessibility.AccessibilityEvent;
@@ -63,8 +64,10 @@
  * @attr ref android.R.styleable#ViewGroup_alwaysDrawnWithCache
  * @attr ref android.R.styleable#ViewGroup_addStatesFromChildren
  * @attr ref android.R.styleable#ViewGroup_descendantFocusability
+ * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
  */
 public abstract class ViewGroup extends View implements ViewParent, ViewManager {
+
     private static final boolean DBG = false;
 
     /**
@@ -86,18 +89,44 @@
     // 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;
 
-    // Target of Motion events
-    private View mMotionTarget;
-    private final Rect mTempRect = new Rect();
+    /**
+     * A Transformation used to calculate a correct
+     * invalidation area when the application is autoscaled.
+     */
+    private Transformation mInvalidationTransformation;
+
+    // View currently under an ongoing drag
+    private View mCurrentDragView;
+
+    // Does this group have a child that can accept the current drag payload?
+    private boolean mChildAcceptsDrag;
+
+    // Used during drag dispatch
+    private final PointF mLocalPoint = new PointF();
 
     // Layout animation
     private LayoutAnimationController mLayoutAnimationController;
     private Animation.AnimationListener mAnimationListener;
 
+    // First touch target in the linked list of touch targets.
+    private TouchTarget mFirstTouchTarget;
+
+    // Temporary arrays for splitting pointers.
+    private int[] mTmpPointerIndexMap;
+    private int[] mTmpPointerIds;
+    private MotionEvent.PointerCoords[] mTmpPointerCoords;
+
     /**
      * Internal flags.
      *
@@ -228,6 +257,11 @@
     protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
 
     /**
+     * When set, this ViewGroup will split MotionEvents to multiple child Views when appropriate.
+     */
+    private static final int FLAG_SPLIT_MOTION_EVENTS = 0x200000;
+
+    /**
      * Indicates which types of drawing caches are to be kept in memory.
      * This field should be made private, so it is hidden from the SDK.
      * {@hide}
@@ -277,6 +311,18 @@
     // Used to draw cached views
     private final Paint mCachePaint = new Paint();
 
+    // Used to animate add/remove changes in layout
+    private LayoutTransition mTransition;
+
+    // The set of views that are currently being transitioned. This list is used to track views
+    // being removed that should not actually be removed from the parent yet because they are
+    // being animated.
+    private ArrayList<View> mTransitioningViews;
+
+    // List of children changing visibility. This is used to potentially keep rendering
+    // views during a transition when they otherwise would have become gone/invisible
+    private ArrayList<View> mVisibilityChangingChildren;
+
     public ViewGroup(Context context) {
         super(context);
         initViewGroup();
@@ -348,6 +394,15 @@
                 case R.styleable.ViewGroup_descendantFocusability:
                     setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]);
                     break;
+                case R.styleable.ViewGroup_splitMotionEvents:
+                    setMotionEventSplittingEnabled(a.getBoolean(attr, false));
+                    break;
+                case R.styleable.ViewGroup_animateLayoutChanges:
+                    boolean animateLayoutChanges = a.getBoolean(attr, false);
+                    if (animateLayoutChanges) {
+                        setLayoutTransition(new LayoutTransition());
+                    }
+                    break;
             }
         }
 
@@ -461,6 +516,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.
      *
@@ -696,6 +758,32 @@
     }
 
     /**
+     * @hide
+     * @param child
+     * @param visibility
+     */
+    void onChildVisibilityChanged(View child, int visibility) {
+        if (mTransition != null) {
+            if (visibility == VISIBLE) {
+                mTransition.showChild(this, child);
+            } else {
+                mTransition.hideChild(this, child);
+            }
+            if (visibility != VISIBLE) {
+                // Only track this on disappearing views - appearing views are already visible
+                // and don't need special handling during drawChild()
+                if (mVisibilityChangingChildren == null) {
+                    mVisibilityChangingChildren = new ArrayList<View>();
+                }
+                mVisibilityChangingChildren.add(child);
+                if (mTransitioningViews != null && mTransitioningViews.contains(child)) {
+                    addDisappearingView(child);
+                }
+            }
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -767,6 +855,125 @@
 
     /**
      * {@inheritDoc}
+     *
+     * !!! TODO: write real docs
+     */
+    @Override
+    public boolean dispatchDragEvent(DragEvent event) {
+        boolean retval = false;
+        final float tx = event.mX;
+        final float ty = event.mY;
+
+        ViewRoot root = getViewRoot();
+
+        // Dispatch down the view hierarchy
+        switch (event.mAction) {
+        case DragEvent.ACTION_DRAG_STARTED: {
+            // clear state to recalculate which views we drag over
+            root.setDragFocus(event, null);
+
+            // Now dispatch down to our children, caching the responses
+            mChildAcceptsDrag = false;
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                if (child.getVisibility() == VISIBLE) {
+                    final boolean handled = children[i].dispatchDragEvent(event);
+                    children[i].mCanAcceptDrop = handled;
+                    if (handled) {
+                        mChildAcceptsDrag = true;
+                    }
+                }
+            }
+
+            // Return HANDLED if one of our children can accept the drag
+            if (mChildAcceptsDrag) {
+                retval = true;
+            }
+        } break;
+
+        case DragEvent.ACTION_DRAG_ENDED: {
+            // Notify all of our children that the drag is over
+            final int count = mChildrenCount;
+            final View[] children = mChildren;
+            for (int i = 0; i < count; i++) {
+                final View child = children[i];
+                if (child.getVisibility() == VISIBLE) {
+                    child.dispatchDragEvent(event);
+                }
+            }
+            // We consider drag-ended to have been handled if one of our children
+            // had offered to handle the drag.
+            if (mChildAcceptsDrag) {
+                retval = true;
+            }
+        } break;
+
+        case DragEvent.ACTION_DRAG_LOCATION: {
+            // Find the [possibly new] drag target
+            final View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
+
+            // If we've changed apparent drag target, tell the view root which view
+            // we're over now.  This will in turn send out DRAG_ENTERED / DRAG_EXITED
+            // notifications as appropriate.
+            if (mCurrentDragView != target) {
+                root.setDragFocus(event, target);
+                mCurrentDragView = target;
+            }
+
+            // Dispatch the actual drag location notice, localized into its coordinates
+            if (target != null) {
+                event.mX = mLocalPoint.x;
+                event.mY = mLocalPoint.y;
+
+                retval = target.dispatchDragEvent(event);
+
+                event.mX = tx;
+                event.mY = ty;
+            }
+        } break;
+
+        case DragEvent.ACTION_DROP: {
+            if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, "Drop event: " + event);
+            View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
+            if (target != null) {
+                event.mX = mLocalPoint.x;
+                event.mY = mLocalPoint.y;
+                retval = target.dispatchDragEvent(event);
+                event.mX = tx;
+                event.mY = ty;
+            }
+        } break;
+        }
+
+        // If none of our children could handle the event, try here
+        if (!retval) {
+            retval = onDragEvent(event);
+        }
+        return retval;
+    }
+
+    // Find the frontmost child view that lies under the given point, and calculate
+    // the position within its own local coordinate system.
+    View findFrontmostDroppableChildAt(float x, float y, PointF outLocalPoint) {
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = count - 1; i >= 0; i--) {
+            final View child = children[i];
+            if (!child.mCanAcceptDrop) {
+                continue;
+            }
+
+            if (isTransformedTouchPointInView(x, y, child, outLocalPoint)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
      */
     @Override
     public boolean dispatchKeyEventPreIme(KeyEvent event) {
@@ -827,119 +1034,523 @@
         }
 
         final int action = ev.getAction();
-        final float xf = ev.getX();
-        final float yf = ev.getY();
-        final float scrolledXFloat = xf + mScrollX;
-        final float scrolledYFloat = yf + mScrollY;
-        final Rect frame = mTempRect;
+        final int actionMasked = action & MotionEvent.ACTION_MASK;
 
-        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+        // Handle an initial down.
+        if (actionMasked == MotionEvent.ACTION_DOWN) {
+            // Throw away all previous state when starting a new touch gesture.
+            // The framework may have dropped the up or cancel event for the previous gesture
+            // due to an app switch, ANR, or some other state change.
+            cancelAndClearTouchTargets(ev);
+            resetTouchState();
+        }
 
-        if (action == MotionEvent.ACTION_DOWN) {
-            if (mMotionTarget != null) {
-                // this is weird, we got a pen down, but we thought it was
-                // already down!
-                // XXX: We should probably send an ACTION_UP to the current
-                // target.
-                mMotionTarget = null;
+        // Check for interception.
+        final boolean intercepted;
+        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
+            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+            if (!disallowIntercept) {
+                intercepted = onInterceptTouchEvent(ev);
+                ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
+            } else {
+                intercepted = false;
             }
-            // If we're disallowing intercept or if we're allowing and we didn't
-            // intercept
-            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
-                // reset this event's action (just to protect ourselves)
-                ev.setAction(MotionEvent.ACTION_DOWN);
-                // We know we want to dispatch the event down, find a child
-                // who can handle it, start with the front-most child.
-                final int scrolledXInt = (int) scrolledXFloat;
-                final int scrolledYInt = (int) scrolledYFloat;
-                final View[] children = mChildren;
-                final int count = mChildrenCount;
+        } else {
+            intercepted = true;
+        }
 
-                for (int i = count - 1; i >= 0; i--) {
-                    final View child = children[i];
-                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
-                            || child.getAnimation() != null) {
-                        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.
+        // Check for cancelation.
+        final boolean canceled = resetCancelNextUpFlag(this)
+                || actionMasked == MotionEvent.ACTION_CANCEL;
+
+        // Update list of touch targets for pointer down, if needed.
+        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+        TouchTarget newTouchTarget = null;
+        boolean alreadyDispatchedToNewTouchTarget = false;
+        if (!canceled && !intercepted) {
+            if (actionMasked == MotionEvent.ACTION_DOWN
+                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)) {
+                final int actionIndex = ev.getActionIndex(); // always 0 for down
+                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+                        : TouchTarget.ALL_POINTER_IDS;
+
+                // Clean up earlier touch targets for this pointer id in case they
+                // have become out of sync.
+                removePointersFromTouchTargets(idBitsToAssign);
+
+                final int childrenCount = mChildrenCount;
+                if (childrenCount != 0) {
+                    // Find a child that can receive the event.  Scan children from front to back.
+                    final View[] children = mChildren;
+                    final float x = ev.getX(actionIndex);
+                    final float y = ev.getY(actionIndex);
+
+                    for (int i = childrenCount - 1; i >= 0; i--) {
+                        final View child = children[i];
+                        if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE
+                                && child.getAnimation() == null) {
+                            // Skip invisible child unless it is animating.
+                            continue;
+                        }
+
+                        if (!isTransformedTouchPointInView(x, y, child, null)) {
+                            // New pointer is out of child's bounds.
+                            continue;
+                        }
+
+                        newTouchTarget = getTouchTarget(child);
+                        if (newTouchTarget != null) {
+                            // Child is already receiving touch within its bounds.
+                            // Give it the new pointer in addition to the ones it is handling.
+                            newTouchTarget.pointerIdBits |= idBitsToAssign;
+                            break;
+                        }
+
+                        resetCancelNextUpFlag(child);
+                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+                            // Child wants to receive touch within its bounds.
+                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
+                            alreadyDispatchedToNewTouchTarget = true;
+                            break;
                         }
                     }
                 }
+
+                if (newTouchTarget == null && mFirstTouchTarget != null) {
+                    // Did not find a child to receive the event.
+                    // Assign the pointer to the least recently added target.
+                    newTouchTarget = mFirstTouchTarget;
+                    while (newTouchTarget.next != null) {
+                        newTouchTarget = newTouchTarget.next;
+                    }
+                    newTouchTarget.pointerIdBits |= idBitsToAssign;
+                }
             }
         }
 
-        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
-                (action == MotionEvent.ACTION_CANCEL);
-
-        if (isUpOrCancel) {
-            // Note, we've already copied the previous state to our local
-            // variable, so this takes effect on the next event
-            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+        // Dispatch to touch targets.
+        boolean handled = false;
+        if (mFirstTouchTarget == null) {
+            // No touch targets so treat this as an ordinary view.
+            handled = dispatchTransformedTouchEvent(ev, canceled, null,
+                    TouchTarget.ALL_POINTER_IDS);
+        } else {
+            // Dispatch to touch targets, excluding the new touch target if we already
+            // dispatched to it.  Cancel touch targets if necessary.
+            TouchTarget predecessor = null;
+            TouchTarget target = mFirstTouchTarget;
+            while (target != null) {
+                final TouchTarget next = target.next;
+                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
+                    handled = true;
+                } else {
+                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
+                    if (dispatchTransformedTouchEvent(ev, cancelChild,
+                            target.child, target.pointerIdBits)) {
+                        handled = true;
+                    }
+                    if (cancelChild) {
+                        if (predecessor == null) {
+                            mFirstTouchTarget = next;
+                        } else {
+                            predecessor.next = next;
+                        }
+                        target.recycle();
+                        target = next;
+                        continue;
+                    }
+                }
+                predecessor = target;
+                target = next;
+            }
         }
 
-        // The event wasn't an ACTION_DOWN, dispatch it to our target if
-        // we have one.
-        final View target = mMotionTarget;
-        if (target == null) {
-            // We don't have a target, this means we're handling the
-            // event as a regular view.
-            ev.setLocation(xf, yf);
-            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
-                ev.setAction(MotionEvent.ACTION_CANCEL);
-                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-            }
-            return super.dispatchTouchEvent(ev);
+        // Update list of touch targets for pointer up or cancel, if needed.
+        if (canceled || actionMasked == MotionEvent.ACTION_UP) {
+            resetTouchState();
+        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+            final int actionIndex = ev.getActionIndex();
+            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+            removePointersFromTouchTargets(idBitsToRemove);
         }
 
-        // 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);
-            if (!target.dispatchTouchEvent(ev)) {
-                // target didn't handle ACTION_CANCEL. not much we can do
-                // but they should have.
-            }
-            // clear the target
-            mMotionTarget = null;
-            // Don't dispatch this event to our own view, because we already
-            // saw it when intercepting; we just want to give the following
-            // event to the normal onTouchEvent().
+        return handled;
+    }
+
+    /**
+     * Resets all touch state in preparation for a new cycle.
+     */
+    private void resetTouchState() {
+        clearTouchTargets();
+        resetCancelNextUpFlag(this);
+        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+    }
+
+    /**
+     * Resets the cancel next up flag.
+     * Returns true if the flag was previously set.
+     */
+    private boolean resetCancelNextUpFlag(View view) {
+        if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
+            view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
             return true;
         }
+        return false;
+    }
 
-        if (isUpOrCancel) {
-            mMotionTarget = null;
+    /**
+     * Clears all touch targets.
+     */
+    private void clearTouchTargets() {
+        TouchTarget target = mFirstTouchTarget;
+        if (target != null) {
+            do {
+                TouchTarget next = target.next;
+                target.recycle();
+                target = next;
+            } while (target != null);
+            mFirstTouchTarget = null;
+        }
+    }
+
+    /**
+     * Cancels and clears all touch targets.
+     */
+    private void cancelAndClearTouchTargets(MotionEvent event) {
+        if (mFirstTouchTarget != null) {
+            boolean syntheticEvent = false;
+            if (event == null) {
+                final long now = SystemClock.uptimeMillis();
+                event = MotionEvent.obtain(now, now,
+                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+                syntheticEvent = true;
+            }
+
+            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+                resetCancelNextUpFlag(target.child);
+                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
+            }
+            clearTouchTargets();
+
+            if (syntheticEvent) {
+                event.recycle();
+            }
+        }
+    }
+
+    /**
+     * Gets the touch target for specified child view.
+     * Returns null if not found.
+     */
+    private TouchTarget getTouchTarget(View child) {
+        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+            if (target.child == child) {
+                return target;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds a touch target for specified child to the beginning of the list.
+     * Assumes the target child is not already present.
+     */
+    private TouchTarget addTouchTarget(View child, int pointerIdBits) {
+        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
+        target.next = mFirstTouchTarget;
+        mFirstTouchTarget = target;
+        return target;
+    }
+
+    /**
+     * Removes the pointer ids from consideration.
+     */
+    private void removePointersFromTouchTargets(int pointerIdBits) {
+        TouchTarget predecessor = null;
+        TouchTarget target = mFirstTouchTarget;
+        while (target != null) {
+            final TouchTarget next = target.next;
+            if ((target.pointerIdBits & pointerIdBits) != 0) {
+                target.pointerIdBits &= ~pointerIdBits;
+                if (target.pointerIdBits == 0) {
+                    if (predecessor == null) {
+                        mFirstTouchTarget = next;
+                    } else {
+                        predecessor.next = next;
+                    }
+                    target.recycle();
+                    target = next;
+                    continue;
+                }
+            }
+            predecessor = target;
+            target = next;
+        }
+    }
+
+    /**
+     * Returns true if a child view contains the specified point when transformed
+     * into its coordinate space.
+     * Child must not be null.
+     */
+    private boolean isTransformedTouchPointInView(float x, float y, View child,
+            PointF outLocalPoint) {
+        float localX = x + mScrollX - child.mLeft;
+        float localY = y + mScrollY - child.mTop;
+        if (! child.hasIdentityMatrix() && mAttachInfo != null) {
+            final float[] localXY = mAttachInfo.mTmpTransformLocation;
+            localXY[0] = localX;
+            localXY[1] = localY;
+            child.getInverseMatrix().mapPoints(localXY);
+            localX = localXY[0];
+            localY = localXY[1];
+        }
+        final boolean isInView = child.pointInView(localX, localY);
+        if (isInView && outLocalPoint != null) {
+            outLocalPoint.set(localX, localY);
+        }
+        return isInView;
+    }
+
+    /**
+     * Transforms a motion event into the coordinate space of a particular child view,
+     * filters out irrelevant pointer ids, and overrides its action if necessary.
+     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
+     */
+    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
+            View child, int desiredPointerIdBits) {
+        final boolean handled;
+
+        // Canceling motions is a special case.  We don't need to perform any transformations
+        // or filtering.  The important part is the action, not the contents.
+        final int oldAction = event.getAction();
+        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
+            event.setAction(MotionEvent.ACTION_CANCEL);
+            if (child == null) {
+                handled = super.dispatchTouchEvent(event);
+            } else {
+                handled = child.dispatchTouchEvent(event);
+            }
+            event.setAction(oldAction);
+            return handled;
         }
 
-        // 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) {
-            ev.setAction(MotionEvent.ACTION_CANCEL);
-            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-            mMotionTarget = null;
+        // Calculate the number of pointers to deliver.
+        final int oldPointerCount = event.getPointerCount();
+        int newPointerCount = 0;
+        if (desiredPointerIdBits == TouchTarget.ALL_POINTER_IDS) {
+            newPointerCount = oldPointerCount;
+        } else {
+            for (int i = 0; i < oldPointerCount; i++) {
+                final int pointerId = event.getPointerId(i);
+                final int pointerIdBit = 1 << pointerId;
+                if ((pointerIdBit & desiredPointerIdBits) != 0) {
+                    newPointerCount += 1;
+                }
+            }
         }
 
-        return target.dispatchTouchEvent(ev);
+        // If for some reason we ended up in an inconsistent state where it looks like we
+        // might produce a motion event with no pointers in it, then drop the event.
+        if (newPointerCount == 0) {
+            return false;
+        }
+
+        // If the number of pointers is the same and we don't need to perform any fancy
+        // irreversible transformations, then we can reuse the motion event for this
+        // dispatch as long as we are careful to revert any changes we make.
+        final boolean reuse = newPointerCount == oldPointerCount
+                && (child == null || child.hasIdentityMatrix());
+        if (reuse) {
+            if (child == null) {
+                handled = super.dispatchTouchEvent(event);
+            } else {
+                final float offsetX = mScrollX - child.mLeft;
+                final float offsetY = mScrollY - child.mTop;
+                event.offsetLocation(offsetX, offsetY);
+
+                handled = child.dispatchTouchEvent(event);
+
+                event.offsetLocation(-offsetX, -offsetY);
+            }
+            return handled;
+        }
+
+        // Make a copy of the event.
+        // If the number of pointers is different, then we need to filter out irrelevant pointers
+        // as we make a copy of the motion event.
+        MotionEvent transformedEvent;
+        if (newPointerCount == oldPointerCount) {
+            transformedEvent = MotionEvent.obtain(event);
+        } else {
+            growTmpPointerArrays(newPointerCount);
+            final int[] newPointerIndexMap = mTmpPointerIndexMap;
+            final int[] newPointerIds = mTmpPointerIds;
+            final MotionEvent.PointerCoords[] newPointerCoords = mTmpPointerCoords;
+
+            int newPointerIndex = 0;
+            int oldPointerIndex = 0;
+            while (newPointerIndex < newPointerCount) {
+                final int pointerId = event.getPointerId(oldPointerIndex);
+                final int pointerIdBits = 1 << pointerId;
+                if ((pointerIdBits & desiredPointerIdBits) != 0) {
+                    newPointerIndexMap[newPointerIndex] = oldPointerIndex;
+                    newPointerIds[newPointerIndex] = pointerId;
+                    if (newPointerCoords[newPointerIndex] == null) {
+                        newPointerCoords[newPointerIndex] = new MotionEvent.PointerCoords();
+                    }
+
+                    newPointerIndex += 1;
+                }
+                oldPointerIndex += 1;
+            }
+
+            final int newAction;
+            if (cancel) {
+                newAction = MotionEvent.ACTION_CANCEL;
+            } else {
+                final int oldMaskedAction = oldAction & MotionEvent.ACTION_MASK;
+                if (oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN
+                        || oldMaskedAction == MotionEvent.ACTION_POINTER_UP) {
+                    final int changedPointerId = event.getPointerId(
+                            (oldAction & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                                    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+                    final int changedPointerIdBits = 1 << changedPointerId;
+                    if ((changedPointerIdBits & desiredPointerIdBits) != 0) {
+                        if (newPointerCount == 1) {
+                            // The first/last pointer went down/up.
+                            newAction = oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN
+                                    ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP;
+                        } else {
+                            // A secondary pointer went down/up.
+                            int newChangedPointerIndex = 0;
+                            while (newPointerIds[newChangedPointerIndex] != changedPointerId) {
+                                newChangedPointerIndex += 1;
+                            }
+                            newAction = oldMaskedAction | (newChangedPointerIndex
+                                    << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+                        }
+                    } else {
+                        // An unrelated pointer changed.
+                        newAction = MotionEvent.ACTION_MOVE;
+                    }
+                } else {
+                    // Simple up/down/cancel/move motion action.
+                    newAction = oldMaskedAction;
+                }
+            }
+
+            transformedEvent = null;
+            final int historySize = event.getHistorySize();
+            for (int historyIndex = 0; historyIndex <= historySize; historyIndex++) {
+                for (newPointerIndex = 0; newPointerIndex < newPointerCount; newPointerIndex++) {
+                    final MotionEvent.PointerCoords c = newPointerCoords[newPointerIndex];
+                    oldPointerIndex = newPointerIndexMap[newPointerIndex];
+                    if (historyIndex != historySize) {
+                        event.getHistoricalPointerCoords(oldPointerIndex, historyIndex, c);
+                    } else {
+                        event.getPointerCoords(oldPointerIndex, c);
+                    }
+                }
+
+                final long eventTime;
+                if (historyIndex != historySize) {
+                    eventTime = event.getHistoricalEventTime(historyIndex);
+                } else {
+                    eventTime = event.getEventTime();
+                }
+
+                if (transformedEvent == null) {
+                    transformedEvent = MotionEvent.obtain(
+                            event.getDownTime(), eventTime, newAction,
+                            newPointerCount, newPointerIds, newPointerCoords,
+                            event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
+                            event.getDeviceId(), event.getEdgeFlags(), event.getSource(),
+                            event.getFlags());
+                } else {
+                    transformedEvent.addBatch(eventTime, newPointerCoords, 0);
+                }
+            }
+        }
+
+        // Perform any necessary transformations and dispatch.
+        if (child == null) {
+            handled = super.dispatchTouchEvent(transformedEvent);
+        } else {
+            final float offsetX = mScrollX - child.mLeft;
+            final float offsetY = mScrollY - child.mTop;
+            transformedEvent.offsetLocation(offsetX, offsetY);
+            if (! child.hasIdentityMatrix()) {
+                transformedEvent.transform(child.getInverseMatrix());
+            }
+
+            handled = child.dispatchTouchEvent(transformedEvent);
+        }
+
+        // Done.
+        transformedEvent.recycle();
+        return handled;
+    }
+
+    /**
+     * Enlarge the temporary pointer arrays for splitting pointers.
+     * May discard contents (but keeps PointerCoords objects to avoid reallocating them).
+     */
+    private void growTmpPointerArrays(int desiredCapacity) {
+        final MotionEvent.PointerCoords[] oldTmpPointerCoords = mTmpPointerCoords;
+        int capacity;
+        if (oldTmpPointerCoords != null) {
+            capacity = oldTmpPointerCoords.length;
+            if (desiredCapacity <= capacity) {
+                return;
+            }
+        } else {
+            capacity = 4;
+        }
+
+        while (capacity < desiredCapacity) {
+            capacity *= 2;
+        }
+
+        mTmpPointerIndexMap = new int[capacity];
+        mTmpPointerIds = new int[capacity];
+        mTmpPointerCoords = new MotionEvent.PointerCoords[capacity];
+
+        if (oldTmpPointerCoords != null) {
+            System.arraycopy(oldTmpPointerCoords, 0, mTmpPointerCoords, 0,
+                    oldTmpPointerCoords.length);
+        }
+    }
+
+    /**
+     * Enable or disable the splitting of MotionEvents to multiple children during touch event
+     * dispatch. This behavior is disabled by default.
+     *
+     * <p>When this option is enabled MotionEvents may be split and dispatched to different child
+     * views depending on where each pointer initially went down. This allows for user interactions
+     * such as scrolling two panes of content independently, chording of buttons, and performing
+     * independent gestures on different pieces of content.
+     *
+     * @param split <code>true</code> to allow MotionEvents to be split and dispatched to multiple
+     *              child views. <code>false</code> to only allow one child view to be the target of
+     *              any MotionEvent received by this ViewGroup.
+     */
+    public void setMotionEventSplittingEnabled(boolean split) {
+        // TODO Applications really shouldn't change this setting mid-touch event,
+        // but perhaps this should handle that case and send ACTION_CANCELs to any child views
+        // with gestures in progress when this is changed.
+        if (split) {
+            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
+        } else {
+            mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
+        }
+    }
+
+    /**
+     * @return true if MotionEvents dispatched to this ViewGroup can be split to multiple children.
+     */
+    public boolean isMotionEventSplittingEnabled() {
+        return (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS;
     }
 
     /**
@@ -1142,19 +1753,12 @@
      */
     @Override
     void dispatchDetachedFromWindow() {
-        // If we still have a motion target, we are still in the process of
+        // If we still have a touch target, we are still in the process of
         // dispatching motion events to a child; we need to get rid of that
         // child to avoid dispatching events to it after the window is torn
         // down. To make sure we keep the child in a consistent state, we
         // first send it an ACTION_CANCEL motion event.
-        if (mMotionTarget != null) {
-            final long now = SystemClock.uptimeMillis();
-            final MotionEvent event = MotionEvent.obtain(now, now,
-                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
-            mMotionTarget.dispatchTouchEvent(event);
-            event.recycle();
-            mMotionTarget = null;
-        }
+        cancelAndClearTouchTargets(null);
 
         final int count = mChildrenCount;
         final View[] children = mChildren;
@@ -1187,7 +1791,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);
+            }
         }
     }
 
@@ -1212,7 +1819,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);
+            }
         }
     }
 
@@ -1482,22 +2092,24 @@
         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 ((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());
@@ -1505,10 +2117,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();
@@ -1525,7 +2144,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
@@ -1538,9 +2161,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();
@@ -1550,6 +2170,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;
@@ -1564,15 +2186,19 @@
         final int sx = child.mScrollX;
         final int sy = child.mScrollY;
 
-        boolean scalingRequired = false;
+        DisplayList displayList = null;
         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) {
-            cache = child.getDrawingCache(true);
-            if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired;
+        if (caching) {
+            if (!canvas.isHardwareAccelerated()) {
+                cache = child.getDrawingCache(true);
+            } else {
+                // TODO: bring back
+                // displayList = child.getDisplayList();
+            }
         }
 
-        final boolean hasNoCache = cache == null;
+        final boolean hasDisplayList = displayList != null && displayList.isReady();
+        final boolean hasNoCache = cache == null || hasDisplayList;
 
         final int restoreTo = canvas.save();
         if (hasNoCache) {
@@ -1586,40 +2212,60 @@
             }
         }
 
-        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)) {
+                        int layerFlags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
+                        if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
+                            layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG;
+                        }
+                        canvas.saveLayerAlpha(sx, sy, sx + cr - cl, sy + cb - ct, multipliedAlpha,
+                                layerFlags);
+                    } else {
+                        child.mPrivateFlags |= ALPHA_SET;
+                    }
                 }
             }
         } else if ((child.mPrivateFlags & ALPHA_SET) == ALPHA_SET) {
             child.onSetAlpha(255);
+            child.mPrivateFlags &= ~ALPHA_SET;
         }
 
         if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
@@ -1635,17 +2281,21 @@
         }
 
         if (hasNoCache) {
-            // Fast path for layouts with no backgrounds
-            if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
-                if (ViewDebug.TRACE_HIERARCHY) {
-                    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
+            if (!hasDisplayList) {
+                // Fast path for layouts with no backgrounds
+                if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+                    if (ViewDebug.TRACE_HIERARCHY) {
+                        ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
+                    }
+                    child.mPrivateFlags &= ~DIRTY_MASK;
+                    child.dispatchDraw(canvas);
+                } else {
+                    child.draw(canvas);
                 }
-                child.mPrivateFlags &= ~DIRTY_MASK;
-                child.dispatchDraw(canvas);
             } else {
-                child.draw(canvas);
+                ((HardwareCanvas) canvas).drawDisplayList(displayList);
             }
-        } else {
+        } else if (cache != null) {
             final Paint cachePaint = mCachePaint;
             if (alpha < 1.0f) {
                 cachePaint.setAlpha((int) (alpha * 255));
@@ -1654,9 +2304,6 @@
                 cachePaint.setAlpha(255);
                 mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE;
             }
-            if (Config.DEBUG && ViewDebug.profileDrawing) {
-                EventLog.writeEvent(60003, hashCode());
-            }
             canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
         }
 
@@ -1702,10 +2349,24 @@
         final View[] children = mChildren;
         final int count = mChildrenCount;
         for (int i = 0; i < count; i++) {
+            
             children[i].setSelected(selected);
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void dispatchSetActivated(boolean activated) {
+        final View[] children = mChildren;
+        final int count = mChildrenCount;
+        for (int i = 0; i < count; i++) {
+
+            children[i].setActivated(activated);
+        }
+    }
+
     @Override
     protected void dispatchSetPressed(boolean pressed) {
         final View[] children = mChildren;
@@ -1977,6 +2638,10 @@
                     "You must call removeView() on the child's parent first.");
         }
 
+        if (mTransition != null) {
+            mTransition.addChild(this, child);
+        }
+
         if (!checkLayoutParams(params)) {
             params = generateLayoutParams(params);
         }
@@ -2054,7 +2719,9 @@
     // This method also sets the child's mParent to null
     private void removeFromArray(int index) {
         final View[] children = mChildren;
-        children[index].mParent = null;
+        if (!(mTransitioningViews != null && mTransitioningViews.contains(children[index]))) {
+            children[index].mParent = null;
+        }
         final int count = mChildrenCount;
         if (index == count - 1) {
             children[--mChildrenCount] = null;
@@ -2189,13 +2856,19 @@
     }
 
     private void removeViewInternal(int index, View view) {
+
+        if (mTransition != null) {
+            mTransition.removeChild(this, view);
+        }
+
         boolean clearChildFocus = false;
         if (view == mFocused) {
             view.clearFocusForRemoval();
             clearChildFocus = true;
         }
 
-        if (view.getAnimation() != null) {
+        if (view.getAnimation() != null ||
+                (mTransitioningViews != null && mTransitioningViews.contains(view))) {
             addDisappearingView(view);
         } else if (view.mAttachInfo != null) {
            view.dispatchDetachedFromWindow();
@@ -2214,6 +2887,39 @@
         }
     }
 
+    /**
+     * Sets the LayoutTransition object for this ViewGroup. If the LayoutTransition object is
+     * not null, changes in layout which occur because of children being added to or removed from
+     * the ViewGroup will be animated according to the animations defined in that LayoutTransition
+     * object. By default, the transition object is null (so layout changes are not animated).
+     *
+     * @param transition The LayoutTransition object that will animated changes in layout. A value
+     * of <code>null</code> means no transition will run on layout changes.
+     * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
+     */
+    public void setLayoutTransition(LayoutTransition transition) {
+        if (mTransition != null) {
+            mTransition.removeTransitionListener(mLayoutTransitionListener);
+        }
+        mTransition = transition;
+        if (mTransition != null) {
+            mTransition.addTransitionListener(mLayoutTransitionListener);
+        }
+    }
+
+    /**
+     * Gets the LayoutTransition object for this ViewGroup. If the LayoutTransition object is
+     * not null, changes in layout which occur because of children being added to or removed from
+     * the ViewGroup will be animated according to the animations defined in that LayoutTransition
+     * object. By default, the transition object is null (so layout changes are not animated).
+     *
+     * @return LayoutTranstion The LayoutTransition object that will animated changes in layout.
+     * A value of <code>null</code> means no transition will run on layout changes.
+     */
+    public LayoutTransition getLayoutTransition() {
+        return mTransition;
+    }
+
     private void removeViewsInternal(int start, int count) {
         final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener;
         final boolean notifyListener = onHierarchyChangeListener != null;
@@ -2227,12 +2933,17 @@
         for (int i = start; i < end; i++) {
             final View view = children[i];
 
+            if (mTransition != null) {
+                mTransition.removeChild(this, view);
+            }
+
             if (view == focused) {
                 view.clearFocusForRemoval();
                 clearChildFocus = view;
             }
 
-            if (view.getAnimation() != null) {
+            if (view.getAnimation() != null ||
+                (mTransitioningViews != null && mTransitioningViews.contains(view))) {
                 addDisappearingView(view);
             } else if (detach) {
                view.dispatchDetachedFromWindow();
@@ -2291,12 +3002,17 @@
         for (int i = count - 1; i >= 0; i--) {
             final View view = children[i];
 
+            if (mTransition != null) {
+                mTransition.removeChild(this, view);
+            }
+
             if (view == focused) {
                 view.clearFocusForRemoval();
                 clearChildFocus = view;
             }
 
-            if (view.getAnimation() != null) {
+            if (view.getAnimation() != null ||
+                    (mTransitioningViews != null && mTransitioningViews.contains(view))) {
                 addDisappearingView(view);
             } else if (detach) {
                view.dispatchDetachedFromWindow();
@@ -2329,11 +3045,16 @@
      * @see #detachViewFromParent(int)
      */
     protected void removeDetachedView(View child, boolean animate) {
+        if (mTransition != null) {
+            mTransition.removeChild(this, child);
+        }
+
         if (child == mFocused) {
             child.clearFocus();
         }
 
-        if (animate && child.getAnimation() != null) {
+        if ((animate && child.getAnimation() != null) ||
+                (mTransitioningViews != null && mTransitioningViews.contains(child))) {
             addDisappearingView(child);
         } else if (child.mAttachInfo != null) {
             child.dispatchDetachedFromWindow();
@@ -2475,6 +3196,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
@@ -2509,6 +3239,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);
         }
     }
@@ -2875,7 +3617,7 @@
      */
     @ViewDebug.ExportedProperty(category = "drawing", 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")
     })
@@ -3288,6 +4030,80 @@
     }
 
     /**
+     * This method tells the ViewGroup that the given View object, which should have this
+     * ViewGroup as its parent,
+     * should be kept around  (re-displayed when the ViewGroup draws its children) even if it
+     * is removed from its parent. This allows animations, such as those used by
+     * {@link android.app.Fragment} and {@link android.animation.LayoutTransition} to animate
+     * the removal of views. A call to this method should always be accompanied by a later call
+     * to {@link #endViewTransition(View)}, such as after an animation on the View has finished,
+     * so that the View finally gets removed.
+     *
+     * @param view The View object to be kept visible even if it gets removed from its parent.
+     */
+    public void startViewTransition(View view) {
+        if (view.mParent == this) {
+            if (mTransitioningViews == null) {
+                mTransitioningViews = new ArrayList<View>();
+            }
+            mTransitioningViews.add(view);
+        }
+    }
+
+    /**
+     * This method should always be called following an earlier call to
+     * {@link #startViewTransition(View)}. The given View is finally removed from its parent
+     * and will no longer be displayed. Note that this method does not perform the functionality
+     * of removing a view from its parent; it just discontinues the display of a View that
+     * has previously been removed.
+     *
+     * @return view The View object that has been removed but is being kept around in the visible
+     * hierarchy by an earlier call to {@link #startViewTransition(View)}.
+     */
+    public void endViewTransition(View view) {
+        if (mTransitioningViews != null) {
+            mTransitioningViews.remove(view);
+            final ArrayList<View> disappearingChildren = mDisappearingChildren;
+            if (disappearingChildren != null && disappearingChildren.contains(view)) {
+                disappearingChildren.remove(view);
+                if (mVisibilityChangingChildren != null &&
+                        mVisibilityChangingChildren.contains(view)) {
+                    mVisibilityChangingChildren.remove(view);
+                } else {
+                    if (view.mAttachInfo != null) {
+                        view.dispatchDetachedFromWindow();
+                    }
+                    if (view.mParent != null) {
+                        view.mParent = null;
+                    }
+                }
+                mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+            }
+        }
+    }
+
+    private LayoutTransition.TransitionListener mLayoutTransitionListener =
+            new LayoutTransition.TransitionListener() {
+        @Override
+        public void startTransition(LayoutTransition transition, ViewGroup container,
+                View view, int transitionType) {
+            // We only care about disappearing items, since we need special logic to keep
+            // those items visible after they've been 'removed'
+            if (transitionType == LayoutTransition.DISAPPEARING) {
+                startViewTransition(view);
+            }
+        }
+
+        @Override
+        public void endTransition(LayoutTransition transition, ViewGroup container,
+                View view, int transitionType) {
+            if (transitionType == LayoutTransition.DISAPPEARING && mTransitioningViews != null) {
+                endViewTransition(view);
+            }
+        }
+    };
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -3749,4 +4565,59 @@
             bottomMargin = bottom;
         }
     }
+
+    /* Describes a touched view and the ids of the pointers that it has captured.
+     *
+     * This code assumes that pointer ids are always in the range 0..31 such that
+     * it can use a bitfield to track which pointer ids are present.
+     * As it happens, the lower layers of the input dispatch pipeline also use the
+     * same trick so the assumption should be safe here...
+     */
+    private static final class TouchTarget {
+        private static final int MAX_RECYCLED = 32;
+        private static final Object sRecycleLock = new Object();
+        private static TouchTarget sRecycleBin;
+        private static int sRecycledCount;
+
+        public static final int ALL_POINTER_IDS = -1; // all ones
+
+        // The touched child view.
+        public View child;
+
+        // The combined bit mask of pointer ids for all pointers captured by the target.
+        public int pointerIdBits;
+
+        // The next target in the target list.
+        public TouchTarget next;
+
+        private TouchTarget() {
+        }
+
+        public static TouchTarget obtain(View child, int pointerIdBits) {
+            final TouchTarget target;
+            synchronized (sRecycleLock) {
+                if (sRecycleBin == null) {
+                    target = new TouchTarget();
+                } else {
+                    target = sRecycleBin;
+                    sRecycleBin = target.next;
+                     sRecycledCount--;
+                    target.next = null;
+                }
+            }
+            target.child = child;
+            target.pointerIdBits = pointerIdBits;
+            return target;
+        }
+
+        public void recycle() {
+            synchronized (sRecycleLock) {
+                if (sRecycledCount < MAX_RECYCLED) {
+                    next = sRecycleBin;
+                    sRecycleBin = this;
+                    sRecycledCount += 1;
+                }
+            }
+        }
+    }
 }
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 9c249ce..155122f 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -24,6 +24,8 @@
 import android.graphics.Canvas;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
+import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.*;
@@ -45,6 +47,8 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.app.ActivityManagerNative;
@@ -56,10 +60,6 @@
 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
@@ -67,14 +67,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;
@@ -95,8 +93,6 @@
      */
     static final int MAX_TRACKBALL_DELAY = 250;
 
-    static long sInstanceCount = 0;
-
     static IWindowSession sWindowSession;
 
     static final Object mStaticInit = new Object();
@@ -204,17 +200,14 @@
     int mCurScrollY;
     Scroller mScroller;
 
-    EGL10 mEgl;
-    EGLDisplay mEglDisplay;
-    EGLContext mEglContext;
-    EGLSurface mEglSurface;
-    GL11 mGL;
-    Canvas mGlCanvas;
-    boolean mUseGL;
-    boolean mGlWanted;
-
     final ViewConfiguration mViewConfiguration;
 
+    /* Drag/drop */
+    ClipDescription mDragDescription;
+    View mCurrentDragView;
+    final PointF mDragPoint = new PointF();
+    final PointF mLastTouchPoint = new PointF();
+
     /**
      * see {@link #playSoundEffect(int)}
      */
@@ -241,13 +234,12 @@
     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
-        //++sInstanceCount;
-
         // Initialize the statics when this class is first instantiated. This is
         // done here instead of in the static block because Zygote does not
         // allow the spawning of threads.
@@ -262,7 +254,7 @@
         mTempRect = new Rect();
         mVisRect = new Rect();
         mWinFrame = new Rect();
-        mWindow = new W(this, context);
+        mWindow = new W(this);
         mInputMethodCallback = new InputMethodCallback(this);
         mViewVisibility = View.GONE;
         mTransparentRegion = new Region();
@@ -274,19 +266,6 @@
         mDensity = context.getResources().getDisplayMetrics().densityDpi;
     }
 
-    // For debug only
-    /*
-    @Override
-    protected void finalize() throws Throwable {
-        super.finalize();
-        --sInstanceCount;
-    }
-    */
-
-    public static long getInstanceCount() {
-        return sInstanceCount;
-    }
-
     public static void addFirstDrawHandler(Runnable callback) {
         synchronized (sFirstDrawHandlers) {
             if (!sFirstDrawComplete) {
@@ -330,122 +309,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();
@@ -575,6 +450,24 @@
         }
     }
 
+    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 (!HardwareRenderer.sRendererDisabled) {
+            // 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;
+                if (mAttachInfo.mHardwareRenderer != null) {
+                    mAttachInfo.mHardwareRenderer.destroy(true);
+                }                
+                mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
+                mAttachInfo.mHardwareAccelerated = true;
+            }
+        }
+    }
+
     public View getView() {
         return mView;
     }
@@ -731,8 +624,6 @@
         boolean viewVisibilityChanged = mViewVisibility != viewVisibility
                 || mNewSurfaceNeeded;
 
-        float appScale = mAttachInfo.mApplicationScale;
-
         WindowManager.LayoutParams params = null;
         if (mWindowAttributesChanged) {
             mWindowAttributesChanged = false;
@@ -780,9 +671,9 @@
             attachInfo.mWindowVisibility = viewVisibility;
             host.dispatchWindowVisibilityChanged(viewVisibility);
             if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
-                if (mUseGL) {
-                    destroyGL();
-                }
+                if (mAttachInfo.mHardwareRenderer != null) {
+                    mAttachInfo.mHardwareRenderer.destroy(false);
+                }                
             }
             if (viewVisibility == View.GONE) {
                 // After making a window gone, we will count it as being
@@ -897,10 +788,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
@@ -912,26 +805,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) {
@@ -991,9 +877,8 @@
                         fullRedrawNeeded = true;
                         mPreviousTransparentRegion.setEmpty();
 
-                        if (mGlWanted && !mUseGL) {
-                            initializeGL();
-                            initialized = mGlCanvas != null;
+                        if (mAttachInfo.mHardwareRenderer != null) {
+                            hwIntialized = mAttachInfo.mHardwareRenderer.initialize(mHolder);
                         }
                     }
                 } else if (!mSurface.isValid()) {
@@ -1070,10 +955,9 @@
                     mSurfaceHolder.mSurfaceLock.unlock();
                 }
             }
-            
-            if (initialized) {
-                mGlCanvas.setViewport((int) (mWidth * appScale + 0.5f),
-                        (int) (mHeight * appScale + 0.5f));
+
+            if (hwIntialized || (windowShouldResize && mAttachInfo.mHardwareRenderer != null)) {
+                mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight);
             }
 
             boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
@@ -1133,7 +1017,7 @@
                 TAG, "Laying out " + host + " to (" +
                 host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")");
             long startTime = 0L;
-            if (Config.DEBUG && ViewDebug.profileLayout) {
+            if (ViewDebug.DEBUG_PROFILE_LAYOUT) {
                 startTime = SystemClock.elapsedRealtime();
             }
             host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
@@ -1146,7 +1030,7 @@
                 }
             }
 
-            if (Config.DEBUG && ViewDebug.profileLayout) {
+            if (ViewDebug.DEBUG_PROFILE_LAYOUT) {
                 EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime);
             }
 
@@ -1346,7 +1230,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));
                 }
             }
@@ -1379,61 +1264,24 @@
             dirty.setEmpty();
             return;
         }
-        
-        if (mUseGL) {
-            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;
-                    }
-                }
-            }
-            if (scrolling) {
-                mFullRedrawNeeded = true;
-                scheduleTraversals();
-            }
-            return;
-        }
 
         if (fullRedrawNeeded) {
             mAttachInfo.mIgnoreDirtyState = true;
             dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
         }
+        
+        if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
+            if (!dirty.isEmpty()) {
+                mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, yoff);
+            }
+
+            if (scrolling) {
+                mFullRedrawNeeded = true;
+                scheduleTraversals();
+            }
+
+            return;
+        }
 
         if (DEBUG_ORIENTATION || DEBUG_DRAW) {
             Log.v(TAG, "Draw " + mView + "/"
@@ -1482,7 +1330,7 @@
                         //canvas.drawARGB(255, 255, 0, 0);
                     }
 
-                    if (Config.DEBUG && ViewDebug.profileDrawing) {
+                    if (ViewDebug.DEBUG_PROFILE_DRAWING) {
                         startTime = SystemClock.elapsedRealtime();
                     }
 
@@ -1509,7 +1357,6 @@
                                 ", metrics=" + cxt.getResources().getDisplayMetrics() +
                                 ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
                     }
-                    int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
                     try {
                         canvas.translate(0, -yoff);
                         if (mTranslator != null) {
@@ -1520,14 +1367,13 @@
                         mView.draw(canvas);
                     } finally {
                         mAttachInfo.mIgnoreDirtyState = false;
-                        canvas.restoreToCount(saveCount);
                     }
 
                     if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
                         mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                     }
 
-                    if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
+                    if (SHOW_FPS || ViewDebug.DEBUG_SHOW_FPS) {
                         int now = (int)SystemClock.elapsedRealtime();
                         if (sDrawTime != 0) {
                             nativeShowFPS(canvas, now - sDrawTime);
@@ -1535,7 +1381,7 @@
                         sDrawTime = now;
                     }
 
-                    if (Config.DEBUG && ViewDebug.profileDrawing) {
+                    if (ViewDebug.DEBUG_PROFILE_DRAWING) {
                         EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
                     }
                 }
@@ -1738,8 +1584,6 @@
     }
 
     void dispatchDetachedFromWindow() {
-        if (Config.LOGV) Log.v(TAG, "Detaching in " + this + " of " + mSurface);
-
         if (mView != null) {
             mView.dispatchDetachedFromWindow();
         }
@@ -1748,9 +1592,8 @@
         mAttachInfo.mRootView = null;
         mAttachInfo.mSurface = null;
 
-        if (mUseGL) {
-            destroyGL();
-        }
+        destroyHardwareRenderer();
+
         mSurface.release();
 
         if (mInputChannel != null) {
@@ -1837,6 +1680,7 @@
     public final static int FINISH_INPUT_CONNECTION = 1012;
     public final static int CHECK_FOCUS = 1013;
     public final static int CLOSE_SYSTEM_DIALOGS = 1014;
+    public final static int DISPATCH_DRAG_EVENT = 1015;
 
     @Override
     public void handleMessage(Message msg) {
@@ -1939,18 +1783,9 @@
                     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 (mAttachInfo.mHardwareRenderer != null) {
+                        mAttachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
+                                mAttachInfo, mHolder);
                     }
                 }
 
@@ -2000,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;
@@ -2022,6 +1856,9 @@
                 mView.onCloseSystemDialogs((String)msg.obj);
             }
         } break;
+        case DISPATCH_DRAG_EVENT: {
+            handleDragEvent((DragEvent)msg.obj);
+        } break;
         }
     }
     
@@ -2189,6 +2026,9 @@
             if (MEASURE_LATENCY) {
                 lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
             }
+            // cache for possible drag-initiation
+            mLastTouchPoint.x = event.getRawX();
+            mLastTouchPoint.y = event.getRawY();
             handled = mView.dispatchTouchEvent(event);
             if (MEASURE_LATENCY) {
                 lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano());
@@ -2498,8 +2338,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) {
                 finishInputEvent();
@@ -2531,7 +2370,6 @@
             final boolean sendDone = seq >= 0;
             if (!handled) {
                 deliverKeyEventToViewHierarchy(event, sendDone);
-                return;
             } else if (sendDone) {
                 finishInputEvent();
             } else {
@@ -2613,6 +2451,92 @@
         }
     }
 
+    /* drag/drop */
+    private void handleDragEvent(DragEvent event) {
+        // From the root, only drag start/end/location are dispatched.  entered/exited
+        // are determined and dispatched by the viewgroup hierarchy, who then report
+        // that back here for ultimate reporting back to the framework.
+        if (mView != null && mAdded) {
+            final int what = event.mAction;
+
+            if (what == DragEvent.ACTION_DRAG_EXITED) {
+                // A direct EXITED event means that the window manager knows we've just crossed
+                // a window boundary, so the current drag target within this one must have
+                // just been exited.  Send it the usual notifications and then we're done
+                // for now.
+                setDragFocus(event, null);
+            } else {
+                // Cache the drag description when the operation starts, then fill it in
+                // on subsequent calls as a convenience
+                if (what == DragEvent.ACTION_DRAG_STARTED) {
+                    mDragDescription = event.mClipDescription;
+                } else {
+                    event.mClipDescription = mDragDescription;
+                }
+
+                // For events with a [screen] location, translate into window coordinates
+                if ((what == DragEvent.ACTION_DRAG_LOCATION) || (what == DragEvent.ACTION_DROP)) {
+                    mDragPoint.set(event.mX, event.mY);
+                    if (mTranslator != null) {
+                        mTranslator.translatePointInScreenToAppWindow(mDragPoint);
+                    }
+
+                    if (mCurScrollY != 0) {
+                        mDragPoint.offset(0, mCurScrollY);
+                    }
+
+                    event.mX = mDragPoint.x;
+                    event.mY = mDragPoint.y;
+                }
+
+                // Remember who the current drag target is pre-dispatch
+                final View prevDragView = mCurrentDragView;
+
+                // Now dispatch the drag/drop event
+                mView.dispatchDragEvent(event);
+
+                // If we changed apparent drag target, tell the OS about it
+                if (prevDragView != mCurrentDragView) {
+                    try {
+                        if (prevDragView != null) {
+                            sWindowSession.dragRecipientExited(mWindow);
+                        }
+                        if (mCurrentDragView != null) {
+                            sWindowSession.dragRecipientEntered(mWindow);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to note drag target change");
+                    }
+                    mCurrentDragView = prevDragView;
+                }
+            }
+        }
+        event.recycle();
+    }
+
+    public void getLastTouchPoint(Point outLocation) {
+        outLocation.x = (int) mLastTouchPoint.x;
+        outLocation.y = (int) mLastTouchPoint.y;
+    }
+
+    public void setDragFocus(DragEvent event, View newDragTarget) {
+        final int action = event.mAction;
+        // If we've dragged off of a view, send it the EXITED message
+        if (mCurrentDragView != newDragTarget) {
+            if (mCurrentDragView != null) {
+                event.mAction = DragEvent.ACTION_DRAG_EXITED;
+                mCurrentDragView.dispatchDragEvent(event);
+            }
+        }
+        // If we've dragged over a new view, send it the ENTERED message
+        if (newDragTarget != null) {
+            event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+            newDragTarget.dispatchDragEvent(event);
+        }
+        mCurrentDragView = newDragTarget;
+        event.mAction = action;  // restore the event's original state
+    }
+
     private AudioManager getAudioManager() {
         if (mView == null) {
             throw new IllegalStateException("getAudioManager called when there is no mView");
@@ -2730,9 +2654,11 @@
 
     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) {
+                destroyHardwareRenderer();
+
                 int viewVisibility = mView.getVisibility();
                 boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                 if (mWindowAttributesChanged || viewVisibilityChanged) {
@@ -2757,6 +2683,14 @@
         }
     }
 
+    private void destroyHardwareRenderer() {
+        if (mAttachInfo.mHardwareRenderer != null) {
+            mAttachInfo.mHardwareRenderer.destroy(true);
+            mAttachInfo.mHardwareRenderer = null;
+            mAttachInfo.mHardwareAccelerated = false;
+        }
+    }
+
     public void dispatchFinishedEvent(int seq, boolean handled) {
         Message msg = obtainMessage(FINISHED_EVENT);
         msg.arg1 = seq;
@@ -2809,13 +2743,12 @@
         //noinspection ConstantConditions
         if (false && event.getAction() == KeyEvent.ACTION_DOWN) {
             if (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", "===================================================");
             }
         }
 
@@ -2895,7 +2828,12 @@
         msg.obj = reason;
         sendMessage(msg);
     }
-    
+
+    public void dispatchDragEvent(DragEvent event) {
+        Message msg = obtainMessage(DISPATCH_DRAG_EVENT, event);
+        sendMessage(msg);
+    }
+
     /**
      * The window is getting focus so if there is anything focused/selected
      * send an {@link AccessibilityEvent} to announce that.
@@ -2915,16 +2853,16 @@
         return false;
     }
 
+    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
+        return null;
+    }
+
     public void createContextMenu(ContextMenu menu) {
     }
 
     public void childDrawableStateChanged(View child) {
     }
 
-    protected Rect getWindowFrame() {
-        return mWinFrame;
-    }
-
     void checkThread() {
         if (mThread != Thread.currentThread()) {
             throw new CalledFromWrongThreadException(
@@ -3003,16 +2941,15 @@
     static class W extends IWindow.Stub {
         private final WeakReference<ViewRoot> mViewRoot;
 
-        public W(ViewRoot viewRoot, Context context) {
+        W(ViewRoot viewRoot) {
             mViewRoot = new WeakReference<ViewRoot>(viewRoot);
         }
 
-        public void resized(int w, int h, Rect coveredInsets,
-                Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
+        public void resized(int w, int h, Rect coveredInsets, Rect visibleInsets,
+                boolean reportDraw, Configuration newConfig) {
             final ViewRoot viewRoot = mViewRoot.get();
             if (viewRoot != null) {
-                viewRoot.dispatchResized(w, h, coveredInsets,
-                        visibleInsets, reportDraw, newConfig);
+                viewRoot.dispatchResized(w, h, coveredInsets, visibleInsets, reportDraw, newConfig);
             }
         }
 
@@ -3107,6 +3044,14 @@
                 }
             }
         }
+
+        /* Drag/drop */
+        public void dispatchDragEvent(DragEvent event) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchDragEvent(event);
+            }
+        }
     }
 
     /**
@@ -3415,8 +3360,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..9fadc58 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -56,11 +56,28 @@
     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 requesting an Action Bar that overlays window content.
+     * Normally an Action Bar will sit in the space above window content, but if this
+     * feature is requested along with {@link #FEATURE_ACTION_BAR} it will be layered over
+     * the window content itself. This is useful if you would like your app to have more control
+     * over how the Action Bar is displayed, such as letting application content scroll beneath
+     * an Action Bar with a transparent background or otherwise displaying a transparent/translucent
+     * Action Bar over application content.
+     */
+    public static final int FEATURE_ACTION_BAR_OVERLAY = 9;
+    /**
+     * 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 = 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 */
@@ -109,6 +126,8 @@
 
     private boolean mHasSoftInputMode = false;
     
+    private boolean mDestroyed;
+
     // The current window attributes.
     private final WindowManager.LayoutParams mWindowAttributes =
         new WindowManager.LayoutParams();
@@ -292,6 +311,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) {
@@ -353,6 +380,16 @@
         return mHasChildren;
     }
     
+    /** @hide */
+    public final void destroy() {
+        mDestroyed = true;
+    }
+
+    /** @hide */
+    public final boolean isDestroyed() {
+        return mDestroyed;
+    }
+
     /**
      * Set the window manager for use by this Window to, for example,
      * display panels.  This is <em>not</em> used for displaying the
@@ -360,21 +397,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 +472,9 @@
             if (wp.packageName == null) {
                 wp.packageName = mContext.getPackageName();
             }
+            if (mHardwareAccelerated) {
+                wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+            }
             mWindowManager.addView(view, params);
         }
 
@@ -713,6 +767,15 @@
         return (mFeatures&flag) != 0;
     }
 
+    /**
+     * @hide Used internally to help resolve conflicting features.
+     */
+    protected void removeFeature(int featureId) {
+        final int flag = 1<<featureId;
+        mFeatures &= ~flag;
+        mLocalFeatures &= ~(mContainer != null ? (flag&~mContainer.mFeatures) : flag);
+    }
+
     public final void makeActive() {
         if (mContainer != null) {
             if (mContainer.mActiveChild != null) {
@@ -817,6 +880,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 +1061,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 9c4aefe..eddd04e 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:
@@ -343,6 +347,13 @@
         public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
         
         /**
+         * Window type: the drag-and-drop pseudowindow.  There is only one
+         * drag layer (at most), and it is placed on top of all other windows.
+         * @hide
+         */
+        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+15;
+
+        /**
          * End of types of system windows.
          */
         public static final int LAST_SYSTEM_WINDOW      = 2999;
@@ -389,6 +400,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 +432,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;
         
@@ -616,6 +630,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
@@ -1088,6 +1108,7 @@
                 screenOrientation = o.screenOrientation;
                 changes |= SCREEN_ORIENTATION_CHANGED;
             }
+
             return changes;
         }
     
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..9a1e41c 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.
      */
@@ -301,7 +307,7 @@
      * animated as well as the objects parents. (This is to support animation
      * sizes being specifed relative to these dimensions.)
      *
-     * <p>Objects that interpret a Animations should call this method when
+     * <p>Objects that interpret Animations should call this method when
      * the sizes of the object being animated and its parent are known, and
      * before calling {@link #getTransformation}.
      *
@@ -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..32ff647 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -33,6 +33,14 @@
  *
  */
 public class AnimationUtils {
+
+    /**
+     * These flags are used when parsing AnimatorSet 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 +57,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
@@ -82,12 +90,12 @@
 
         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 +108,7 @@
             }
 
             String  name = parser.getName();
-    
+
             if (name.equals("set")) {
                 anim = new AnimationSet(c, attrs);
                 createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
@@ -120,7 +128,7 @@
                 parent.addAnimation(anim);
             }
         }
-    
+
         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/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 2ddf5f8..5ab3d34 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -219,4 +219,10 @@
      * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
      */
     public void hideSoftInput(int flags, ResultReceiver resultReceiver);
+
+    /**
+     * Notify that the input method subtype is being changed in the same input method.
+     * @param subtype New subtype of the notified input method
+     */
+    public void changeInputMethodSubtype(InputMethodSubtype subtype);
 }
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 357cb5fe..54102f6 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -37,13 +37,14 @@
 import android.util.Xml;
 
 import java.io.IOException;
+import java.util.ArrayList;
 
 /**
  * This class is used to specify meta information of an input method.
  */
 public final class InputMethodInfo implements Parcelable {
     static final String TAG = "InputMethodInfo";
-    
+
     /**
      * The Service that implements this input method component.
      */
@@ -68,7 +69,12 @@
      * can change based on the configuration (in particular locale).
      */
     final int mIsDefaultResId;
-    
+
+    /**
+     * The array of the subtypes.
+     */
+    private final ArrayList<InputMethodSubtype> mSubtypes = new ArrayList<InputMethodSubtype>();
+
     /**
      * Constructor.
      * 
@@ -81,11 +87,11 @@
         mService = service;
         ServiceInfo si = service.serviceInfo;
         mId = new ComponentName(si.packageName, si.name).flattenToShortString();
-        
+
         PackageManager pm = context.getPackageManager();
         String settingsActivityComponent = null;
         int isDefaultResId = 0;
-        
+
         XmlResourceParser parser = null;
         try {
             parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA);
@@ -116,13 +122,39 @@
             isDefaultResId = sa.getResourceId(
                     com.android.internal.R.styleable.InputMethod_isDefault, 0);
             sa.recycle();
+
+            final int depth = parser.getDepth();
+            // Parse all subtypes
+            while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                    && type != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG) {
+                    nodeName = parser.getName();
+                    if (!"subtype".equals(nodeName)) {
+                        throw new XmlPullParserException(
+                                "Meta-data in input-method does not start with subtype tag");
+                    }
+                    final TypedArray a = res.obtainAttributes(
+                            attrs, com.android.internal.R.styleable.InputMethod_Subtype);
+                    InputMethodSubtype subtype = new InputMethodSubtype(
+                            a.getResourceId(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_label, 0),
+                            a.getResourceId(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_icon, 0),
+                            a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_imeSubtypeLocale),
+                            a.getResourceId(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_imeSubtypeMode, 0),
+                            a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_imeSubtypeExtraValue));
+                    mSubtypes.add(subtype);
+                }
+            }
         } catch (NameNotFoundException e) {
             throw new XmlPullParserException(
                     "Unable to create context for: " + si.packageName);
         } finally {
             if (parser != null) parser.close();
         }
-        
         mSettingsActivityName = settingsActivityComponent;
         mIsDefaultResId = isDefaultResId;
     }
@@ -132,8 +164,9 @@
         mSettingsActivityName = source.readString();
         mIsDefaultResId = source.readInt();
         mService = ResolveInfo.CREATOR.createFromParcel(source);
+        source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR);
     }
-    
+
     /**
      * Temporary API for creating a built-in input method.
      */
@@ -156,7 +189,7 @@
         mSettingsActivityName = settingsActivity;
         mIsDefaultResId = 0;
     }
-    
+
     /**
      * Return a unique ID for this input method.  The ID is generated from
      * the package and class name implementing the method.
@@ -164,14 +197,14 @@
     public String getId() {
         return mId;
     }
-    
+
     /**
      * Return the .apk package that implements this input method.
      */
     public String getPackageName() {
         return mService.serviceInfo.packageName;
     }
-    
+
     /**
      * Return the class name of the service component that implements
      * this input method.
@@ -196,7 +229,7 @@
         return new ComponentName(mService.serviceInfo.packageName,
                 mService.serviceInfo.name);
     }
-    
+
     /**
      * Load the user-displayed label for this input method.
      * 
@@ -206,7 +239,7 @@
     public CharSequence loadLabel(PackageManager pm) {
         return mService.loadLabel(pm);
     }
-    
+
     /**
      * Load the user-displayed icon for this input method.
      * 
@@ -216,7 +249,7 @@
     public Drawable loadIcon(PackageManager pm) {
         return mService.loadIcon(pm);
     }
-    
+
     /**
      * Return the class name of an activity that provides a settings UI for
      * the input method.  You can launch this activity be starting it with
@@ -230,7 +263,14 @@
     public String getSettingsActivity() {
         return mSettingsActivityName;
     }
-    
+
+    /**
+     * Return the subtypes of Input Method.
+     */
+    public ArrayList<InputMethodSubtype> getSubtypes() {
+        return mSubtypes;
+    }
+
     /**
      * Return the resource identifier of a resource inside of this input
      * method's .apk that determines whether it should be considered a
@@ -239,7 +279,7 @@
     public int getIsDefaultResourceId() {
         return mIsDefaultResId;
     }
-    
+
     public void dump(Printer pw, String prefix) {
         pw.println(prefix + "mId=" + mId
                 + " mSettingsActivityName=" + mSettingsActivityName);
@@ -285,12 +325,14 @@
         dest.writeString(mSettingsActivityName);
         dest.writeInt(mIsDefaultResId);
         mService.writeToParcel(dest, flags);
+        dest.writeTypedList(mSubtypes);
     }
 
     /**
      * Used to make this class parcelable.
      */
-    public static final Parcelable.Creator<InputMethodInfo> CREATOR = new Parcelable.Creator<InputMethodInfo>() {
+    public static final Parcelable.Creator<InputMethodInfo> CREATOR
+            = new Parcelable.Creator<InputMethodInfo>() {
         public InputMethodInfo createFromParcel(Parcel source) {
             return new InputMethodInfo(source);
         }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index e30687f..8bd3298 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1380,7 +1380,7 @@
             }
         }
     }
-    
+
     public void showInputMethodPicker() {
         synchronized (mH) {
             try {
@@ -1391,6 +1391,16 @@
         }
     }
 
+    public void showInputMethodSubtypePicker() {
+        synchronized (mH) {
+            try {
+                mService.showInputMethodSubtypePickerFromClient(mClient);
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId, e);
+            }
+        }
+    }
+
     void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
         final Printer p = new PrintWriterPrinter(fout);
         p.println("Input method client state for " + this + ":");
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.aidl b/core/java/android/view/inputmethod/InputMethodSubtype.aidl
new file mode 100644
index 0000000..ed82b84
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.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.view.inputmethod;
+
+parcelable InputMethodSubtype;
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
new file mode 100644
index 0000000..a1ed044
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Information given to an {@link InputMethod} about a client connecting
+ * to it.
+ */
+/**
+ * InputMethodSubtype is a subtype contained in the input method. Subtype can describe
+ * locales (e.g. en_US, fr_FR...) and modes (e.g. voice, keyboard...), and is used for
+ * IME switch. The subtype allows the system to call the specified subtype of IME directly.
+ */
+public final class InputMethodSubtype implements Parcelable {
+    private final int mSubtypeNameResId;
+    private final int mSubtypeIconResId;
+    private final String mSubtypeLocale;
+    private final int mSubtypeModeResId;
+    private final String mSubtypeExtraValue;
+    private final int mSubtypeHashCode;
+
+    /**
+     * Constructor
+     * @param nameId The name of the subtype
+     * @param iconId The icon of the subtype
+     * @param locale The locale supported by the subtype
+     * @param modeId The mode supported by the subtype
+     * @param extraValue The extra value of the subtype
+     */
+    InputMethodSubtype(int nameId, int iconId, String locale, int modeId, String extraValue) {
+        mSubtypeNameResId = nameId;
+        mSubtypeIconResId = iconId;
+        mSubtypeLocale = locale;
+        mSubtypeModeResId = modeId;
+        mSubtypeExtraValue = extraValue;
+        mSubtypeHashCode = hashCodeInternal(mSubtypeNameResId, mSubtypeIconResId, mSubtypeLocale,
+                mSubtypeModeResId, mSubtypeExtraValue);
+    }
+
+    InputMethodSubtype(Parcel source) {
+        mSubtypeNameResId = source.readInt();
+        mSubtypeIconResId = source.readInt();
+        mSubtypeLocale = source.readString();
+        mSubtypeModeResId = source.readInt();
+        mSubtypeExtraValue = source.readString();
+        mSubtypeHashCode = hashCodeInternal(mSubtypeNameResId, mSubtypeIconResId, mSubtypeLocale,
+                mSubtypeModeResId, mSubtypeExtraValue);
+    }
+
+    /**
+     * @return the name of the subtype
+     */
+    public int getNameResId() {
+        return mSubtypeNameResId;
+    }
+
+    /**
+     * @return the icon of the subtype
+     */
+    public int getIconResId() {
+        return mSubtypeIconResId;
+    }
+
+    /**
+     * @return the locale of the subtype
+     */
+    public String getLocale() {
+        return mSubtypeLocale;
+    }
+
+    /**
+     * @return the mode of the subtype
+     */
+    public int getModeResId() {
+        return mSubtypeModeResId;
+    }
+
+    /**
+     * @return the extra value of the subtype
+     */
+    public String getExtraValue() {
+        return mSubtypeExtraValue;
+    }
+
+    @Override
+    public int hashCode() {
+        return mSubtypeHashCode;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof InputMethodSubtype) {
+            InputMethodSubtype subtype = (InputMethodSubtype) o;
+            return (subtype.getNameResId() == getNameResId())
+                && (subtype.getModeResId() == getModeResId())
+                && (subtype.getIconResId() == getIconResId())
+                && (subtype.getLocale().equals(getLocale()))
+                && (subtype.getExtraValue().equals(getExtraValue()));
+        }
+        return false;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeInt(mSubtypeNameResId);
+        dest.writeInt(mSubtypeIconResId);
+        dest.writeString(mSubtypeLocale);
+        dest.writeInt(mSubtypeModeResId);
+        dest.writeString(mSubtypeExtraValue);
+    }
+
+    public static final Parcelable.Creator<InputMethodSubtype> CREATOR
+            = new Parcelable.Creator<InputMethodSubtype>() {
+        public InputMethodSubtype createFromParcel(Parcel source) {
+            return new InputMethodSubtype(source);
+        }
+
+        public InputMethodSubtype[] newArray(int size) {
+            return new InputMethodSubtype[size];
+        }
+    };
+
+    private static int hashCodeInternal(int nameResId, int iconResId, String locale,
+            int modeResId, String extraValue) {
+        return Arrays.hashCode(new Object[] {nameResId, iconResId, locale, modeResId, extraValue});
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
new file mode 100644
index 0000000..ba16c8a
--- /dev/null
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.provider.Settings;
+import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.webkit.WebViewCore.EventHub;
+
+import java.util.ArrayList;
+import java.util.Stack;
+
+/**
+ * This class injects accessibility into WebViews with disabled JavaScript or
+ * WebViews with enabled JavaScript but for which we have no accessibility
+ * script to inject.
+ * </p>
+ * Note: To avoid changes in the framework upon changing the available
+ *       navigation axis, or reordering the navigation axis, or changing
+ *       the key bindings, or defining sequence of actions to be bound to
+ *       a given key this class is navigation axis agnostic. It is only
+ *       aware of one navigation axis which is in fact the default behavior
+ *       of webViews while using the DPAD/TrackBall.
+ * </p>
+ * In general a key binding is a mapping from meta state + key code to
+ * a sequence of actions. For more detail how to specify key bindings refer to
+ * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
+ * </p>
+ * The possible actions are invocations to
+ * {@link #setCurrentAxis(int, boolean, String)}, or
+ * {@link #traverseCurrentAxis(int, boolean, String)}
+ * {@link #traverseGivenAxis(int, int, boolean, String)}
+ * {@link #prefromAxisTransition(int, int, boolean, String)}
+ * referred via the values of:
+ * {@link #ACTION_SET_CURRENT_AXIS},
+ * {@link #ACTION_TRAVERSE_CURRENT_AXIS},
+ * {@link #ACTION_TRAVERSE_GIVEN_AXIS},
+ * {@link #ACTION_PERFORM_AXIS_TRANSITION},
+ * respectively.
+ * The arguments for the action invocation are specified as offset
+ * hexademical pairs. Note the last argument of the invocation
+ * should NOT be specified in the binding as it is provided by
+ * this class. For details about the key binding implementation
+ * refer to {@link AccessibilityWebContentKeyBinding}.
+ */
+class AccessibilityInjector {
+    private static final String LOG_TAG = "AccessibilityInjector";
+
+    private static final boolean DEBUG = true;
+
+    private static final int ACTION_SET_CURRENT_AXIS = 0;
+    private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1;
+    private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2;
+    private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
+
+    // the default WebView behavior abstracted as a navigation axis
+    private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
+
+    // these are the same for all instances so make them process wide
+    private static SparseArray<AccessibilityWebContentKeyBinding> sBindings =
+        new SparseArray<AccessibilityWebContentKeyBinding>();
+
+    // handle to the WebView this injector is associated with.
+    private final WebView mWebView;
+
+    // events scheduled for sending as soon as we receive the selected text
+    private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
+
+    // the current traversal axis
+    private int mCurrentAxis = 2; // sentence
+
+    // we need to consume the up if we have handled the last down
+    private boolean mLastDownEventHandled;
+
+    // getting two empty selection strings in a row we let the WebView handle the event
+    private boolean mIsLastSelectionStringNull;
+
+    // keep track of last direction
+    private int mLastDirection;
+
+    /**
+     * Creates a new injector associated with a given {@link WebView}.
+     *
+     * @param webView The associated WebView.
+     */
+    public AccessibilityInjector(WebView webView) {
+        mWebView = webView;
+        ensureWebContentKeyBindings();
+    }
+
+    /**
+     * Processes a key down <code>event</code>.
+     *
+     * @return True if the event was processed.
+     */
+    public boolean onKeyEvent(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_UP) {
+            return mLastDownEventHandled;
+        }
+
+        mLastDownEventHandled = false;
+
+        int key = event.getMetaState() << AccessibilityWebContentKeyBinding.OFFSET_META_STATE |
+            event.getKeyCode() << AccessibilityWebContentKeyBinding.OFFSET_KEY_CODE;
+
+        AccessibilityWebContentKeyBinding binding = sBindings.get(key);
+        if (binding == null) {
+            return false;
+        }
+
+        for (int i = 0, count = binding.getActionCount(); i < count; i++) {
+            int actionCode = binding.getActionCode(i);
+            String contentDescription = Integer.toHexString(binding.getAction(i));
+            switch (actionCode) {
+                case ACTION_SET_CURRENT_AXIS:
+                    int axis = binding.getFirstArgument(i);
+                    boolean sendEvent = (binding.getSecondArgument(i) == 1);
+                    setCurrentAxis(axis, sendEvent, contentDescription);
+                    mLastDownEventHandled = true;
+                    break;
+                case ACTION_TRAVERSE_CURRENT_AXIS:
+                    int direction = binding.getFirstArgument(i);
+                    // on second null selection string in same direction => WebView handle the event
+                    if (direction == mLastDirection && mIsLastSelectionStringNull) {
+                        mLastDirection = direction;
+                        mIsLastSelectionStringNull = false;
+                        return false;
+                    }
+                    mLastDirection = direction;
+                    sendEvent = (binding.getSecondArgument(i) == 1);
+                    mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
+                            contentDescription);
+                    break;
+                case ACTION_TRAVERSE_GIVEN_AXIS:
+                    direction = binding.getFirstArgument(i);
+                    // on second null selection string in same direction => WebView handle the event
+                    if (direction == mLastDirection && mIsLastSelectionStringNull) {
+                        mLastDirection = direction;
+                        mIsLastSelectionStringNull = false;
+                        return false;
+                    }
+                    mLastDirection = direction;
+                    axis =  binding.getSecondArgument(i);
+                    sendEvent = (binding.getThirdArgument(i) == 1);
+                    traverseGivenAxis(direction, axis, sendEvent, contentDescription);
+                    mLastDownEventHandled = true;
+                    break;
+                case ACTION_PERFORM_AXIS_TRANSITION:
+                    int fromAxis = binding.getFirstArgument(i);
+                    int toAxis = binding.getSecondArgument(i);
+                    sendEvent = (binding.getThirdArgument(i) == 1);
+                    prefromAxisTransition(fromAxis, toAxis, sendEvent, contentDescription);
+                    mLastDownEventHandled = true;
+                    break;
+                default:
+                    Log.w(LOG_TAG, "Unknown action code: " + actionCode);
+            }
+        }
+
+        return mLastDownEventHandled;
+    }
+
+    /**
+     * Set the current navigation axis which will be used while
+     * calling {@link #traverseCurrentAxis(int, boolean, String)}.
+     *
+     * @param axis The axis to set.
+     * @param sendEvent Whether to send an accessibility event to
+     *        announce the change.
+     */
+    private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) {
+        mCurrentAxis = axis;
+        if (sendEvent) {
+            AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent();
+            event.getText().add(String.valueOf(axis));
+            event.setContentDescription(contentDescription);
+            sendAccessibilityEvent(event);
+        }
+    }
+
+    /**
+     * Performs conditional transition one axis to another.
+     *
+     * @param fromAxis The axis which must be the current for the transition to occur.
+     * @param toAxis The axis to which to transition.
+     * @param sendEvent Flag if to send an event to announce successful transition.
+     * @param contentDescription A description of the performed action.
+     */
+    private void prefromAxisTransition(int fromAxis, int toAxis, boolean sendEvent,
+            String contentDescription) {
+        if (mCurrentAxis == fromAxis) {
+            setCurrentAxis(toAxis, sendEvent, contentDescription);
+        }
+    }
+
+    /**
+     * Traverse the document along the current navigation axis.
+     *
+     * @param direction The direction of traversal.
+     * @param sendEvent Whether to send an accessibility event to
+     *        announce the change.
+     * @param contentDescription A description of the performed action.
+     * @see #setCurrentAxis(int, boolean, String)
+     */
+    private boolean traverseCurrentAxis(int direction, boolean sendEvent,
+            String contentDescription) {
+        return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
+    }
+
+    /**
+     * Traverse the document along the given navigation axis.
+     *
+     * @param direction The direction of traversal.
+     * @param axis The axis along which to traverse.
+     * @param sendEvent Whether to send an accessibility event to
+     *        announce the change.
+     * @param contentDescription A description of the performed action.
+     */
+    private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
+            String contentDescription) {
+        // if the axis is the default let WebView handle the event
+        if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
+            return false;
+        }
+        WebViewCore webViewCore = mWebView.getWebViewCore();
+        if (webViewCore != null) {
+            AccessibilityEvent event = null;
+            if (sendEvent) {
+                event = getPartialyPopulatedAccessibilityEvent();
+                // the text will be set upon receiving the selection string
+                event.setContentDescription(contentDescription);
+            }
+            mScheduledEventStack.push(event);
+            webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
+        }
+        return true;
+    }
+
+    /**
+     * Called when the <code>selectionString</code> has changed.
+     */
+    public void onSelectionStringChange(String selectionString) {
+        mIsLastSelectionStringNull = (selectionString == null);
+        AccessibilityEvent event = mScheduledEventStack.pop();
+        if (event != null) {
+            event.getText().add(selectionString);
+            sendAccessibilityEvent(event);
+        }
+    }
+
+    /**
+     * Sends an {@link AccessibilityEvent}.
+     *
+     * @param event The event to send.
+     */
+    private void sendAccessibilityEvent(AccessibilityEvent event) {
+        if (DEBUG) {
+            Log.d(LOG_TAG, "Dispatching: " + event);
+        }
+        AccessibilityManager.getInstance(mWebView.getContext()).sendAccessibilityEvent(event);
+    }
+
+    /**
+     * @return An accessibility event whose members are populated except its
+     *         text and content description.
+     */
+    private AccessibilityEvent getPartialyPopulatedAccessibilityEvent() {
+        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SELECTED);
+        event.setClassName(mWebView.getClass().getName());
+        event.setPackageName(mWebView.getContext().getPackageName());
+        event.setEnabled(mWebView.isEnabled());
+        return event;
+    }
+
+    /**
+     * Ensures that the Web content key bindings are loaded.
+     */
+    private void ensureWebContentKeyBindings() {
+        if (sBindings.size() > 0) {
+            return;
+        }
+
+        String webContentKeyBindingsString  = Settings.Secure.getString(
+                mWebView.getContext().getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
+
+        SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
+        semiColonSplitter.setString(webContentKeyBindingsString);
+
+        ArrayList<AccessibilityWebContentKeyBinding> bindings =
+            new ArrayList<AccessibilityWebContentKeyBinding>();
+
+        while (semiColonSplitter.hasNext()) {
+            String bindingString = semiColonSplitter.next();
+            if (TextUtils.isEmpty(bindingString)) {
+                Log.e(LOG_TAG, "Malformed Web content key binding: "
+                        + webContentKeyBindingsString);
+                continue;
+            }
+            String[] keyValueArray = bindingString.split("=");
+            if (keyValueArray.length != 2) {
+                Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " +
+                        bindingString);
+                continue;
+            }
+            try {
+                SimpleStringSplitter colonSplitter = new SimpleStringSplitter(':');//remove
+                int key = Integer.decode(keyValueArray[0].trim());
+                String[] actionStrings = keyValueArray[1].split(":");
+                int[] actions = new int[actionStrings.length];
+                for (int i = 0, count = actions.length; i < count; i++) {
+                    actions[i] = Integer.decode(actionStrings[i].trim());
+                }
+
+                bindings.add(new AccessibilityWebContentKeyBinding(key, actions));
+            } catch (NumberFormatException nfe) {
+                Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
+            }
+        }
+
+        for (AccessibilityWebContentKeyBinding binding : bindings) {
+            sBindings.put(binding.getKey(), binding);
+        }
+    }
+
+    /**
+     * Represents a web content key-binding.
+     */
+    private class AccessibilityWebContentKeyBinding {
+
+        private static final int OFFSET_META_STATE = 0x00000010;
+
+        private static final int MASK_META_STATE = 0xFFFF0000;
+
+        private static final int OFFSET_KEY_CODE = 0x00000000;
+
+        private static final int MASK_KEY_CODE = 0x0000FFFF;
+
+        private static final int OFFSET_ACTION = 0x00000018;
+
+        private static final int MASK_ACTION = 0xFF000000;
+
+        private static final int OFFSET_FIRST_ARGUMENT = 0x00000010;
+
+        private static final int MASK_FIRST_ARGUMENT = 0x00FF0000;
+
+        private static final int OFFSET_SECOND_ARGUMENT = 0x00000008;
+
+        private static final int MASK_SECOND_ARGUMENT = 0x0000FF00;
+
+        private static final int OFFSET_THIRD_ARGUMENT = 0x00000000;
+
+        private static final int MASK_THIRD_ARGUMENT = 0x000000FF;
+
+        private int mKey;
+
+        private int [] mActionSequence;
+
+        /**
+         * @return The binding key with key code and meta state.
+         *
+         * @see #MASK_KEY_CODE
+         * @see #MASK_META_STATE
+         * @see #OFFSET_KEY_CODE
+         * @see #OFFSET_META_STATE
+         */
+        public int getKey() {
+            return mKey;
+        }
+
+        /**
+         * @return The key code of the binding key.
+         */
+        public int getKeyCode() {
+            return (mKey & MASK_KEY_CODE) >> OFFSET_KEY_CODE;
+        }
+
+        /**
+         * @return The meta state of the binding key.
+         */
+        public int getMetaState() {
+            return (mKey & MASK_META_STATE) >> OFFSET_META_STATE;
+        }
+
+        /**
+         * @return The number of actions in the key binding.
+         */
+        public int getActionCount() {
+            return mActionSequence.length;
+        }
+
+        /**
+         * @param index The action for a given action <code>index</code>.
+         */
+        public int getAction(int index) {
+            return mActionSequence[index];
+        }
+
+        /**
+         * @param index The action code for a given action <code>index</code>.
+         */
+        public int getActionCode(int index) {
+            return (mActionSequence[index] & MASK_ACTION) >> OFFSET_ACTION;
+        }
+
+        /**
+         * @param index The first argument for a given action <code>index</code>.
+         */
+        public int getFirstArgument(int index) {
+            return (mActionSequence[index] & MASK_FIRST_ARGUMENT) >> OFFSET_FIRST_ARGUMENT;
+        }
+
+        /**
+         * @param index The second argument for a given action <code>index</code>.
+         */
+        public int getSecondArgument(int index) {
+            return (mActionSequence[index] & MASK_SECOND_ARGUMENT) >> OFFSET_SECOND_ARGUMENT;
+        }
+
+        /**
+         * @param index The third argument for a given action <code>index</code>.
+         */
+        public int getThirdArgument(int index) {
+            return (mActionSequence[index] & MASK_THIRD_ARGUMENT) >> OFFSET_THIRD_ARGUMENT;
+        }
+
+        /**
+         * Creates a new instance.
+         * @param key The key for the binding (key and meta state)
+         * @param actionSequence The sequence of action for the binding.
+         * @see #getKey()
+         */
+        public AccessibilityWebContentKeyBinding(int key, int[] actionSequence) {
+            mKey = key;
+            mActionSequence = actionSequence;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("key: ");
+            builder.append(getKey());
+            builder.append(", metaState: ");
+            builder.append(getMetaState());
+            builder.append(", keyCode: ");
+            builder.append(getKeyCode());
+            builder.append(", actions[");
+            for (int i = 0, count = getActionCount(); i < count; i++) {
+                builder.append("{actionCode");
+                builder.append(i);
+                builder.append(": ");
+                builder.append(getActionCode(i));
+                builder.append(", firstArgument: ");
+                builder.append(getFirstArgument(i));
+                builder.append(", secondArgument: ");
+                builder.append(getSecondArgument(i));
+                builder.append(", thirdArgument: ");
+                builder.append(getThirdArgument(i));
+                builder.append("}");
+            }
+            builder.append("]");
+            return builder.toString();
+        }
+    }
+}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index a2c80f2..efe4b9d 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -38,6 +38,8 @@
 
 import junit.framework.Assert;
 
+import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
 import java.net.URLEncoder;
@@ -72,6 +74,9 @@
     // queue has been cleared,they are ignored.
     private boolean mBlockMessages = false;
 
+    private static String sDatabaseDirectory;
+    private static String sCacheDirectory;
+
     // Is this frame the main frame?
     private boolean mIsMainFrame;
 
@@ -224,6 +229,13 @@
         AssetManager am = context.getAssets();
         nativeCreateFrame(w, am, proxy.getBackForwardList());
 
+        if (sDatabaseDirectory == null) {
+            sDatabaseDirectory = appContext.getDatabasePath("dummy").getParent();
+        }
+        if (sCacheDirectory == null) {
+            sCacheDirectory = appContext.getCacheDir().getAbsolutePath();
+        }
+
         if (DebugFlags.BROWSER_FRAME) {
             Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
         }
@@ -294,6 +306,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 +534,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 +557,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 +655,112 @@
     }
 
     /**
+     * Called by JNI. Gets the application's database directory, excluding the trailing slash.
+     * @return String The application's database directory
+     */
+    private static String getDatabaseDirectory() {
+        return sDatabaseDirectory;
+    }
+
+    /**
+     * Called by JNI. Gets the application's cache directory, excluding the trailing slash.
+     * @return String The application's cache directory
+     */
+    private static String getCacheDirectory() {
+        return sCacheDirectory;
+    }
+
+    /**
+     * Called by JNI.
+     * Read from an InputStream into a supplied byte[]
+     * This method catches any exceptions so they don't crash the JVM.
+     * @param inputStream InputStream to read from.
+     * @param output Bytearray that gets the output.
+     * @return the number of bytes read, or -i if then end of stream has been reached
+     */
+    private static int readFromStream(InputStream inputStream, byte[] output) {
+        try {
+            return inputStream.read(output);
+        } catch(java.io.IOException e) {
+            // If we get an exception, return end of stream
+            return -1;
+        }
+    }
+
+    /**
+     * Get the InputStream for an Android resource
+     * There are three different kinds of android resources:
+     * - file:///android_res
+     * - file:///android_asset
+     * - content://
+     * @param url The url to load.
+     * @return An InputStream to the android resource
+     */
+    private InputStream inputStreamForAndroidResource(String url, int type) {
+        final int RESOURCE = 1;
+        final int ASSET = 2;
+        final int CONTENT = 3;
+
+        if (type == RESOURCE) {
+            // file:///android_res
+            if (url == null || url.length() == 0) {
+                Log.e(LOGTAG, "url has length 0 " + url);
+                return null;
+            }
+            int slash = url.indexOf('/');
+            int dot = url.indexOf('.', slash);
+            if (slash == -1 || dot == -1) {
+                Log.e(LOGTAG, "Incorrect res path: " + url);
+                return null;
+            }
+            String subClassName = url.substring(0, slash);
+            String fieldName = url.substring(slash + 1, dot);
+            String errorMsg = null;
+            try {
+                final Class<?> d = mContext.getApplicationContext()
+                        .getClassLoader().loadClass(
+                                mContext.getPackageName() + ".R$"
+                                        + subClassName);
+                final java.lang.reflect.Field field = d.getField(fieldName);
+                final int id = field.getInt(null);
+                TypedValue value = new TypedValue();
+                mContext.getResources().getValue(id, value, true);
+                if (value.type == TypedValue.TYPE_STRING) {
+                    return mContext.getAssets().openNonAsset(
+                            value.assetCookie, value.string.toString(),
+                            AssetManager.ACCESS_STREAMING);
+                } else {
+                    // Old stack only supports TYPE_STRING for res files
+                    Log.e(LOGTAG, "not of type string: " + url);
+                    return null;
+                }
+            } catch (Exception e) {
+                Log.e(LOGTAG, "Exception: " + url);
+                return null;
+            }
+
+        } else if (type == ASSET) {
+            // file:///android_asset
+            try {
+                AssetManager assets = mContext.getAssets();
+                return assets.open(url, AssetManager.ACCESS_STREAMING);
+            } catch (IOException e) {
+                return null;
+            }
+        } else if (type == CONTENT) {
+            try {
+                Uri uri = Uri.parse(url);
+                return mContext.getContentResolver().openInputStream(uri);
+            } catch (Exception e) {
+                Log.e(LOGTAG, "Exception: " + url);
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
      * Start loading a resource.
      * @param loaderHandle The native ResourceLoader that is the target of the
      *                     data.
@@ -842,6 +986,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;
@@ -871,6 +1016,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 "";
@@ -893,6 +1042,40 @@
         return mContext.getResources().getDisplayMetrics().density;
     }
 
+    /**
+     * Called by JNI when the native HTTP stack gets an authentication request.
+     *
+     * We delegate the request to CallbackProxy, and route its response to
+     * {@link #nativeAuthenticationProceed(int, String, String)} or
+     * {@link #nativeAuthenticationCancel(int)}.
+     *
+     * We don't care what thread the callback is invoked on. All threading is
+     * handled on the C++ side, because the WebKit thread may be blocked on a
+     * synchronous call and unable to pump our MessageQueue.
+     */
+    private void didReceiveAuthenticationChallenge(
+            final int handle, String host, String realm, final boolean useCachedCredentials) {
+
+        HttpAuthHandler handler = new HttpAuthHandler() {
+
+            @Override
+            public boolean useHttpAuthUsernamePassword() {
+                return useCachedCredentials;
+            }
+
+            @Override
+            public void proceed(String username, String password) {
+                nativeAuthenticationProceed(handle, username, password);
+            }
+
+            @Override
+            public void cancel() {
+                nativeAuthenticationCancel(handle);
+            }
+        };
+        mCallbackProxy.onReceivedHttpAuthRequest(handler, host, realm);
+    }
+
     //==========================================================================
     // native functions
     //==========================================================================
@@ -1006,5 +1189,10 @@
      */
     private native HashMap getFormTextData();
 
+    private native String nativeSaveWebArchive(String basename, boolean autoname);
+
     private native void nativeOrientationChanged(int orientation);
+
+    private native void nativeAuthenticationProceed(int handle, String username, String password);
+    private native void nativeAuthenticationCancel(int handle);
 }
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index d65c106..b00f88c 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;
@@ -253,17 +256,10 @@
         // 32-bit reads and writes.
         switch (msg.what) {
             case PAGE_STARTED:
-                // every time we start a new page, we want to reset the
-                // WebView certificate:
-                // if the new site is secure, we will reload it and get a
-                // new certificate set;
-                // if the new site is not secure, the certificate must be
-                // null, and that will be the case
-                mWebView.setCertificate(null);
+                String startedUrl = msg.getData().getString("url");
+                mWebView.onPageStarted(startedUrl);
                 if (mWebViewClient != null) {
-                    mWebViewClient.onPageStarted(mWebView,
-                            msg.getData().getString("url"),
-                            (Bitmap) msg.obj);
+                    mWebViewClient.onPageStarted(mWebView, startedUrl, (Bitmap) msg.obj);
                 }
                 break;
 
@@ -500,18 +496,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 +535,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 +583,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 +642,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 +759,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 +785,9 @@
                 mWebView.setHttpAuthUsernamePassword(
                         host, realm, username, password);
                 break;
+            case SET_INSTALLABLE_WEBAPP:
+                mWebChromeClient.setInstallableWebApp();
+                break;
         }
     }
 
@@ -1087,10 +1125,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;
@@ -1172,9 +1215,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.
@@ -1432,6 +1473,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) {
@@ -1448,13 +1507,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 {
@@ -1483,4 +1543,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/DeviceOrientationManager.java b/core/java/android/webkit/DeviceOrientationManager.java
new file mode 100644
index 0000000..aac2f43
--- /dev/null
+++ b/core/java/android/webkit/DeviceOrientationManager.java
@@ -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.
+ */
+
+package android.webkit;
+
+/**
+ * This class is simply a container for the methods used to implement DeviceOrientation,
+ * including the mock DeviceOrientationClient for use in LayoutTests.
+ *
+ * This could be part of WebViewCore, but have moved it to its own class to
+ * avoid bloat there.
+ * @hide
+ */
+public final class DeviceOrientationManager {
+    private WebViewCore mWebViewCore;
+    private DeviceOrientationService mService;
+
+    public DeviceOrientationManager(WebViewCore webViewCore) {
+        mWebViewCore = webViewCore;
+    }
+
+    /**
+     * Sets whether the Page for this WebViewCore should use a mock DeviceOrientation
+     * client.
+     */
+    public void useMock() {
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+        nativeUseMock(mWebViewCore);
+    }
+
+    /**
+     * Set the position for the mock DeviceOrientation service for this WebViewCore.
+     */
+    public void setMockOrientation(boolean canProvideAlpha, double alpha, boolean canProvideBeta,
+            double beta, boolean canProvideGamma, double gamma) {
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+        nativeSetMockOrientation(mWebViewCore, canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
+    }
+
+    public void onOrientationChange(Double alpha, Double beta, Double gamma) {
+        nativeOnOrientationChange(mWebViewCore,
+                alpha != null, alpha != null ? alpha.doubleValue() : 0.0,
+                beta != null, beta != null ? beta.doubleValue() : 0.0,
+                gamma != null, gamma != null ? gamma.doubleValue() : 0.0);
+    }
+
+    // Native functions
+    private static native void nativeUseMock(WebViewCore webViewCore);
+    private static native void nativeSetMockOrientation(WebViewCore webViewCore,
+            boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
+            boolean canProvideGamma, double gamma);
+    private static native void nativeOnOrientationChange(WebViewCore webViewCore,
+            boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
+            boolean canProvideGamma, double gamma);
+}
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
new file mode 100755
index 0000000..9b866d3
--- /dev/null
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.webkit.DeviceOrientationManager;
+import java.lang.Runnable;
+import java.util.List;
+
+
+final class DeviceOrientationService implements SensorEventListener {
+    // The gravity vector expressed in the body frame.
+    private float[] mGravityVector;
+    // The geomagnetic vector expressed in the body frame.
+    private float[] mMagneticFieldVector;
+
+    private DeviceOrientationManager mManager;
+    private boolean mIsRunning;
+    private Handler mHandler;
+    private SensorManager mSensorManager;
+    private Context mContext;
+    private Double mAlpha;
+    private Double mBeta;
+    private Double mGamma;
+    private boolean mHaveSentErrorEvent;
+
+    private static final double DELTA_DEGRESS = 1.0;
+
+    public DeviceOrientationService(DeviceOrientationManager manager, Context context) {
+        mManager = manager;
+        assert(mManager != null);
+        mContext = context;
+        assert(mContext != null);
+     }
+
+    public void start() {
+        mIsRunning = true;
+        registerForSensors();
+    }
+
+    public void stop() {
+        mIsRunning = false;
+        unregisterFromSensors();
+    }
+
+    public void suspend() {
+        if (mIsRunning) {
+            unregisterFromSensors();
+        }
+    }
+
+    public void resume() {
+        if (mIsRunning) {
+            registerForSensors();
+        }
+    }
+
+    private void sendErrorEvent() {
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+        // The spec requires that each listener receives the error event only once.
+        if (mHaveSentErrorEvent)
+            return;
+        mHaveSentErrorEvent = true;
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+                if (mIsRunning) {
+                    // The special case of all nulls is used to signify a failure to get data.
+                    mManager.onOrientationChange(null, null, null);
+                }
+            }
+        });
+    }
+
+    private void registerForSensors() {
+        if (mHandler == null) {
+            mHandler = new Handler();
+        }
+        if (!registerForAccelerometerSensor() || !registerForMagneticFieldSensor()) {
+            unregisterFromSensors();
+            sendErrorEvent();
+        }
+    }
+
+    private void getOrientationUsingGetRotationMatrix() {
+        if (mGravityVector == null || mMagneticFieldVector == null) {
+            return;
+        }
+
+        // Get the rotation matrix.
+        // The rotation matrix that transforms from the body frame to the earth frame.
+        float[] deviceRotationMatrix = new float[9];
+        if (!SensorManager.getRotationMatrix(
+                deviceRotationMatrix, null, mGravityVector, mMagneticFieldVector)) {
+            return;
+        }
+
+        // Convert rotation matrix to rotation angles.
+        // Assuming that the rotations are appied in the order listed at
+        // http://developer.android.com/reference/android/hardware/SensorEvent.html#values
+        // the rotations are applied about the same axes and in the same order as required by the
+        // API. The only conversions are sign changes as follows.
+        // The angles are in radians
+        float[] rotationAngles = new float[3];
+        SensorManager.getOrientation(deviceRotationMatrix, rotationAngles);
+        double alpha = Math.toDegrees(-rotationAngles[0]) - 90.0;
+        while (alpha < 0.0) { alpha += 360.0; } // [0, 360)
+        double beta = Math.toDegrees(-rotationAngles[1]);
+        while (beta < -180.0) { beta += 360.0; } // [-180, 180)
+        double gamma = Math.toDegrees(rotationAngles[2]);
+        while (gamma < -90.0) { gamma += 360.0; } // [-90, 90)
+
+        maybeSendChange(alpha, beta, gamma);
+    }
+
+    private SensorManager getSensorManager() {
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+        if (mSensorManager == null) {
+            mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+        }
+        return mSensorManager;
+    }
+
+    private boolean registerForAccelerometerSensor() {
+        List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_ACCELEROMETER);
+        if (sensors.isEmpty()) {
+            return false;
+        }
+        // TODO: Consider handling multiple sensors.
+        return getSensorManager().registerListener(
+                this, sensors.get(0), SensorManager.SENSOR_DELAY_FASTEST, mHandler);
+    }
+
+    private boolean registerForMagneticFieldSensor() {
+        List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
+        if (sensors.isEmpty()) {
+            return false;
+        }
+        // TODO: Consider handling multiple sensors.
+        return getSensorManager().registerListener(
+                this, sensors.get(0), SensorManager.SENSOR_DELAY_FASTEST, mHandler);
+    }
+
+    private void unregisterFromSensors() {
+        getSensorManager().unregisterListener(this);
+    }
+
+    private void maybeSendChange(double alpha, double beta, double gamma) {
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+        if (mAlpha == null || mBeta == null || mGamma == null
+                || Math.abs(alpha - mAlpha) > DELTA_DEGRESS
+                || Math.abs(beta - mBeta) > DELTA_DEGRESS
+                || Math.abs(gamma - mGamma) > DELTA_DEGRESS) {
+            mAlpha = alpha;
+            mBeta = beta;
+            mGamma = gamma;
+            mManager.onOrientationChange(mAlpha, mBeta, mGamma);
+            // Now that we have successfully sent some data, reset whether we've sent an error.
+            mHaveSentErrorEvent = false;
+        }
+    }
+
+    /**
+     * SensorEventListener implementation.
+     * Callbacks happen on the thread on which we registered - the WebCore thread.
+     */
+    public void onSensorChanged(SensorEvent event) {
+        assert(event.values.length == 3);
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+
+        if (!mIsRunning) {
+            return;
+        }
+
+        switch (event.sensor.getType()) {
+          case Sensor.TYPE_ACCELEROMETER:
+            if (mGravityVector == null) {
+                mGravityVector = new float[3];
+            }
+            mGravityVector[0] = event.values[0];
+            mGravityVector[1] = event.values[1];
+            mGravityVector[2] = event.values[2];
+            getOrientationUsingGetRotationMatrix();
+            break;
+          case Sensor.TYPE_MAGNETIC_FIELD:
+            if (mMagneticFieldVector == null) {
+                mMagneticFieldVector = new float[3];
+            }
+            mMagneticFieldVector[0] = event.values[0];
+            mMagneticFieldVector[1] = event.values[1];
+            mMagneticFieldVector[2] = event.values[2];
+            getOrientationUsingGetRotationMatrix();
+            break;
+          default:
+            assert(false);
+        }
+    }
+
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+    }
+}
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
new file mode 100644
index 0000000..27043e0
--- /dev/null
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextWatcher;
+import android.webkit.WebView;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
+        View.OnLongClickListener {
+    private View mCustomView;
+    private EditText mEditText;
+    private TextView mMatches;
+    private WebView mWebView;
+    private InputMethodManager mInput;
+    private Resources mResources;
+    private boolean mMatchesFound;
+    private int mNumberOfMatches;
+    private View mTitleBar;
+    private ActionMode mActionMode;
+
+    FindActionModeCallback(Context context) {
+        mCustomView = LayoutInflater.from(context).inflate(
+                com.android.internal.R.layout.webview_find, null);
+        mEditText = (EditText) mCustomView.findViewById(
+                com.android.internal.R.id.edit);
+        // Override long click so that select ActionMode is not opened, which
+        // would exit find ActionMode.
+        mEditText.setOnLongClickListener(this);
+        setText("");
+        mMatches = (TextView) mCustomView.findViewById(
+                com.android.internal.R.id.matches);
+        mInput = (InputMethodManager)
+                context.getSystemService(Context.INPUT_METHOD_SERVICE);
+        mResources = context.getResources();
+    }
+
+    void setTitleBar(View v) { mTitleBar = v; }
+
+    void finish() {
+        mActionMode.finish();
+    }
+
+    /*
+     * Place text in the text field so it can be searched for.  Need to press
+     * the find next or find previous button to find all of the matches.
+     */
+    void setText(String text) {
+        mEditText.setText(text);
+        Spannable span = (Spannable) mEditText.getText();
+        int length = span.length();
+        // Ideally, we would like to set the selection to the whole field,
+        // but this brings up the Text selection CAB, which dismisses this
+        // one.
+        Selection.setSelection(span, length, length);
+        // Necessary each time we set the text, so that this will watch
+        // changes to it.
+        span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+        mMatchesFound = false;
+    }
+
+    /*
+     * Set the WebView to search.  Must be non null, and set before calling
+     * startActionMode.
+     */
+    void setWebView(WebView webView) {
+        if (null == webView) {
+            throw new AssertionError("WebView supplied to "
+                    + "FindActionModeCallback cannot be null");
+        }
+        mWebView = webView;
+    }
+
+    /*
+     * Move the highlight to the next match.
+     * @param next If true, find the next match further down in the document.
+     *             If false, find the previous match, up in the document.
+     */
+    private void findNext(boolean next) {
+        if (mWebView == null) {
+            throw new AssertionError(
+                    "No WebView for FindActionModeCallback::findNext");
+        }
+        mWebView.findNext(next);
+    }
+
+    /*
+     * Highlight all the instances of the string from mEditText in mWebView.
+     */
+    void findAll() {
+        if (mWebView == null) {
+            throw new AssertionError(
+                    "No WebView for FindActionModeCallback::findAll");
+        }
+        CharSequence find = mEditText.getText();
+        if (0 == find.length()) {
+            mWebView.clearMatches();
+            mMatches.setVisibility(View.GONE);
+            mMatchesFound = false;
+        } else {
+            mMatchesFound = true;
+            mMatches.setVisibility(View.VISIBLE);
+            mNumberOfMatches = mWebView.findAll(find.toString());
+            if (0 == mNumberOfMatches) {
+                mMatches.setText(mResources.getString(
+                        com.android.internal.R.string.no_matches));
+            } else {
+                updateMatchesString();
+            }
+        }
+    }
+
+    /*
+     * Update the string which tells the user how many matches were found, and
+     * which match is currently highlighted.
+     */
+    private void updateMatchesString() {
+        String template = mResources.getQuantityString(
+                com.android.internal.R.plurals.matches_found, mNumberOfMatches,
+                mWebView.findIndex() + 1, mNumberOfMatches);
+
+        mMatches.setText(template);
+    }
+
+    // OnLongClickListener implementation
+
+    @Override
+    public boolean onLongClick(View v) { return true; }
+
+    // ActionMode.Callback implementation
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        mode.setCustomView(mCustomView);
+        mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find,
+                menu);
+        mActionMode = mode;
+        Editable edit = mEditText.getText();
+        Selection.setSelection(edit, edit.length());
+        mMatches.setVisibility(View.GONE);
+        mMatchesFound = false;
+        mMatches.setText("0");
+        mEditText.requestFocus();
+        mInput.showSoftInput(mEditText, 0);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        if (mTitleBar != null) mWebView.setEmbeddedTitleBar(mTitleBar);
+        mWebView.notifyFindDialogDismissed();
+        mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        return false;
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        if (!mMatchesFound) {
+            findAll();
+            return true;
+        }
+        switch(item.getItemId()) {
+            case com.android.internal.R.id.find_prev:
+                findNext(false);
+                break;
+            case com.android.internal.R.id.find_next:
+                findNext(true);
+                break;
+            default:
+                return false;
+        }
+        updateMatchesString();
+        return true;
+    }
+
+    // TextWatcher implementation
+
+    @Override
+    public void beforeTextChanged(CharSequence s,
+                                  int start,
+                                  int count,
+                                  int after) {
+        // Does nothing.  Needed to implement TextWatcher.
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s,
+                              int start,
+                              int before,
+                              int count) {
+        findAll();
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        // Does nothing.  Needed to implement TextWatcher.
+    }
+
+}
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/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java
index ed85da6..1797eb4 100644
--- a/core/java/android/webkit/HttpAuthHandler.java
+++ b/core/java/android/webkit/HttpAuthHandler.java
@@ -16,151 +16,19 @@
 
 package android.webkit;
 
-import android.os.Bundle;
 import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.ListIterator;
-import java.util.LinkedList;
 
 /**
- * HTTP authentication handler: local handler that takes care
- * of HTTP authentication requests. This class is passed as a
- * parameter to BrowserCallback.displayHttpAuthDialog and is
- * meant to receive the user's response.
+ * HTTP authentication request that must be handled by the user interface.
+ * WebView creates the object and hands it to the current {@link WebViewClient},
+ * which must call either {@link #proceed(String, String)} or {@link #cancel()}.
  */
 public class HttpAuthHandler extends Handler {
-    /* It is important that the handler is in Network, because
-     * we want to share it accross multiple loaders and windows
-     * (like our subwindow and the main window).
-     */
-
-    private static final String LOGTAG = "network";
 
     /**
-     * Network.
+     * Package-private constructor needed for API compatibility.
      */
-    private Network mNetwork;
-
-    /**
-     * Loader queue.
-     */
-    private LinkedList<LoadListener> mLoaderQueue;
-
-
-    // Message id for handling the user response
-    private static final int AUTH_PROCEED = 100;
-    private static final int AUTH_CANCEL = 200;
-
-    // Use to synchronize when making synchronous calls to
-    // onReceivedHttpAuthRequest(). We can't use a single Boolean object for
-    // both the lock and the state, because Boolean is immutable.
-    Object mRequestInFlightLock = new Object();
-    boolean mRequestInFlight;
-    String mUsername;
-    String mPassword;
-
-    /**
-     * Creates a new HTTP authentication handler with an empty
-     * loader queue
-     *
-     * @param network The parent network object
-     */
-    /* package */ HttpAuthHandler(Network network) {
-        mNetwork = network;
-        mLoaderQueue = new LinkedList<LoadListener>();
-    }
-
-
-    @Override
-    public void handleMessage(Message msg) {
-        LoadListener loader = null;
-        synchronized (mLoaderQueue) {
-            loader = mLoaderQueue.poll();
-        }
-        assert(loader.isSynchronous() == false);
-
-        switch (msg.what) {
-            case AUTH_PROCEED:
-                String username = msg.getData().getString("username");
-                String password = msg.getData().getString("password");
-
-                loader.handleAuthResponse(username, password);
-                break;
-
-            case AUTH_CANCEL:
-                loader.handleAuthResponse(null, null);
-                break;
-        }
-
-        processNextLoader();
-    }
-
-    /**
-     * Helper method used to unblock handleAuthRequest(), which in the case of a
-     * synchronous request will wait for proxy.onReceivedHttpAuthRequest() to
-     * call back to either proceed() or cancel().
-     *
-     * @param username The username to use for authentication
-     * @param password The password to use for authentication
-     * @return True if the request is synchronous and handleAuthRequest() has
-     * been unblocked
-     */
-    private boolean handleResponseForSynchronousRequest(String username, String password) {
-        LoadListener loader = null;
-        synchronized (mLoaderQueue) {
-            loader = mLoaderQueue.peek();
-        }
-        if (loader.isSynchronous()) {
-            mUsername = username;
-            mPassword = password;
-            return true;
-        }
-        return false;
-    }
-
-    private void signalRequestComplete() {
-        synchronized (mRequestInFlightLock) {
-            assert(mRequestInFlight);
-            mRequestInFlight = false;
-            mRequestInFlightLock.notify();
-        }
-    }
-
-    /**
-     * Proceed with the authorization with the given credentials
-     *
-     * May be called on the UI thread, rather than the WebCore thread.
-     *
-     * @param username The username to use for authentication
-     * @param password The password to use for authentication
-     */
-    public void proceed(String username, String password) {
-        if (handleResponseForSynchronousRequest(username, password)) {
-            signalRequestComplete();
-            return;
-        }
-        Message msg = obtainMessage(AUTH_PROCEED);
-        msg.getData().putString("username", username);
-        msg.getData().putString("password", password);
-        sendMessage(msg);
-        signalRequestComplete();
-    }
-
-    /**
-     * Cancel the authorization request
-     *
-     * May be called on the UI thread, rather than the WebCore thread.
-     *
-     */
-    public void cancel() {
-        if (handleResponseForSynchronousRequest(null, null)) {
-            signalRequestComplete();
-            return;
-        }
-        sendMessage(obtainMessage(AUTH_CANCEL));
-        signalRequestComplete();
+    HttpAuthHandler() {
     }
 
     /**
@@ -168,113 +36,18 @@
      * (ie, if we did not fail trying to use them last time)
      */
     public boolean useHttpAuthUsernamePassword() {
-        LoadListener loader = null;
-        synchronized (mLoaderQueue) {
-            loader = mLoaderQueue.peek();
-        }
-        if (loader != null) {
-            return !loader.authCredentialsInvalid();
-        }
-
         return false;
     }
 
     /**
-     * Enqueues the loader, if the loader is the only element
-     * in the queue, starts processing the loader
-     *
-     * @param loader The loader that resulted in this http
-     * authentication request
+     * Cancel the authorization request.
      */
-    /* package */ void handleAuthRequest(LoadListener loader) {
-        // The call to proxy.onReceivedHttpAuthRequest() may be asynchronous. If
-        // the request is synchronous, we must block here until we have a
-        // response.
-        if (loader.isSynchronous()) {
-            // If there's a request in flight, wait for it to complete. The
-            // response will queue a message on this thread.
-            waitForRequestToComplete();
-            // Make a request to the proxy for this request, jumping the queue.
-            // We use the queue so that the loader is present in
-            // useHttpAuthUsernamePassword().
-            synchronized (mLoaderQueue) {
-                mLoaderQueue.addFirst(loader);
-            }
-            processNextLoader();
-            // Wait for this request to complete.
-            waitForRequestToComplete();
-            // Pop the loader from the queue.
-            synchronized (mLoaderQueue) {
-                assert(mLoaderQueue.peek() == loader);
-                mLoaderQueue.poll();
-            }
-            // Call back.
-            loader.handleAuthResponse(mUsername, mPassword);
-            // The message queued by the response from the last asynchronous
-            // request, if present, will start the next request.
-            return;
-        }
-
-        boolean processNext = false;
-
-        synchronized (mLoaderQueue) {
-            mLoaderQueue.offer(loader);
-            processNext =
-                (mLoaderQueue.size() == 1);
-        }
-
-        if (processNext) {
-            processNextLoader();
-        }
+    public void cancel() {
     }
 
     /**
-     * Wait for the request in flight, if any, to complete
+     * Proceed with the authorization with the given credentials.
      */
-    private void waitForRequestToComplete() {
-        synchronized (mRequestInFlightLock) {
-            while (mRequestInFlight) {
-                try {
-                    mRequestInFlightLock.wait();
-                } catch(InterruptedException e) {
-                    Log.e(LOGTAG, "Interrupted while waiting for request to complete");
-                }
-            }
-        }
-    }
-
-    /**
-     * Process the next loader in the queue (helper method)
-     */
-    private void processNextLoader() {
-        LoadListener loader = null;
-        synchronized (mLoaderQueue) {
-            loader = mLoaderQueue.peek();
-        }
-        if (loader != null) {
-            synchronized (mRequestInFlightLock) {
-                assert(mRequestInFlight == false);
-                mRequestInFlight = true;
-            }
-
-            CallbackProxy proxy = loader.getFrame().getCallbackProxy();
-
-            String hostname = loader.proxyAuthenticate() ?
-                mNetwork.getProxyHostname() : loader.host();
-
-            String realm = loader.realm();
-
-            proxy.onReceivedHttpAuthRequest(this, hostname, realm);
-        }
-    }
-
-    /**
-     * Informs the WebView of a new set of credentials.
-     * @hide Pending API council review
-     */
-    public static void onReceivedCredentials(LoadListener loader,
-            String host, String realm, String username, String password) {
-        CallbackProxy proxy = loader.getFrame().getCallbackProxy();
-        proxy.onReceivedHttpAuthCredentials(host, realm, username, password);
+    public void proceed(String username, String password) {
     }
 }
diff --git a/core/java/android/webkit/HttpAuthHandlerImpl.java b/core/java/android/webkit/HttpAuthHandlerImpl.java
new file mode 100644
index 0000000..ac05125
--- /dev/null
+++ b/core/java/android/webkit/HttpAuthHandlerImpl.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.ListIterator;
+import java.util.LinkedList;
+
+/**
+ * HttpAuthHandler implementation is used only by the Android Java HTTP stack.
+ * <p>
+ * This class is not needed when we're using the Chromium HTTP stack.
+ */
+class HttpAuthHandlerImpl extends HttpAuthHandler {
+    /*
+     * It is important that the handler is in Network, because we want to share
+     * it accross multiple loaders and windows (like our subwindow and the main
+     * window).
+     */
+
+    private static final String LOGTAG = "network";
+
+    /**
+     * Network.
+     */
+    private Network mNetwork;
+
+    /**
+     * Loader queue.
+     */
+    private LinkedList<LoadListener> mLoaderQueue;
+
+
+    // Message id for handling the user response
+    private static final int AUTH_PROCEED = 100;
+    private static final int AUTH_CANCEL = 200;
+
+    // Use to synchronize when making synchronous calls to
+    // onReceivedHttpAuthRequest(). We can't use a single Boolean object for
+    // both the lock and the state, because Boolean is immutable.
+    Object mRequestInFlightLock = new Object();
+    boolean mRequestInFlight;
+    String mUsername;
+    String mPassword;
+
+    /**
+     * Creates a new HTTP authentication handler with an empty
+     * loader queue
+     *
+     * @param network The parent network object
+     */
+    /* package */ HttpAuthHandlerImpl(Network network) {
+        mNetwork = network;
+        mLoaderQueue = new LinkedList<LoadListener>();
+    }
+
+
+    @Override
+    public void handleMessage(Message msg) {
+        LoadListener loader = null;
+        synchronized (mLoaderQueue) {
+            loader = mLoaderQueue.poll();
+        }
+        assert(loader.isSynchronous() == false);
+
+        switch (msg.what) {
+            case AUTH_PROCEED:
+                String username = msg.getData().getString("username");
+                String password = msg.getData().getString("password");
+
+                loader.handleAuthResponse(username, password);
+                break;
+
+            case AUTH_CANCEL:
+                loader.handleAuthResponse(null, null);
+                break;
+        }
+
+        processNextLoader();
+    }
+
+    /**
+     * Helper method used to unblock handleAuthRequest(), which in the case of a
+     * synchronous request will wait for proxy.onReceivedHttpAuthRequest() to
+     * call back to either proceed() or cancel().
+     *
+     * @param username The username to use for authentication
+     * @param password The password to use for authentication
+     * @return True if the request is synchronous and handleAuthRequest() has
+     * been unblocked
+     */
+    private boolean handleResponseForSynchronousRequest(String username, String password) {
+        LoadListener loader = null;
+        synchronized (mLoaderQueue) {
+            loader = mLoaderQueue.peek();
+        }
+        if (loader.isSynchronous()) {
+            mUsername = username;
+            mPassword = password;
+            return true;
+        }
+        return false;
+    }
+
+    private void signalRequestComplete() {
+        synchronized (mRequestInFlightLock) {
+            assert(mRequestInFlight);
+            mRequestInFlight = false;
+            mRequestInFlightLock.notify();
+        }
+    }
+
+    /**
+     * Proceed with the authorization with the given credentials
+     *
+     * May be called on the UI thread, rather than the WebCore thread.
+     *
+     * @param username The username to use for authentication
+     * @param password The password to use for authentication
+     */
+    public void proceed(String username, String password) {
+        if (handleResponseForSynchronousRequest(username, password)) {
+            signalRequestComplete();
+            return;
+        }
+        Message msg = obtainMessage(AUTH_PROCEED);
+        msg.getData().putString("username", username);
+        msg.getData().putString("password", password);
+        sendMessage(msg);
+        signalRequestComplete();
+    }
+
+    /**
+     * Cancel the authorization request
+     *
+     * May be called on the UI thread, rather than the WebCore thread.
+     *
+     */
+    public void cancel() {
+        if (handleResponseForSynchronousRequest(null, null)) {
+            signalRequestComplete();
+            return;
+        }
+        sendMessage(obtainMessage(AUTH_CANCEL));
+        signalRequestComplete();
+    }
+
+    /**
+     * @return True if we can use user credentials on record
+     * (ie, if we did not fail trying to use them last time)
+     */
+    public boolean useHttpAuthUsernamePassword() {
+        LoadListener loader = null;
+        synchronized (mLoaderQueue) {
+            loader = mLoaderQueue.peek();
+        }
+        if (loader != null) {
+            return !loader.authCredentialsInvalid();
+        }
+
+        return false;
+    }
+
+    /**
+     * Enqueues the loader, if the loader is the only element
+     * in the queue, starts processing the loader
+     *
+     * @param loader The loader that resulted in this http
+     * authentication request
+     */
+    /* package */ void handleAuthRequest(LoadListener loader) {
+        // The call to proxy.onReceivedHttpAuthRequest() may be asynchronous. If
+        // the request is synchronous, we must block here until we have a
+        // response.
+        if (loader.isSynchronous()) {
+            // If there's a request in flight, wait for it to complete. The
+            // response will queue a message on this thread.
+            waitForRequestToComplete();
+            // Make a request to the proxy for this request, jumping the queue.
+            // We use the queue so that the loader is present in
+            // useHttpAuthUsernamePassword().
+            synchronized (mLoaderQueue) {
+                mLoaderQueue.addFirst(loader);
+            }
+            processNextLoader();
+            // Wait for this request to complete.
+            waitForRequestToComplete();
+            // Pop the loader from the queue.
+            synchronized (mLoaderQueue) {
+                assert(mLoaderQueue.peek() == loader);
+                mLoaderQueue.poll();
+            }
+            // Call back.
+            loader.handleAuthResponse(mUsername, mPassword);
+            // The message queued by the response from the last asynchronous
+            // request, if present, will start the next request.
+            return;
+        }
+
+        boolean processNext = false;
+
+        synchronized (mLoaderQueue) {
+            mLoaderQueue.offer(loader);
+            processNext =
+                (mLoaderQueue.size() == 1);
+        }
+
+        if (processNext) {
+            processNextLoader();
+        }
+    }
+
+    /**
+     * Wait for the request in flight, if any, to complete
+     */
+    private void waitForRequestToComplete() {
+        synchronized (mRequestInFlightLock) {
+            while (mRequestInFlight) {
+                try {
+                    mRequestInFlightLock.wait();
+                } catch(InterruptedException e) {
+                    Log.e(LOGTAG, "Interrupted while waiting for request to complete");
+                }
+            }
+        }
+    }
+
+    /**
+     * Process the next loader in the queue (helper method)
+     */
+    private void processNextLoader() {
+        LoadListener loader = null;
+        synchronized (mLoaderQueue) {
+            loader = mLoaderQueue.peek();
+        }
+        if (loader != null) {
+            synchronized (mRequestInFlightLock) {
+                assert(mRequestInFlight == false);
+                mRequestInFlight = true;
+            }
+
+            CallbackProxy proxy = loader.getFrame().getCallbackProxy();
+
+            String hostname = loader.proxyAuthenticate() ?
+                mNetwork.getProxyHostname() : loader.host();
+
+            String realm = loader.realm();
+
+            proxy.onReceivedHttpAuthRequest(this, hostname, realm);
+        }
+    }
+
+    /**
+     * Informs the WebView of a new set of credentials.
+     * @hide Pending API council review
+     */
+    public static void onReceivedCredentials(LoadListener loader,
+            String host, String realm, String username, String password) {
+        CallbackProxy proxy = loader.getFrame().getCallbackProxy();
+        proxy.onReceivedHttpAuthCredentials(host, realm, username, password);
+    }
+}
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index 1f8d53f..908526f 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.net.Uri;
 import android.os.Handler;
 import android.os.Message;
 import android.util.Log;
@@ -52,12 +53,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
@@ -271,6 +275,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..8a55c48 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;
     }
 
     /*
@@ -681,7 +683,7 @@
                         String host = mAuthHeader.isProxy() ?
                                 Network.getInstance(mContext).getProxyHostname() :
                                 mUri.mHost;
-                        HttpAuthHandler.onReceivedCredentials(this, host,
+                        HttpAuthHandlerImpl.onReceivedCredentials(this, host,
                                 mAuthHeader.getRealm(), mUsername, mPassword);
                         makeAuthResponse(mUsername, mPassword);
                     } else {
@@ -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..59bec24 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;
@@ -74,7 +79,20 @@
      * HTTP authentication handler: takes care of synchronization of HTTP
      * authentication requests.
      */
-    private HttpAuthHandler mHttpAuthHandler;
+    private HttpAuthHandlerImpl 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);
+        mHttpAuthHandler = new HttpAuthHandlerImpl(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/PluginManager.java b/core/java/android/webkit/PluginManager.java
index e5eeb8c..ab3b6d5 100644
--- a/core/java/android/webkit/PluginManager.java
+++ b/core/java/android/webkit/PluginManager.java
@@ -159,41 +159,11 @@
                     directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
                 }
 
-                // check if the plugin has the required permissions
-                String permissions[] = pkgInfo.requestedPermissions;
-                if (permissions == null) {
+                // check if the plugin has the required permissions and
+                // signatures
+                if (!containsPluginPermissionAndSignatures(pkgInfo)) {
                     continue;
                 }
-                boolean permissionOk = false;
-                for (String permit : permissions) {
-                    if (PLUGIN_PERMISSION.equals(permit)) {
-                        permissionOk = true;
-                        break;
-                    }
-                }
-                if (!permissionOk) {
-                    continue;
-                }
-
-                // check to ensure the plugin is properly signed
-                Signature signatures[] = pkgInfo.signatures;
-                if (signatures == null) {
-                    continue;
-                }
-                if (SystemProperties.getBoolean("ro.secure", false)) {
-                    boolean signatureMatch = false;
-                    for (Signature signature : signatures) {
-                        for (int i = 0; i < SIGNATURES.length; i++) {
-                            if (SIGNATURES[i].equals(signature)) {
-                                signatureMatch = true;
-                                break;
-                            }
-                        }
-                    }
-                    if (!signatureMatch) {
-                        continue;
-                    }
-                }
 
                 // determine the type of plugin from the manifest
                 if (serviceInfo.metaData == null) {
@@ -236,6 +206,64 @@
     }
 
     /* package */
+    boolean containsPluginPermissionAndSignatures(String pluginAPKName) {
+        PackageManager pm = mContext.getPackageManager();
+
+        // retrieve information from the plugin's manifest
+        try {
+            PackageInfo pkgInfo = pm.getPackageInfo(pluginAPKName, PackageManager.GET_PERMISSIONS
+                    | PackageManager.GET_SIGNATURES);
+            if (pkgInfo != null) {
+                return containsPluginPermissionAndSignatures(pkgInfo);
+            }
+        } catch (NameNotFoundException e) {
+            Log.w(LOGTAG, "Can't find plugin: " + pluginAPKName);
+        }
+        return false;
+    }
+
+    private static boolean containsPluginPermissionAndSignatures(PackageInfo pkgInfo) {
+
+        // check if the plugin has the required permissions
+        String permissions[] = pkgInfo.requestedPermissions;
+        if (permissions == null) {
+            return false;
+        }
+        boolean permissionOk = false;
+        for (String permit : permissions) {
+            if (PLUGIN_PERMISSION.equals(permit)) {
+                permissionOk = true;
+                break;
+            }
+        }
+        if (!permissionOk) {
+            return false;
+        }
+
+        // check to ensure the plugin is properly signed
+        Signature signatures[] = pkgInfo.signatures;
+        if (signatures == null) {
+            return false;
+        }
+        if (SystemProperties.getBoolean("ro.secure", false)) {
+            boolean signatureMatch = false;
+            for (Signature signature : signatures) {
+                for (int i = 0; i < SIGNATURES.length; i++) {
+                    if (SIGNATURES[i].equals(signature)) {
+                        signatureMatch = true;
+                        break;
+                    }
+                }
+            }
+            if (!signatureMatch) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /* package */
     String getPluginsAPKName(String pluginLib) {
 
         // basic error checking on input params
diff --git a/core/java/android/webkit/SelectActionModeCallback.java b/core/java/android/webkit/SelectActionModeCallback.java
new file mode 100644
index 0000000..cf91902
--- /dev/null
+++ b/core/java/android/webkit/SelectActionModeCallback.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 android.webkit;
+
+import android.provider.Browser;
+import android.webkit.WebView;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+class SelectActionModeCallback implements ActionMode.Callback {
+    private WebView mWebView;
+    private View mTitleBar;
+    private ActionMode mActionMode;
+
+    void setWebView(WebView webView) {
+        mWebView = webView;
+    }
+
+    void setTitleBar(View v) { mTitleBar = v; }
+
+    void finish() {
+        mActionMode.finish();
+    }
+
+    // ActionMode.Callback implementation
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_copy,
+                menu);
+        mode.setTitle(com.android.internal.R.string.textSelectionCABTitle);
+        mActionMode = mode;
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        return true;
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        switch(item.getItemId()) {
+            case android.R.id.copy:
+                mWebView.copySelection();
+                mode.finish();
+                break;
+
+            case com.android.internal.R.id.share:
+                String selection = mWebView.getSelection();
+                Browser.sendString(mWebView.getContext(), selection);
+                mode.finish();
+                break;
+
+            case com.android.internal.R.id.select_all:
+                mWebView.selectAll();
+                break;
+
+            case com.android.internal.R.id.find:
+                String sel= mWebView.getSelection();
+                mode.finish();
+                mWebView.showFindDialog(sel);
+                break;
+
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        if (mTitleBar != null) mWebView.setEmbeddedTitleBar(mTitleBar);
+        mWebView.selectionDone();
+    }
+}
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 7c5f2b0..c1319e5 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -47,7 +47,7 @@
         String retVal = inUrl;
         WebAddress webAddress;
 
-        Log.v(LOGTAG, "guessURL before queueRequest: " + inUrl);
+        if (DebugFlags.URL_UTIL) Log.v(LOGTAG, "guessURL before queueRequest: " + inUrl);
 
         if (inUrl.length() == 0) return inUrl;
         if (inUrl.startsWith("about:")) return inUrl;
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 9f642c0..b2ba7e23 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -314,28 +314,20 @@
     /**
      * 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.
+     * 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 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) {
-    }
+    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 da0c61b..7b9af5b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -19,11 +19,13 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.util.DisplayMetrics;
 import android.util.EventLog;
-import java.lang.SecurityException;
+
 import java.util.Locale;
 
 /**
@@ -107,7 +109,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}.
@@ -176,14 +178,20 @@
     private boolean         mJavaScriptCanOpenWindowsAutomatically = false;
     private boolean         mUseDoubleTree = false;
     private boolean         mUseWideViewport = false;
+    private boolean         mUseFixedViewport = false;
+    private int             mMaxFixedViewportWidth = WebView.DEFAULT_VIEWPORT_WIDTH;
     private boolean         mSupportMultipleWindows = false;
     private boolean         mShrinksStandaloneImagesToFit = false;
+    private long            mMaximumDecodedImageSize = 0; // 0 means default
+    private boolean         mPrivateBrowsingEnabled = false;
+    private boolean         mSyntheticLinksEnabled = true;
     // 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 = "";
@@ -199,15 +207,17 @@
     private RenderPriority  mRenderPriority = RenderPriority.NORMAL;
     private int             mOverrideCacheMode = LOAD_DEFAULT;
     private boolean         mSaveFormData = true;
+    private boolean         mAutoFillEnabled = false;
     private boolean         mSavePassword = true;
     private boolean         mLightTouchEnabled = false;
     private boolean         mNeedInitialFocus = true;
     private boolean         mNavDump = false;
     private boolean         mSupportZoom = true;
     private boolean         mBuiltInZoomControls = false;
+    private boolean         mDisplayZoomControls = true;
     private boolean         mAllowFileAccess = true;
     private boolean         mLoadWithOverviewMode = false;
-    private boolean         mUseWebViewBackgroundOverscrollBackground = true;
+    private boolean         mEnableSmoothTransition = false;
 
     // private WebSettings, not accessible by the host activity
     static private int      mDoubleTapToastCount = 3;
@@ -297,13 +307,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.
@@ -315,6 +325,14 @@
         mDefaultTextEncoding = context.getString(com.android.internal.
                                                  R.string.default_text_encoding);
 
+        // Detect tablet device for fixed viewport mode.
+        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+        final int landscapeWidth = Math.max(metrics.widthPixels, metrics.heightPixels);
+        final int minTabletWidth = context.getResources().getDimensionPixelSize(
+            com.android.internal.R.dimen.min_xlarge_screen_width);
+        mUseFixedViewport = (metrics.density == 1.0f && landscapeWidth >= minTabletWidth);
+        mMaxFixedViewportWidth = (int) (landscapeWidth * 1.25);
+
         if (sLockForLocaleSettings == null) {
             sLockForLocaleSettings = new Object();
             sLocale = Locale.getDefault();
@@ -328,6 +346,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.
@@ -337,32 +357,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.
@@ -380,11 +421,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("-");
@@ -407,11 +448,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.
      */
@@ -427,6 +470,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) {
@@ -448,14 +506,34 @@
         mBuiltInZoomControls = enabled;
         mWebView.updateMultiTouchSupport(mContext);
     }
-    
+
     /**
      * Returns true if the zoom mechanism built into WebView is being used.
      */
     public boolean getBuiltInZoomControls() {
         return mBuiltInZoomControls;
     }
-    
+
+    /**
+     * Sets whether the on screen zoom buttons are used.
+     * A combination of built in zoom controls enabled
+     * and on screen zoom controls disabled allows for pinch to zoom
+     * to work without the on screen controls
+     * @hide
+     */
+    public void setDisplayZoomControls(boolean enabled) {
+        mDisplayZoomControls = enabled;
+        mWebView.updateMultiTouchSupport(mContext);
+    }
+
+    /**
+     * Returns true if the on screen zoom buttons are being used.
+     * @hide
+     */
+    public boolean getDisplayZoomControls() {
+        return mDisplayZoomControls;
+    }
+
     /**
      * Enable or disable file access within WebView. File access is enabled by
      * default.
@@ -486,20 +564,22 @@
     }
 
     /**
-     * Set whether the WebView uses its background for over scroll background.
-     * If true, it will use the WebView's background. If false, it will use an
-     * internal pattern. Default is true.
+     * 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 setUseWebViewBackgroundForOverscrollBackground(boolean view) {
-        mUseWebViewBackgroundOverscrollBackground = view;
+    public void setEnableSmoothTransition(boolean enable) {
+        mEnableSmoothTransition = enable;
     }
 
     /**
-     * Returns true if this WebView uses WebView's background instead of
-     * internal pattern for over scroll background.
+     * Returns true if the WebView enables smooth transition while panning or
+     * zooming.
      */
-    public boolean getUseWebViewBackgroundForOverscrollBackground() {
-        return mUseWebViewBackgroundOverscrollBackground;
+    public boolean enableSmoothTransition() {
+        return mEnableSmoothTransition;
     }
 
     /**
@@ -517,6 +597,20 @@
     }
 
     /**
+     * @hide
+     */
+    public void setAutoFillEnabled(boolean enabled) {
+        mAutoFillEnabled = enabled;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean getAutoFillEnabled() {
+        return mAutoFillEnabled;
+    }
+
+    /**
      *  Store whether the WebView is saving password.
      */
     public void setSavePassword(boolean save) {
@@ -987,6 +1081,7 @@
         if (mBlockNetworkLoads != flag) {
             mBlockNetworkLoads = flag;
             verifyNetworkAccess();
+            postSync();
         }
     }
 
@@ -1002,8 +1097,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 - " +
@@ -1029,6 +1124,7 @@
      * @deprecated This method has been deprecated in favor of
      *             {@link #setPluginState}
      */
+    @Deprecated
     public synchronized void setPluginsEnabled(boolean flag) {
         setPluginState(PluginState.ON);
     }
@@ -1199,6 +1295,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.
      */
@@ -1211,6 +1319,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;
     }
@@ -1284,7 +1393,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();
@@ -1339,11 +1448,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) {
@@ -1370,7 +1479,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
@@ -1384,7 +1493,7 @@
             mOverrideCacheMode = mode;
         }
     }
-    
+
     /**
      * Return the current setting for overriding the cache mode. For a full
      * description, see the {@link #setCacheMode(int)} function.
@@ -1392,7 +1501,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
@@ -1407,6 +1516,58 @@
         }
      }
 
+    /**
+     * 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();
+        }
+    }
+
+    /**
+     * Returns whether to use fixed viewport.
+     */
+    /* package */ boolean getUseFixedViewport() {
+        return mUseFixedViewport;
+    }
+
+    /**
+     * Returns maximum fixed viewport width.
+     */
+    /* package */ int getMaxFixedViewportWidth() {
+        return mMaxFixedViewportWidth;
+    }
+
+    /**
+     * Returns whether private browsing is enabled.
+     */
+    /* package */ boolean isPrivateBrowsingEnabled() {
+        return mPrivateBrowsingEnabled;
+    }
+
+    /**
+     * Sets whether private browsing is enabled.
+     * @param flag Whether private browsing should be enabled.
+     */
+    /* package */ synchronized void setPrivateBrowsingEnabled(boolean flag) {
+        if (mPrivateBrowsingEnabled != flag) {
+            mPrivateBrowsingEnabled = flag;
+            postSync();
+        }
+    }
+
+    synchronized void setSyntheticLinksEnabled(boolean flag) {
+        if (mSyntheticLinksEnabled != flag) {
+            mSyntheticLinksEnabled = flag;
+            postSync();
+        }
+    }
+
     int getDoubleTapToastCount() {
         return mDoubleTapToastCount;
     }
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 19abec1..1aff170 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;
@@ -46,6 +47,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputConnection;
 import android.widget.AbsoluteLayout.LayoutParams;
+import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.AutoCompleteTextView;
 import android.widget.TextView;
@@ -109,16 +111,35 @@
     // FIXME: This can be replaced with TextView.NO_FILTERS if that
     // is made public/protected.
     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
+    // For keeping track of the fact that the delete key was pressed, so
+    // we can simply pass a delete key instead of calling deleteSelection.
+    private boolean mGotDelete;
+    private int mDelSelStart;
+    private int mDelSelEnd;
+
+    // Keep in sync with native constant in
+    // external/webkit/WebKit/android/WebCoreSupport/autofill/WebAutoFill.cpp
+    /* package */ static final int FORM_NOT_AUTOFILLABLE = -1;
+
+    private boolean mAutoFillable; // Is this textview part of an autofillable form?
+    private int mQueryId;
 
     /**
      * Create a new WebTextView.
      * @param   context The Context for this WebTextView.
      * @param   webView The WebView that created this.
      */
-    /* package */ WebTextView(Context context, WebView webView) {
+    /* package */ WebTextView(Context context, WebView webView, int autoFillQueryId) {
         super(context, null, com.android.internal.R.attr.webTextViewStyle);
         mWebView = webView;
         mMaxLength = -1;
+        setAutoFillable(autoFillQueryId);
+    }
+
+    public void setAutoFillable(int queryId) {
+        mAutoFillable = mWebView.getSettings().getAutoFillEnabled()
+                && (queryId != FORM_NOT_AUTOFILLABLE);
+        mQueryId = queryId;
     }
 
     @Override
@@ -152,13 +173,22 @@
             return true;
         }
         Spannable text = (Spannable) getText();
-        int oldLength = text.length();
+        int oldStart = Selection.getSelectionStart(text);
+        int oldEnd = Selection.getSelectionEnd(text);
         // Normally the delete key's dom events are sent via onTextChanged.
-        // However, if the length is zero, the text did not change, so we
-        // go ahead and pass the key down immediately.
-        if (KeyEvent.KEYCODE_DEL == keyCode && 0 == oldLength) {
-            sendDomEvent(event);
-            return true;
+        // However, if the cursor is at the beginning of the field, which
+        // includes the case where it has zero length, then the text is not
+        // changed, so send the events immediately.
+        if (KeyEvent.KEYCODE_DEL == keyCode) {
+            if (oldStart == 0 && oldEnd == 0) {
+                sendDomEvent(event);
+                return true;
+            }
+            if (down) {
+                mGotDelete = true;
+                mDelSelStart = oldStart;
+                mDelSelEnd = oldEnd;
+            }
         }
 
         if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)) {
@@ -196,9 +226,8 @@
         if (getLayout() == null) {
             measure(mWidthSpec, mHeightSpec);
         }
-        int oldStart = Selection.getSelectionStart(text);
-        int oldEnd = Selection.getSelectionEnd(text);
 
+        int oldLength = text.length();
         boolean maxedOut = mMaxLength != -1 && oldLength == mMaxLength;
         // If we are at max length, and there is a selection rather than a
         // cursor, we need to store the text to compare later, since the key
@@ -300,6 +329,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
@@ -322,9 +378,6 @@
         switch (actionCode) {
         case EditorInfo.IME_ACTION_NEXT:
             if (mWebView.nativeMoveCursorToNextTextInput()) {
-                // Since the cursor will no longer be in the same place as the
-                // focus, set the focus controller back to inactive
-                mWebView.setFocusControllerInactive();
                 // Preemptively rebuild the WebTextView, so that the action will
                 // be set properly.
                 mWebView.rebuildWebTextView();
@@ -360,19 +413,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);
@@ -397,17 +439,38 @@
         mPreChange = postChange;
         if (0 == count) {
             if (before > 0) {
+                // For this and all changes to the text, update our cache
+                updateCachedTextfield();
+                if (mGotDelete) {
+                    mGotDelete = false;
+                    int oldEnd = start + before;
+                    if (mDelSelEnd == oldEnd
+                            && (mDelSelStart == start
+                            || (mDelSelStart == oldEnd && before == 1))) {
+                        // If the selection is set up properly before the
+                        // delete, send the DOM events.
+                        sendDomEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
+                                KeyEvent.KEYCODE_DEL));
+                        sendDomEvent(new KeyEvent(KeyEvent.ACTION_UP,
+                                KeyEvent.KEYCODE_DEL));
+                        return;
+                    }
+                }
                 // This was simply a delete or a cut, so just delete the
                 // selection.
                 mWebView.deleteSelection(start, start + before);
-                // For this and all changes to the text, update our cache
-                updateCachedTextfield();
             }
+            mGotDelete = false;
             // before should never be negative, so whether it was a cut
             // (handled above), or before is 0, in which case nothing has
             // changed, we should return.
             return;
         }
+        // Ensure that this flag gets cleared, since with autocorrect on, a
+        // delete key press may have a more complex result than deleting one
+        // character or the existing selection, so it will not get cleared
+        // above.
+        mGotDelete = false;
         // Find the last character being replaced.  If it can be represented by
         // events, we will pass them to native (after replacing the beginning
         // of the changed text), so we can see javascript events.
@@ -481,9 +544,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);
@@ -578,16 +642,31 @@
      */
     /* package */ void remove() {
         // hide the soft keyboard when the edit text is out of focus
-        InputMethodManager.getInstance(mContext).hideSoftInputFromWindow(
-                getWindowToken(), 0);
+        InputMethodManager imm = InputMethodManager.getInstance(mContext);
+        if (imm.isActive(this)) {
+            imm.hideSoftInputFromWindow(getWindowToken(), 0);
+        }
         mWebView.removeView(this);
         mWebView.requestFocus();
     }
 
+    /**
+     * Move the caret/selection into view.
+     */
     /* package */ void bringIntoView() {
-        if (getLayout() != null) {
-            bringPointIntoView(Selection.getSelectionEnd(getText()));
+        bringPointIntoView(Selection.getSelectionEnd(getText()));
+    }
+
+    @Override
+    public boolean bringPointIntoView(int offset) {
+        if (mWebView == null) return false;
+        if (mWebView.nativeFocusCandidateIsPassword()) {
+            return getLayout() != null && super.bringPointIntoView(offset);
         }
+        // For non password text input, tell webkit to move the caret/selection
+        // on screen, since webkit draws them.
+        mWebView.revealSelection();
+        return true;
     }
 
     /**
@@ -609,6 +688,22 @@
             adapter.setTextView(this);
         }
         super.setAdapter(adapter);
+        if (mAutoFillable) {
+            setOnItemClickListener(new AdapterView.OnItemClickListener() {
+                @Override
+                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                    if (id == 0 && position == 0) {
+                        // Blank out the text box while we wait for WebCore to fill the form.
+                        replaceText("");
+                        // Call a webview method to tell WebCore to autofill the form.
+                        mWebView.autoFillForm(mQueryId);
+                    }
+                }
+            });
+        } else {
+            setOnItemClickListener(null);
+        }
+        showDropDown();
     }
 
     /**
@@ -667,6 +762,7 @@
         } else {
             Selection.setSelection(text, selection, selection);
         }
+        if (mWebView != null) mWebView.incrementTextGeneration();
     }
 
     /**
@@ -899,7 +995,7 @@
             if (type != 2 /* PASSWORD */) {
                 String name = mWebView.nativeFocusCandidateName();
                 if (name != null && name.length() > 0) {
-                    mWebView.requestFormData(name, mNodePointer);
+                    mWebView.requestFormData(name, mNodePointer, mAutoFillable);
                 }
             }
         }
@@ -919,14 +1015,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 b7c902b..fedb873 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -16,39 +16,41 @@
 
 package android.webkit;
 
-import com.android.internal.R;
-
 import android.annotation.Widget;
 import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.IntentFilter;
 import android.content.DialogInterface.OnCancelListener;
-import android.content.Intent;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
+import android.content.Intent;
 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.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;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.text.IClipboard;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech;
 import android.text.Selection;
 import android.text.Spannable;
 import android.util.AttributeSet;
@@ -66,40 +68,42 @@
 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.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
 import android.widget.CheckedTextView;
-import android.widget.EdgeGlow;
-import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.ListView;
-import android.widget.OverScroller;
+import android.widget.Scroller;
 import android.widget.Toast;
-import android.widget.ZoomButtonsController;
-import android.widget.ZoomControls;
+
+import junit.framework.Assert;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
-import junit.framework.Assert;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * <p>A View that displays web pages. This class is the basis upon which you
@@ -116,7 +120,7 @@
  * <pre>&lt;uses-permission android:name="android.permission.INTERNET" /></pre>
  *
  * <p>This must be a child of the <a
- * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code &lt;manifest&gt;}</a>
+ * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
  * element.</p>
  *
  * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-webview.html">Web View
@@ -139,7 +143,7 @@
  * </pre>
  * <p>See {@link android.content.Intent} for more information.</p>
  *
- * <p>To provide a WebView in your own Activity, include a {@code &lt;WebView&gt;} in your layout,
+ * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
  * or set the entire Activity window as a WebView during {@link
  * android.app.Activity#onCreate(Bundle) onCreate()}:</p>
  * <pre class="prettyprint">
@@ -286,7 +290,7 @@
  * low density screens scale down. This is also the default behavior.</li>
  * <li>{@code low-dpi} - Use ldpi as the target dpi. Medium and high density screens scale up
  * as appropriate.</li>
- * <li><em>{@code &lt;value&gt;}</em> - Specify a dpi value to use as the target dpi (accepted
+ * <li><em>{@code <value>}</em> - Specify a dpi value to use as the target dpi (accepted
  * values are 70-400).</li>
  * </ul>
  * <p>Here's an example meta tag to specify the target density:</p>
@@ -298,7 +302,7 @@
  * property to {@code device-dpi}. This stops Android from performing scaling in your web page and
  * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p>
  *
- * 
+ *
  */
 @Widget
 public class WebView extends AbsoluteLayout
@@ -315,49 +319,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.
@@ -403,6 +365,8 @@
     // more key events.
     private int mTextGeneration;
 
+    /* package */ void incrementTextGeneration() { mTextGeneration++; }
+
     // Used by WebViewCore to create child views.
     /* package */ final ViewManager mViewManager;
 
@@ -450,6 +414,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
      */
@@ -462,11 +434,7 @@
     private static final int TOUCH_DOUBLE_TAP_MODE = 6;
     private static final int TOUCH_DONE_MODE = 7;
     private static final int TOUCH_PINCH_DRAG = 8;
-
-    /**
-     * True if we have a touch panel capable of detecting smooth pan/scale at the same time
-     */
-    private boolean mAllowPanAndScale;
+    private static final int TOUCH_DRAG_LAYER_MODE = 9;
 
     // Whether to forward the touch events to WebCore
     private boolean mForwardTouchEvents = false;
@@ -489,6 +457,9 @@
     // default is not set, the UI will continue handle them.
     private boolean mDeferTouchProcess;
 
+    // if true, multi-touch events will be passed to webkit directly before UI
+    private boolean mDeferMultitouch = false;
+
     // to avoid interfering with the current touch events, track them
     // separately. Currently no snapping or fling in the deferred process mode
     private int mDeferTouchMode = TOUCH_DONE_MODE;
@@ -505,10 +476,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
      */
@@ -530,9 +497,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,10 +528,7 @@
     // time for the longest scroll animation
     private static final int MAX_DURATION = 750;   // milliseconds
     private static final int SLIDE_TITLE_DURATION = 500;   // milliseconds
-    private OverScroller mScroller;
-    private boolean mInOverScrollMode = false;
-    private static Paint mOverScrollBackground;
-    private static Paint mOverScrollBorder;
+    private Scroller mScroller;
 
     private boolean mWrapContent;
     private static final int MOTIONLESS_FALSE           = 0;
@@ -576,15 +537,28 @@
     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;
+    // flag indicating if accessibility script is injected so we
+    // know to handle Shift and arrows natively first
+    private boolean mAccessibilityScriptInjected;
+
+    // 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
@@ -618,7 +592,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;
@@ -632,16 +606,21 @@
     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;
+    static final int FORM_DID_BLUR                      = 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;
+
+    static final int SET_AUTOFILLABLE                   = 133;
 
     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;
@@ -666,7 +645,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;
@@ -679,18 +658,22 @@
         "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;
+        "FORM_DID_BLUR", //                  = 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;
+        "SET_AUTOFILLABLE" //                = 133;
     };
 
     // If the site doesn't use the viewport meta tag to specify the viewport,
     // use DEFAULT_VIEWPORT_WIDTH as the default viewport width
-    static final int DEFAULT_VIEWPORT_WIDTH = 800;
+    static final int DEFAULT_VIEWPORT_WIDTH = 980;
 
     // normally we try to fit the content to the minimum preferred width
     // calculated by the Webkit. To avoid the bad behavior when some site's
@@ -698,49 +681,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;
@@ -764,26 +707,31 @@
     private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
     private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
 
-    /**
-     * Max distance to overscroll by in pixels.
-     * This how far content can be pulled beyond its normal bounds by the user.
-     */
-    private int mOverscrollDistance;
+    // constants for determining script injection strategy
+    private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
+    private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
+    private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
 
-    /**
-     * Max distance to overfling by in pixels.
-     * This is how far flinged content can move beyond the end of its normal bounds.
-     */
-    private int mOverflingDistance;
+    // the alias via which accessibility JavaScript interface is exposed
+    private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
 
-    /*
-     * These manage the edge glow effect when flung or pulled beyond the edges.
-     * If one is not null, all are not null. Checking one for null is as good as checking each.
-     */
-    private EdgeGlow mEdgeGlowTop;
-    private EdgeGlow mEdgeGlowBottom;
-    private EdgeGlow mEdgeGlowLeft;
-    private EdgeGlow mEdgeGlowRight;
+    // 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);" +
+        "  })();";
+
+    // Regular expression that matches the "axs" URL parameter.
+    // The value of 0 means the accessibility script is opted out
+    // The value of 1 means the accessibility script is already injected
+    private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))";
+
+    // variable to cache the above pattern in case accessibility is enabled.
+    private Pattern mMatchAxsUrlParameterPattern;
 
     // Used to match key downs and key ups
     private boolean mGotKeyDown;
@@ -793,6 +741,8 @@
     // for event log
     private long mLastTouchUpTime = 0;
 
+    private int mAutoFillQueryId = WebTextView.FORM_NOT_AUTOFILLABLE;
+
     /**
      * URI scheme for telephone number
      */
@@ -889,43 +839,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.
@@ -950,7 +863,18 @@
      * @param defStyle The default style resource ID.
      */
     public WebView(Context context, AttributeSet attrs, int defStyle) {
-        this(context, attrs, defStyle, null);
+        this(context, attrs, defStyle, false);
+    }
+
+    /**
+     * Construct a new WebView with layout parameters and a default style.
+     * @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.
+     */
+    public WebView(Context context, AttributeSet attrs, int defStyle,
+            boolean privateBrowsing) {
+        this(context, attrs, defStyle, null, privateBrowsing);
     }
 
     /**
@@ -961,53 +885,134 @@
      * @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) {
+            Map<String, Object> javascriptInterfaces, boolean privateBrowsing) {
         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 OverScroller(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();
+        setupPackageListener(context);
         updateMultiTouchSupport(context);
+
+        if (privateBrowsing) {
+            startPrivateBrowsing();
+        }
+    }
+
+    /*
+     * The intent receiver that monitors for changes to relevant packages (e.g.,
+     * sGoogleApps) and notifies WebViewCore of their existence.
+     */
+    private static BroadcastReceiver sPackageInstallationReceiver = null;
+
+    /*
+     * A set of Google packages we monitor for the
+     * navigator.isApplicationInstalled() API. Add additional packages as
+     * needed.
+     */
+    private static Set<String> sGoogleApps;
+    static {
+        sGoogleApps = new HashSet<String>();
+        sGoogleApps.add("com.google.android.youtube");
+    }
+
+    private void setupPackageListener(Context context) {
+
+        /*
+         * we must synchronize the instance check and the creation of the
+         * receiver to ensure that only ONE receiver exists for all WebView
+         * instances.
+         */
+        synchronized (WebView.class) {
+
+            // if the receiver already exists then we do not need to register it
+            // again
+            if (sPackageInstallationReceiver != null) {
+                return;
+            }
+
+            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addDataScheme("package");
+            sPackageInstallationReceiver = new BroadcastReceiver() {
+
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    final String action = intent.getAction();
+                    final String packageName = intent.getData().getSchemeSpecificPart();
+                    final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                    if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
+                        // if it is replacing, refreshPlugins() when adding
+                        return;
+                    }
+
+                    if (sGoogleApps.contains(packageName) && mWebViewCore != null) {
+                        if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                            mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAME, packageName);
+                        } else {
+                            mWebViewCore.sendMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
+                        }
+                    }
+
+                    PluginManager pm = PluginManager.getInstance(context);
+                    if (pm.containsPluginPermissionAndSignatures(packageName)) {
+                        pm.refreshPlugins(Intent.ACTION_PACKAGE_ADDED.equals(action));
+                    }
+                }
+            };
+
+            context.getApplicationContext().registerReceiver(sPackageInstallationReceiver, filter);
+        }
+
+        // check if any of the monitored apps are already installed
+        AsyncTask<Void, Void, Set<String>> task = new AsyncTask<Void, Void, Set<String>>() {
+
+            @Override
+            protected Set<String> doInBackground(Void... unused) {
+                Set<String> installedPackages = new HashSet<String>();
+                PackageManager pm = mContext.getPackageManager();
+                List<PackageInfo> packages = pm.getInstalledPackages(0);
+                for (PackageInfo p : packages) {
+                    if (sGoogleApps.contains(p.packageName)) {
+                        installedPackages.add(p.packageName);
+                    }
+                }
+                return installedPackages;
+            }
+
+            // Executes on the UI thread
+            @Override
+            protected void onPostExecute(Set<String> installedPackages) {
+                if (mWebViewCore != null) {
+                    mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, installedPackages);
+                }
+            }
+        };
+        task.execute();
     }
 
     void updateMultiTouchSupport(Context context) {
-        WebSettings settings = getSettings();
-        final PackageManager pm = context.getPackageManager();
-        mSupportMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
-                && settings.supportZoom() && settings.getBuiltInZoomControls();
-        mAllowPanAndScale = pm.hasSystemFeature(
-                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
-        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() {
@@ -1027,57 +1032,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();
-        mOverscrollDistance = configuration.getScaledOverscrollDistance();
-        mOverflingDistance = configuration.getScaledOverflingDistance();
+
+        // Compute the inverse of the density squared.
+        DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density);
     }
 
-    @Override
-    public void setOverscrollMode(int mode) {
-        super.setOverscrollMode(mode);
-        if (mode != OVERSCROLL_NEVER) {
-            if (mEdgeGlowTop == null) {
-                final Resources res = getContext().getResources();
-                final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
-                final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
-                mEdgeGlowTop = new EdgeGlow(edge, glow);
-                mEdgeGlowBottom = new EdgeGlow(edge, glow);
-                mEdgeGlowLeft = new EdgeGlow(edge, glow);
-                mEdgeGlowRight = new EdgeGlow(edge, glow);
-            }
-        } else {
-            mEdgeGlowTop = null;
-            mEdgeGlowBottom = null;
-            mEdgeGlowLeft = null;
-            mEdgeGlowRight = null;
+    /**
+     * 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,
@@ -1194,16 +1180,17 @@
      * 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
+     *
+     * @hide
      */
-    private int getVisibleTitleHeight() {
-        // need to restrict mScrollY due to over scroll
-        return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0);
+    public int getVisibleTitleHeight() {
+        return Math.max(getTitleHeight() - mScrollY, 0);
     }
 
     /*
@@ -1284,9 +1271,27 @@
         return mDatabase.getHttpAuthUsernamePassword(host, realm);
     }
 
+    /**
+     * Remove Find or Select ActionModes, if active.
+     */
+    private void clearActionModes() {
+        if (mSelectCallback != null) {
+            mSelectCallback.finish();
+        }
+        if (mFindCallback != null) {
+            mFindCallback.finish();
+        }
+    }
+
+    /**
+     * Called to clear state when moving from one page to another, or changing
+     * in some other way that makes elements associated with the current page
+     * (such as WebTextView or ActionModes) no longer relevant.
+     */
     private void clearHelpers() {
-        clearTextEntry(false);
-        selectionDone();
+        clearTextEntry();
+        clearActionModes();
+        dismissFullScreenMode();
     }
 
     /**
@@ -1296,6 +1301,7 @@
      */
     public void destroy() {
         clearHelpers();
+
         if (mWebViewCore != null) {
             // Set the handlers to null before destroying WebViewCore so no
             // more messages will be posted.
@@ -1423,6 +1429,8 @@
             outState.putBundle("certificate",
                                SslCertificate.saveState(mCertificate));
         }
+        outState.putBoolean("privateBrowsingEnabled", isPrivateBrowsingEnabled());
+        mZoomManager.saveZoomState(outState);
         return list;
     }
 
@@ -1468,29 +1476,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();
     }
 
@@ -1591,6 +1593,11 @@
                 // Update the copy to have the correct index.
                 returnList.setCurrentIndex(index);
             }
+            // Restore private browsing setting.
+            if (inState.getBoolean("privateBrowsingEnabled")) {
+                getSettings().setPrivateBrowsingEnabled(true);
+            }
+            mZoomManager.restoreZoomState(inState);
             // Remove all pending messages because we are restoring previous
             // state.
             mWebViewCore.removeMessages();
@@ -1702,6 +1709,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() {
@@ -1801,6 +1847,48 @@
         }
     }
 
+    /**
+     * Returns true if private browsing is enabled in this WebView.
+     */
+    public boolean isPrivateBrowsingEnabled() {
+        return getSettings().isPrivateBrowsingEnabled();
+    }
+
+    private void startPrivateBrowsing() {
+        boolean wasPrivateBrowsingEnabled = isPrivateBrowsingEnabled();
+
+        getSettings().setPrivateBrowsingEnabled(true);
+
+        if (!wasPrivateBrowsingEnabled) {
+            loadUrl("browser:incognito");
+        }
+    }
+
+    /**
+     * Deletes any files that were created as a part of the last private
+     * browsing session and clears any internal state associated with that
+     * session. The consequences of calling this method while a private
+     * browsing session is active are unspecified.
+     * @param context The same context which was used to create the private
+     *                browsing WebView.
+     * @return True if the private browsing files were successfully deleted,
+     *         false otherwise.
+     * @hide pending API council approval.
+     */
+    public static boolean cleanupPrivateBrowsingFiles(Context context) {
+        // It seems wrong that we have to pass the storage locations here, given
+        // that the storage files are created native-side in WebRequestContext
+        // (albeit using a dumb getter on BrowserFrame to get the paths from
+        // Java). It looks like this is required because we may need to call
+        // this method before the BrowserFrame has been set up.
+        // TODO: Investigate whether this can be avoided.
+        return nativeCleanupPrivateBrowsingFiles(context.getDatabasePath("dummy").getParent(),
+                                                 context.getCacheDir().getAbsolutePath());
+    }
+
+    private static native boolean nativeCleanupPrivateBrowsingFiles(String databaseDirectory,
+                                                                    String cacheDirectory);
+
     private boolean extendScroll(int y) {
         int finalY = mScroller.getFinalY();
         int newY = pinLocY(finalY + y);
@@ -1848,7 +1936,7 @@
         }
         nativeClearCursor(); // start next trackball movement from page edge
         if (bottom) {
-            return pinScrollTo(mScrollX, computeRealVerticalScrollRange(), true, 0);
+            return pinScrollTo(mScrollX, computeVerticalScrollRange(), true, 0);
         }
         // Page down.
         int h = getHeight();
@@ -1870,6 +1958,7 @@
     public void clearView() {
         mContentWidth = 0;
         mContentHeight = 0;
+        if (mNativeClass != 0) nativeSetBaseLayer(0);
         mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
     }
 
@@ -1883,8 +1972,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;
     }
 
     /**
@@ -1896,15 +1986,13 @@
 
     /**
      * Remove the WebTextView.
-     * @param disableFocusController If true, send a message to webkit
-     *     disabling the focus controller, so the caret stops blinking.
      */
-    private void clearTextEntry(boolean disableFocusController) {
+    private void clearTextEntry() {
         if (inEditingMode()) {
             mWebTextView.remove();
-            if (disableFocusController) {
-                setFocusControllerInactive();
-            }
+        } else {
+            // The keyboard may be open with the WebView as the served view
+            hideSoftKeyboard();
         }
     }
 
@@ -1913,7 +2001,7 @@
      * @return The current scale.
      */
     public float getScale() {
-        return mActualScale;
+        return mZoomManager.getScale();
     }
 
     /**
@@ -1925,7 +2013,7 @@
      * @param scaleInPercent The initial scale in percent.
      */
     public void setInitialScale(int scaleInPercent) {
-        mInitialScaleInPercent = scaleInPercent;
+        mZoomManager.setInitialScaleInPercent(scaleInPercent);
     }
 
     /**
@@ -1938,14 +2026,8 @@
             Log.w(LOGTAG, "This WebView doesn't support zoom.");
             return;
         }
-        clearTextEntry(false);
-        if (getSettings().getBuiltInZoomControls()) {
-            getZoomButtonsController().setVisible(true);
-        } else {
-            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-            mPrivateHandler.postDelayed(mZoomControlRunnable,
-                    ZOOM_CONTROLS_TIMEOUT);
-        }
+        clearHelpers();
+        mZoomManager.invokeZoomPicker();
     }
 
     /**
@@ -2060,7 +2142,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
@@ -2077,16 +2159,14 @@
     }
 
     // Expects x in view coordinates
-    private int pinLocX(int x) {
-        if (mInOverScrollMode) return x;
-        return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange());
+    int pinLocX(int x) {
+        return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
     }
 
     // Expects y in view coordinates
-    private int pinLocY(int y) {
-        if (mInOverScrollMode) return y;
+    int pinLocY(int y) {
         return pinLoc(y, getViewHeightWithTitle(),
-                      computeRealVerticalScrollRange() + getTitleHeight());
+                      computeVerticalScrollRange() + getTitleHeight());
     }
 
     /**
@@ -2111,6 +2191,16 @@
      * @hide
      */
     public void setEmbeddedTitleBar(View v) {
+        if (null == v) {
+            // If one of our callbacks is holding onto the titlebar to replace
+            // it when its ActionMode ends, remove it.
+            if (mSelectCallback != null) {
+                mSelectCallback.setTitleBar(null);
+            }
+            if (mFindCallback != null) {
+                mFindCallback.setTitleBar(null);
+            }
+        }
         if (mTitleBar == v) return;
         if (mTitleBar != null) {
             removeView(mTitleBar);
@@ -2134,7 +2224,7 @@
      * height.
      */
     private int viewToContentDimension(int d) {
-        return Math.round(d * mInvActualScale);
+        return Math.round(d * mZoomManager.getInvScale());
     }
 
     /**
@@ -2160,7 +2250,7 @@
      * Returns the result as a float.
      */
     private float viewToContentXf(int x) {
-        return x * mInvActualScale;
+        return x * mZoomManager.getInvScale();
     }
 
     /**
@@ -2169,7 +2259,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();
     }
 
     /**
@@ -2179,7 +2269,7 @@
      * height.
      */
     /*package*/ int contentToViewDimension(int d) {
-        return Math.round(d * mActualScale);
+        return Math.round(d * mZoomManager.getScale());
     }
 
     /**
@@ -2220,7 +2310,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,
@@ -2231,7 +2321,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),
@@ -2269,13 +2359,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.
@@ -2287,77 +2371,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);
@@ -2390,15 +2409,12 @@
         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
     private void calcOurContentVisibleRect(Rect r) {
         calcOurVisibleRect(r);
-        // since we might overscroll, pin the rect to the bounds of the content
+        // pin the rect to the bounds of the content
         r.left = Math.max(viewToContentX(r.left), 0);
         // viewToContentY will remove the total height of the title bar.  Add
         // the visible height back in to account for the fact that if the title
@@ -2439,16 +2455,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
@@ -2461,85 +2480,51 @@
             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;
     }
 
-    private int computeRealHorizontalScrollRange() {
+    @Override
+    protected int computeHorizontalScrollRange() {
         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);
-        }
-    }
-
-    @Override
-    protected int computeHorizontalScrollRange() {
-        int range = computeRealHorizontalScrollRange();
-
-        // Adjust reported range if overscrolled to compress the scroll bars
-        final int scrollX = mScrollX;
-        final int overscrollRight = computeMaxScrollX();
-        if (scrollX < 0) {
-            range -= scrollX;
-        } else if (scrollX > overscrollRight) {
-            range += scrollX - overscrollRight;
-        }
-
-        return range;
-    }
-
-    @Override
-    protected int computeHorizontalScrollOffset() {
-        return Math.max(mScrollX, 0);
-    }
-
-    private int computeRealVerticalScrollRange() {
-        if (mDrawHistory) {
-            return mHistoryHeight;
-        } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF
-                && (mActualScale - mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) {
-            // 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(mContentWidth * mZoomManager.getScale());
         }
     }
 
     @Override
     protected int computeVerticalScrollRange() {
-        int range = computeRealVerticalScrollRange();
-
-        // Adjust reported range if overscrolled to compress the scroll bars
-        final int scrollY = mScrollY;
-        final int overscrollBottom = computeMaxScrollY();
-        if (scrollY < 0) {
-            range -= scrollY;
-        } else if (scrollY > overscrollBottom) {
-            range += scrollY - overscrollBottom;
+        if (mDrawHistory) {
+            return mHistoryHeight;
+        } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF
+                && !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 * mZoomManager.getScale());
         }
-
-        return range;
     }
 
     @Override
@@ -2557,31 +2542,10 @@
     protected void onDrawVerticalScrollBar(Canvas canvas,
                                            Drawable scrollBar,
                                            int l, int t, int r, int b) {
-        if (mScrollY < 0) {
-            t -= mScrollY;
-        }
         scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b);
         scrollBar.draw(canvas);
     }
 
-    @Override
-    protected void onOverscrolled(int scrollX, int scrollY, boolean clampedX,
-            boolean clampedY) {
-        mInOverScrollMode = false;
-        int maxX = computeMaxScrollX();
-        if (maxX == 0) {
-            // do not over scroll x if the page just fits the screen
-            scrollX = pinLocX(scrollX);
-        } else if (scrollX < 0 || scrollX > maxX) {
-            mInOverScrollMode = true;
-        }
-        if (scrollY < 0 || scrollY > computeMaxScrollY()) {
-            mInOverScrollMode = true;
-        }
-
-        super.scrollTo(scrollX, scrollY);
-    }
-
     /**
      * Get the url for the current page. This is not always the same as the url
      * passed to WebViewClient.onPageStarted because although the load for
@@ -2627,7 +2591,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() {
@@ -2800,22 +2766,49 @@
     }
 
     /**
-     * @hide
+     * Start an ActionMode for finding text in this WebView.
+     * @param text If non-null, will be the initial text to search for.
+     *             Otherwise, the last String searched for in this WebView will
+     *             be used to start.
      */
-    public void setFindIsUp(boolean isUp) {
-        mFindIsUp = isUp;
-        if (isUp) {
-            recordNewContentSize(mContentWidth, mContentHeight + mFindHeight,
-                    false);
+    public void showFindDialog(String text) {
+        mFindCallback = new FindActionModeCallback(mContext);
+        setFindIsUp(true);
+        mFindCallback.setWebView(this);
+        View titleBar = mTitleBar;
+        // We do not want to show the embedded title bar during find or
+        // select, but keep track of it so that it can be replaced when the
+        // mode is exited.
+        setEmbeddedTitleBar(null);
+        mFindCallback.setTitleBar(titleBar);
+        startActionMode(mFindCallback);
+        if (text == null) {
+            text = mLastFind;
         }
+        if (text != null) {
+            mFindCallback.setText(text);
+        }
+    }
+
+    /**
+     * Keep track of the find callback so that we can remove its titlebar if
+     * necessary.
+     */
+    private FindActionModeCallback mFindCallback;
+
+    /**
+     * Toggle whether the find dialog is showing, for both native and Java.
+     */
+    private void setFindIsUp(boolean isUp) {
+        mFindIsUp = isUp;
         if (0 == mNativeClass) return; // client isn't initialized
         nativeSetFindIsUp(isUp);
     }
 
     /**
-     * @hide
+     * Return the index of the currently highlighted match.
      */
-    public int findIndex() {
+    int findIndex() {
         if (0 == mNativeClass) return -1;
         return nativeFindIndex();
     }
@@ -2824,9 +2817,8 @@
     // 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.
+    // Keep track of the last string sent, so we can search again when find is
+    // reopened.
     private String mLastFind;
 
     /**
@@ -2883,7 +2875,6 @@
      * Clear the highlighting surrounding text matches created by findAll.
      */
     public void clearMatches() {
-        mLastFind = "";
         if (mNativeClass == 0)
             return;
         nativeSetFindIsEmpty();
@@ -2891,16 +2882,15 @@
     }
 
     /**
-     * @hide
+     * Called when the find ActionMode ends.
      */
-    public void notifyFindDialogDismissed() {
+    void notifyFindDialogDismissed() {
+        mFindCallback = null;
         if (mWebViewCore == null) {
             return;
         }
         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);
@@ -2908,16 +2898,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.
@@ -2930,54 +2910,31 @@
         mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
     }
 
+    /**
+     * Request the scroller to abort any ongoing animation
+     *
+     * @hide
+     */
+    public void stopScroll() {
+        mScroller.forceFinished(true);
+        mLastVelocity = 0;
+    }
+
     @Override
     public void computeScroll() {
         if (mScroller.computeScrollOffset()) {
             int oldX = mScrollX;
             int oldY = mScrollY;
-            int x = mScroller.getCurrX();
-            int y = mScroller.getCurrY();
-            invalidate();  // So we draw again
-
-            if (oldX != x || oldY != y) {
-                final int rangeX = computeMaxScrollX();
-                final int rangeY = computeMaxScrollY();
-                overscrollBy(x - oldX, y - oldY, oldX, oldY,
-                        rangeX, rangeY,
-                        mOverflingDistance, mOverflingDistance, false);
-
-                if (mEdgeGlowTop != null) {
-                    if (rangeY > 0 || getOverscrollMode() == OVERSCROLL_ALWAYS) {
-                        if (y < 0 && oldY >= 0) {
-                            mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
-                            if (!mEdgeGlowBottom.isFinished()) {
-                                mEdgeGlowBottom.onRelease();
-                            }
-                        } else if (y > rangeY && oldY <= rangeY) {
-                            mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
-                            if (!mEdgeGlowTop.isFinished()) {
-                                mEdgeGlowTop.onRelease();
-                            }
-                        }
-                    }
-
-                    if (rangeX > 0) {
-                        if (x < 0 && oldX >= 0) {
-                            mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
-                            if (!mEdgeGlowRight.isFinished()) {
-                                mEdgeGlowRight.onRelease();
-                            }
-                        } else if (x > rangeX && oldX <= rangeX) {
-                            mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
-                            if (!mEdgeGlowLeft.isFinished()) {
-                                mEdgeGlowLeft.onRelease();
-                            }
-                        }
-                    }
-                }
-            }
-            if (mScroller.isFinished()) {
-                mPrivateHandler.sendEmptyMessage(RESUME_WEBCORE_PRIORITY);
+            mScrollX = mScroller.getCurrX();
+            mScrollY = mScroller.getCurrY();
+            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();
@@ -3051,6 +3008,23 @@
     }
 
     /**
+     * Called by CallbackProxy when the page starts loading.
+     * @param url The URL of the page which has started loading.
+     */
+    /* package */ void onPageStarted(String url) {
+        // every time we start a new page, we want to reset the
+        // WebView certificate:  if the new site is secure, we
+        // will reload it and get a new certificate set;
+        // if the new site is not secure, the certificate must be
+        // null, and that will be the case
+        setCertificate(null);
+
+        // reset the flag since we set to true in if need after
+        // loading is see onPageFinished(Url)
+        mAccessibilityScriptInjected = false;
+    }
+
+    /**
      * Called by CallbackProxy when the page finishes loading.
      * @param url The URL of the page which has finished loading.
      */
@@ -3066,6 +3040,99 @@
             }
             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.
+     * </p>
+     * If the URL has the "axs" paramter set to 1 it has already done the
+     * script injection so we do nothing. If the parameter is set to 0
+     * the URL opts out accessibility script injection so we fall back to
+     * the default {@link AccessibilityInjector}.
+     * </p>
+     * Note: If the user has not opted-in the accessibility script injection no scripts
+     * are injected rather the default {@link AccessibilityInjector} implementation
+     * is used.
+     *
+     * @param url The URL loaded by this {@link WebView}.
+     */
+    private void injectAccessibilityForUrl(String url) {
+        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
+
+        if (!accessibilityManager.isEnabled()) {
+            // it is possible that accessibility was turned off between reloads
+            ensureAccessibilityScriptInjectorInstance(false);
+            return;
+        }
+
+        if (!getSettings().getJavaScriptEnabled()) {
+            // no JS so we fallback to the basic buil-in support
+            ensureAccessibilityScriptInjectorInstance(true);
+            return;
+        }
+
+        // check the URL "axs" parameter to choose appropriate action
+        int axsParameterValue = getAxsUrlParameterValue(url);
+        if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {
+            boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext
+                    .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
+            if (onDeviceScriptInjectionEnabled) {
+                ensureAccessibilityScriptInjectorInstance(false);
+                // neither script injected nor script injection opted out => we inject
+                loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);
+                // TODO: Set this flag after successfull script injection. Maybe upon injection
+                // the chooser should update the meta tag and we check it to declare success
+                mAccessibilityScriptInjected = true;
+            } else {
+                // injection disabled so we fallback to the basic built-in support
+                ensureAccessibilityScriptInjectorInstance(true);
+            }
+        } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
+            // injection opted out so we fallback to the basic buil-in support
+            ensureAccessibilityScriptInjectorInstance(true);
+        } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) {
+            ensureAccessibilityScriptInjectorInstance(false);
+            // the URL provides accessibility but we still need to add our generic script
+            loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);
+        } else {
+            Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue);
+        }
+    }
+
+    /**
+     * Ensures the instance of the {@link AccessibilityInjector} to be present ot not.
+     *
+     * @param present True to ensure an insance, false to ensure no instance.
+     */
+    private void ensureAccessibilityScriptInjectorInstance(boolean present) {
+        if (present && mAccessibilityInjector == null) {
+            mAccessibilityInjector = new AccessibilityInjector(this);
+        } else {
+            mAccessibilityInjector = null;
+        }
+    }
+
+    /**
+     * Gets the "axs" URL parameter value.
+     *
+     * @param url A url to fetch the paramter from.
+     * @return The parameter value if such, -1 otherwise.
+     */
+    private int getAxsUrlParameterValue(String url) {
+        if (mMatchAxsUrlParameterPattern == null) {
+            mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER);
+        }
+        Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url);
+        if (matcher.find()) {
+            String keyValuePair = url.substring(matcher.start(), matcher.end());
+            return Integer.parseInt(keyValuePair.split("=")[1]);
+        }
+        return -1;
     }
 
     /**
@@ -3182,7 +3249,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);
         }
     }
 
@@ -3312,46 +3379,7 @@
      *         settings.
      */
     public WebSettings getSettings() {
-        return mWebViewCore.getSettings();
-    }
-
-    /**
-     * Use this method to inform the webview about packages that are installed
-     * in the system. This information will be used by the
-     * navigator.isApplicationInstalled() API.
-     * @param packageNames is a set of package names that are known to be
-     * installed in the system.
-     *
-     * @hide not a public API
-     */
-    public void addPackageNames(Set<String> packageNames) {
-        mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, packageNames);
-    }
-
-    /**
-     * Use this method to inform the webview about single packages that are
-     * installed in the system. This information will be used by the
-     * navigator.isApplicationInstalled() API.
-     * @param packageName is the name of a package that is known to be
-     * installed in the system.
-     *
-     * @hide not a public API
-     */
-    public void addPackageName(String packageName) {
-        mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAME, packageName);
-    }
-
-    /**
-     * Use this method to inform the webview about packages that are uninstalled
-     * in the system. This information will be used by the
-     * navigator.isApplicationInstalled() API.
-     * @param packageName is the name of a package that has been uninstalled in
-     * the system.
-     *
-     * @hide not a public API
-     */
-    public void removePackageName(String packageName) {
-        mWebViewCore.sendMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
+        return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
     }
 
    /**
@@ -3425,36 +3453,6 @@
         }
 
         int saveCount = canvas.save();
-        if (mInOverScrollMode && !getSettings()
-                .getUseWebViewBackgroundForOverscrollBackground()) {
-            if (mOverScrollBackground == null) {
-                mOverScrollBackground = new Paint();
-                Bitmap bm = BitmapFactory.decodeResource(
-                        mContext.getResources(),
-                        com.android.internal.R.drawable.status_bar_background);
-                mOverScrollBackground.setShader(new BitmapShader(bm,
-                        Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
-                mOverScrollBorder = new Paint();
-                mOverScrollBorder.setStyle(Paint.Style.STROKE);
-                mOverScrollBorder.setStrokeWidth(0);
-                mOverScrollBorder.setColor(0xffbbbbbb);
-            }
-
-            int top = 0;
-            int right = computeRealHorizontalScrollRange();
-            int bottom = top + computeRealVerticalScrollRange();
-            // first draw the background and anchor to the top of the view
-            canvas.save();
-            canvas.translate(mScrollX, mScrollY);
-            canvas.clipRect(-mScrollX, top - mScrollY, right - mScrollX, bottom
-                    - mScrollY, Region.Op.DIFFERENCE);
-            canvas.drawPaint(mOverScrollBackground);
-            canvas.restore();
-            // then draw the border
-            canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder);
-            // next clip the region for the content
-            canvas.clipRect(0, top, right, bottom);
-        }
         if (mTitleBar != null) {
             canvas.translate(0, (int) mTitleBar.getHeight());
         }
@@ -3480,71 +3478,50 @@
                     mScrollY + height);
             mTitleShadow.draw(canvas);
         }
-
         if (AUTO_REDRAW_HACK && mAutoRedraw) {
             invalidate();
         }
+        if (inEditingMode()) mWebTextView.onDrawSubstitute();
         mWebViewCore.signalRepaintDone();
-    }
 
-    @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-        if (mEdgeGlowTop != null && drawEdgeGlows(canvas)) {
-            invalidate();
+        // 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);
+                }
+            }
         }
     }
 
-    /**
-     * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null.
-     *
-     * @param canvas Canvas to draw into, transformed into view coordinates.
-     * @return true if glow effects are still animating and the view should invalidate again.
-     */
-    private boolean drawEdgeGlows(Canvas canvas) {
-        final int scrollX = mScrollX;
-        final int scrollY = mScrollY;
-        final int width = getWidth();
-        int height = getHeight();
-
-        boolean invalidateForGlow = false;
-        if (!mEdgeGlowTop.isFinished()) {
-            final int restoreCount = canvas.save();
-
-            canvas.translate(-width / 2 + scrollX, Math.min(0, scrollY));
-            mEdgeGlowTop.setSize(width * 2, height);
-            invalidateForGlow |= mEdgeGlowTop.draw(canvas);
-            canvas.restoreToCount(restoreCount);
+    private void removeTouchHighlight(boolean removePendingMessage) {
+        if (removePendingMessage) {
+            mWebViewCore.removeMessages(EventHub.GET_TOUCH_HIGHLIGHT_RECTS);
         }
-        if (!mEdgeGlowBottom.isFinished()) {
-            final int restoreCount = canvas.save();
-
-            canvas.translate(-width / 2 + scrollX, Math.max(computeMaxScrollY(), scrollY) + height);
-            canvas.rotate(180, width, 0);
-            mEdgeGlowBottom.setSize(width * 2, height);
-            invalidateForGlow |= mEdgeGlowBottom.draw(canvas);
-            canvas.restoreToCount(restoreCount);
-        }
-        if (!mEdgeGlowLeft.isFinished()) {
-            final int restoreCount = canvas.save();
-
-            canvas.rotate(270);
-            canvas.translate(-height * 1.5f - scrollY, Math.min(0, scrollX));
-            mEdgeGlowLeft.setSize(height * 2, width);
-            invalidateForGlow |= mEdgeGlowLeft.draw(canvas);
-            canvas.restoreToCount(restoreCount);
-        }
-        if (!mEdgeGlowRight.isFinished()) {
-            final int restoreCount = canvas.save();
-
-            canvas.rotate(90);
-            canvas.translate(-height / 2 + scrollY,
-                    -(Math.max(computeMaxScrollX(), scrollX) + width));
-            mEdgeGlowRight.setSize(height * 2, width);
-            invalidateForGlow |= mEdgeGlowRight.draw(canvas);
-            canvas.restoreToCount(restoreCount);
-        }
-        return invalidateForGlow;
+        mWebViewCore.sendMessage(EventHub.REMOVE_TOUCH_HIGHLIGHT_RECTS);
     }
 
     @Override
@@ -3561,12 +3538,20 @@
         // to windows overview, the WebView will be temporarily removed from the
         // view system. In that case, do nothing.
         if (getParent() == null) return false;
+
+        // A multi-finger gesture can look like a long press; make sure we don't take
+        // long press actions if we're scaling.
+        final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
+        if (detector != null && detector.isInProgress()) {
+            return false;
+        }
+        
         if (mNativeClass != 0 && nativeCursorIsTextInput()) {
             // Send the click so that the textfield is in focus
             centerKeyPressOnTextField();
             rebuildWebTextView();
         } else {
-            clearTextEntry(true);
+            clearTextEntry();
         }
         if (inEditingMode()) {
             return mWebTextView.performLongClick();
@@ -3587,26 +3572,17 @@
         setUpSelect();
         if (mNativeClass != 0 && nativeWordSelection(x, y)) {
             nativeSetExtendSelection();
-            WebChromeClient client = getWebChromeClient();
-            if (client != null) client.onSelectionStart(this);
             return true;
         }
-        notifySelectDialogDismissed();
+        selectionDone();
         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.
+     * Keep track of the Callback so we can end its ActionMode or remove its
+     * titlebar.
      */
-    private boolean mNeedToAdjustWebTextView;
+    private SelectActionModeCallback mSelectCallback;
 
     private boolean didUpdateTextViewBounds(boolean allowIntersect) {
         Rect contentBounds = nativeFocusCandidateNodeBounds();
@@ -3635,25 +3611,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 ||
@@ -3672,59 +3677,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;
@@ -3736,23 +3691,38 @@
             // 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) {
-                extras = DRAW_EXTRAS_FIND;
+            extras = DRAW_EXTRAS_FIND;
         } else if (mSelectingText) {
             extras = DRAW_EXTRAS_SELECTION;
             nativeSetSelectionPointer(mDrawSelectionPointer,
-                    mInvActualScale,
+                    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) {
@@ -3781,10 +3751,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();
@@ -3835,7 +3809,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
@@ -3853,14 +3829,11 @@
         InputMethodManager imm = (InputMethodManager)
                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
 
-        // bring it back to the default scale so that user can enter text
-        boolean zoom = mActualScale < mDefaultScale;
+        // bring it back to the default level scale so that user can enter text
+        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();
@@ -3872,7 +3845,7 @@
                 return;
             }
         }
-        // Used by plugins.
+        // Used by plugins and contentEditable.
         // Also used if the navigation cache is out of date, and
         // does not recognize that a textfield is in focus.  In that
         // case, use WebView as the targeted view.
@@ -3882,10 +3855,11 @@
 
     // Called by WebKit to instruct the UI to hide the keyboard
     private void hideSoftKeyboard() {
-        InputMethodManager imm = (InputMethodManager)
-                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-
-        imm.hideSoftInputFromWindow(this.getWindowToken(), 0);
+        InputMethodManager imm = InputMethodManager.peekInstance();
+        if (imm != null && (imm.isActive(this)
+                || (inEditingMode() && imm.isActive(mWebTextView)))) {
+            imm.hideSoftInputFromWindow(this.getWindowToken(), 0);
+        }
     }
 
     /*
@@ -3910,7 +3884,7 @@
         // At this point, we know we have found an input field, so go ahead
         // and create the WebTextView if necessary.
         if (mWebTextView == null) {
-            mWebTextView = new WebTextView(mContext, WebView.this);
+            mWebTextView = new WebTextView(mContext, WebView.this, mAutoFillQueryId);
             // Initialize our generation number.
             mTextGeneration = 0;
         }
@@ -3963,19 +3937,30 @@
     }
 
     /**
+     * Tell webkit to put the cursor on screen.
+     */
+    /* package */ void revealSelection() {
+        if (mWebViewCore != null) {
+            mWebViewCore.sendMessage(EventHub.REVEAL_SELECTION);
+        }
+    }
+
+    /**
      * Called by WebTextView to find saved form data associated with the
      * textfield
      * @param name Name of the textfield.
      * @param nodePointer Pointer to the node of the textfield, so it can be
      *          compared to the currently focused textfield when the data is
      *          retrieved.
+     * @param autoFillable true if WebKit has determined this field is part of
+     *          a form that can be auto filled.
      */
-    /* package */ void requestFormData(String name, int nodePointer) {
+    /* package */ void requestFormData(String name, int nodePointer, boolean autoFillable) {
         if (mWebViewCore.getSettings().getSaveFormData()) {
             Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA);
             update.arg1 = nodePointer;
             RequestFormData updater = new RequestFormData(name, getUrl(),
-                    update);
+                    update, autoFillable);
             Thread t = new Thread(updater);
             t.start();
         }
@@ -4001,15 +3986,28 @@
         private String mName;
         private String mUrl;
         private Message mUpdateMessage;
+        private boolean mAutoFillable;
 
-        public RequestFormData(String name, String url, Message msg) {
+        public RequestFormData(String name, String url, Message msg, boolean autoFillable) {
             mName = name;
             mUrl = url;
             mUpdateMessage = msg;
+            mAutoFillable = autoFillable;
         }
 
         public void run() {
-            ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName);
+            ArrayList<String> pastEntries = new ArrayList();
+
+            if (mAutoFillable) {
+                // Note that code inside the adapter click handler in WebTextView depends
+                // on the AutoFill item being at the top of the drop down list. If you change
+                // the order, make sure to do it there too!
+                pastEntries.add(getResources().getText(
+                        com.android.internal.R.string.autofill_this_form).toString());
+            }
+
+            pastEntries.addAll(mDatabase.getFormData(mUrl, mName));
+
             if (pastEntries.size() > 0) {
                 AutoCompleteAdapter adapter = new
                         AutoCompleteAdapter(mContext, pastEntries);
@@ -4049,6 +4047,26 @@
     }
 
     /**
+     * Called by DRT on UI thread, need to proxy to WebCore thread.
+     *
+     * @hide debug only
+     */
+    public void useMockDeviceOrientation() {
+        mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION);
+    }
+
+    /**
+     * Called by DRT on WebCore thread.
+     *
+     * @hide debug only
+     */
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        mWebViewCore.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
+    }
+
+    /**
      * Dump the V8 counters to standard output.
      * Note that you need a build with V8 and WEBCORE_INSTRUMENTATION set to
      * true. Otherwise, this will do nothing.
@@ -4099,14 +4117,16 @@
         // 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 (pageShouldHandleShiftAndArrows()) {
                 mShiftIsPressed = true;
             } else if (!nativeCursorWantsKeyEvents() && !mSelectingText) {
                 setUpSelect();
@@ -4126,8 +4146,8 @@
         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
             switchOutDrawHistory();
-            if (nativeFocusIsPlugin()) {
-                letPluginHandleNavKey(keyCode, event.getEventTime(), true);
+            if (pageShouldHandleShiftAndArrows()) {
+                letPageHandleNavKey(keyCode, event.getEventTime(), true);
                 return true;
             }
             if (mSelectingText) {
@@ -4251,13 +4271,16 @@
         // 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 (pageShouldHandleShiftAndArrows()) {
                 mShiftIsPressed = false;
             } else if (copySelection()) {
                 selectionDone();
@@ -4267,8 +4290,8 @@
 
         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
-            if (nativeFocusIsPlugin()) {
-                letPluginHandleNavKey(keyCode, event.getEventTime(), false);
+            if (pageShouldHandleShiftAndArrows()) {
+                letPageHandleNavKey(keyCode, event.getEventTime(), false);
                 return true;
             }
             // always handle the navigation keys in the UI thread
@@ -4311,8 +4334,8 @@
                 }
                 return true;
             }
-            clearTextEntry(true);
-            nativeSetFollowedLink(true);
+            clearTextEntry();
+            nativeShowCursorTimed();
             if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
                 mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
                         nativeCursorNodePointer());
@@ -4332,10 +4355,7 @@
         return false;
     }
 
-    /**
-     * @hide pending API council approval.
-     */
-    public void setUpSelect() {
+    private void setUpSelect() {
         if (0 == mNativeClass) return; // client isn't initialized
         if (inFullScreenMode()) return;
         if (mSelectingText) return;
@@ -4356,6 +4376,15 @@
             mSelectY = mScrollY + getViewHeightWithTitle() / 2;
         }
         nativeHideCursor();
+        mSelectCallback = new SelectActionModeCallback();
+        mSelectCallback.setWebView(this);
+        View titleBar = mTitleBar;
+        // We do not want to show the embedded title bar during find or
+        // select, but keep track of it so that it can be replaced when the
+        // mode is exited.
+        setEmbeddedTitleBar(null);
+        mSelectCallback.setTitleBar(titleBar);
+        startActionMode(mSelectCallback);
     }
 
     /**
@@ -4367,9 +4396,9 @@
     }
 
     /**
-     * @hide pending API council approval.
+     * Select all of the text in this WebView.
      */
-    public void selectAll() {
+    void selectAll() {
         if (0 == mNativeClass) return; // client isn't initialized
         if (inFullScreenMode()) return;
         if (!mSelectingText) setUpSelect();
@@ -4380,36 +4409,24 @@
     }
 
     /**
-     * @hide pending API council approval.
+     * Called when the selection has been removed.
      */
-    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() {
+    void selectionDone() {
         if (mSelectingText) {
-            WebChromeClient client = getWebChromeClient();
-            if (client != null) client.onSelectionDone(this);
+            mSelectingText = false;
+            // finish is idempotent, so this is fine even if selectionDone was
+            // called by mSelectCallback.onDestroyActionMode
+            mSelectCallback.finish();
+            mSelectCallback = null;
+            WebViewCore.resumeUpdatePicture(mWebViewCore);
             invalidate(); // redraw without selection
-            notifySelectDialogDismissed();
         }
     }
 
     /**
-     * @hide pending API council approval.
+     * Copy the selection to the clipboard
      */
-    public boolean copySelection() {
+    boolean copySelection() {
         boolean copiedSomething = false;
         String selection = getSelection();
         if (selection != "") {
@@ -4420,22 +4437,18 @@
                     , 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);
-            }
+            ClipboardManager cm = (ClipboardManager)getContext()
+                    .getSystemService(Context.CLIPBOARD_SERVICE);
+            cm.setText(selection);
         }
         invalidate(); // remove selection region and pointer
         return copiedSomething;
     }
 
     /**
-     * @hide pending API council approval.
+     * Returns the currently highlighted text as a string.
      */
-    public String getSelection() {
+    String getSelection() {
         if (mNativeClass == 0) return "";
         return nativeGetSelection();
     }
@@ -4449,11 +4462,21 @@
     @Override
     protected void onDetachedFromWindow() {
         clearHelpers();
-        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.
@@ -4483,32 +4506,30 @@
                 // If our window regained focus, and we have focus, then begin
                 // drawing the cursor ring
                 mDrawCursorRing = true;
+                setFocusControllerActive(true);
                 if (mNativeClass != 0) {
                     nativeRecordButtons(true, false, true);
-                    if (inEditingMode()) {
-                        mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 1, 0);
-                    }
                 }
             } else {
-                // If our window gained focus, but we do not have it, do not
-                // draw the cursor ring.
-                mDrawCursorRing = false;
+                if (!inEditingMode()) {
+                    // If our window gained focus, but we do not have it, do not
+                    // draw the cursor ring.
+                    mDrawCursorRing = false;
+                    setFocusControllerActive(false);
+                }
                 // We do not call nativeRecordButtons here because we assume
                 // that when we lost focus, or window focus, it got called with
                 // 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;
@@ -4518,7 +4539,7 @@
             if (mNativeClass != 0) {
                 nativeRecordButtons(false, false, true);
             }
-            setFocusControllerInactive();
+            setFocusControllerActive(false);
         }
         invalidate();
     }
@@ -4542,11 +4563,9 @@
      * not draw the blinking cursor.  It gets set to "active" to draw the cursor
      * in WebViewCore.cpp, when the WebCore thread receives key events/clicks.
      */
-    /* package */ void setFocusControllerInactive() {
-        // Do not need to also check whether mWebViewCore is null, because
-        // mNativeClass is only set if mWebViewCore is non null
-        if (mNativeClass == 0) return;
-        mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 0, 0);
+    /* package */ void setFocusControllerActive(boolean active) {
+        if (mWebViewCore == null) return;
+        mWebViewCore.sendMessage(EventHub.SET_ACTIVE, active ? 1 : 0, 0);
     }
 
     @Override
@@ -4563,6 +4582,7 @@
                 if (mNativeClass != 0) {
                     nativeRecordButtons(true, false, true);
                 }
+                setFocusControllerActive(true);
             //} else {
                 // The WebView has gained focus while we do not have
                 // windowfocus.  When our window lost focus, we should have
@@ -4576,7 +4596,7 @@
                 if (mNativeClass != 0) {
                     nativeRecordButtons(false, false, true);
                 }
-                setFocusControllerInactive();
+                setFocusControllerActive(false);
             }
             mGotKeyDown = false;
         }
@@ -4597,94 +4617,35 @@
             // 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
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
         super.onScrollChanged(l, t, oldl, oldt);
-        if (!mInOverScrollMode) {
-            sendOurVisibleRect();
-            // update WebKit if visible title bar height changed. The logic is same
-            // as getVisibleTitleHeight.
-            int titleHeight = getTitleHeight();
-            if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
-                sendViewSizeZoom();
-            }
+        sendOurVisibleRect();
+        // update WebKit if visible title bar height changed. The logic is same
+        // as getVisibleTitleHeight.
+        int titleHeight = getTitleHeight();
+        if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
+            sendViewSizeZoom(false);
         }
     }
 
@@ -4692,9 +4653,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 {
@@ -4754,7 +4717,7 @@
         public DragTrackerHandler(float x, float y, DragTracker proxy) {
             mProxy = proxy;
 
-            int docBottom = computeRealVerticalScrollRange() + getTitleHeight();
+            int docBottom = computeVerticalScrollRange() + getTitleHeight();
             int viewTop = getScrollY();
             int viewBottom = viewTop + getHeight();
 
@@ -4767,7 +4730,7 @@
                       " up/down= " + mMinDY + " " + mMaxDY);
             }
 
-            int docRight = computeRealHorizontalScrollRange();
+            int docRight = computeHorizontalScrollRange();
             int viewLeft = getScrollX();
             int viewRight = viewLeft + getWidth();
             mStartX = x;
@@ -4928,81 +4891,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());
@@ -5024,6 +4912,48 @@
         return mFullScreenHolder != null;
     }
 
+    private void dismissFullScreenMode() {
+        if (inFullScreenMode()) {
+            mFullScreenHolder.dismiss();
+            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())) {
@@ -5031,61 +4961,123 @@
         }
 
         if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode="
-                    + mTouchMode);
+            Log.v(LOGTAG, ev + " at " + ev.getEventTime()
+                + " mTouchMode=" + mTouchMode
+                + " numPointers=" + ev.getPointerCount());
         }
 
-        int action;
-        float x, y;
+        int action = ev.getAction();
+        float x = ev.getX();
+        float y = ev.getY();
         long eventTime = ev.getEventTime();
 
-        // FIXME: we may consider to give WebKit an option to handle multi-touch
-        // events later.
-        if (mSupportMultiTouch && ev.getPointerCount() > 1) {
-            if (mAllowPanAndScale || mMinZoomScale < mMaxZoomScale) {
-                mScaleDetector.onTouchEvent(ev);
-                if (mScaleDetector.isInProgress()) {
-                    mLastTouchTime = eventTime;
-                    if (!mAllowPanAndScale) {
-                        return true;
-                    }
+        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);
                 }
-                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;
-                    }
+                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;
                 }
-            } else {
-                // if the page disallow zoom, skip multi-pointer action
-                return true;
             }
-        } else {
-            action = ev.getAction();
-            x = ev.getX();
-            y = ev.getY();
+        }
+
+        // If the page disallows zoom, pass multi-pointer events to webkit.
+        if (ev.getPointerCount() > 1
+            && (mZoomManager.isZoomScaleFixed() || mDeferMultitouch)) {
+            if (DebugFlags.WEB_VIEW) {
+                Log.v(LOGTAG, "passing " + ev.getPointerCount() + " points to webkit");
+            }
+            passMultiTouchToWebKit(ev);
+            return true;
+        }
+
+        if (mZoomManager.supportsMultiTouchZoom() && ev.getPointerCount() > 1 &&
+                mTouchMode != TOUCH_DRAG_LAYER_MODE && !skipScaleGesture) {
+            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.
+                try {
+                    temp.setAction(temp.getAction() &
+                            ~MotionEvent.ACTION_MASK |
+                            MotionEvent.ACTION_POINTER_DOWN);
+                    detector.onTouchEvent(temp);
+                } finally {
+                    temp.recycle();
+                }
+            }
+
+            detector.onTouchEvent(ev);
+
+            if (detector.isInProgress()) {
+                mLastTouchTime = eventTime;
+                cancelLongPress();
+                mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+                if (!mZoomManager.supportsPanDuringZoom()) {
+                    return true;
+                }
+                mTouchMode = TOUCH_DRAG_MODE;
+                if (mVelocityTracker == null) {
+                    mVelocityTracker = VelocityTracker.obtain();
+                }
+            }
+
+            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;
@@ -5108,6 +5100,9 @@
                     mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
                 } 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 {
@@ -5119,13 +5114,33 @@
                                 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);
@@ -5157,21 +5172,19 @@
                     if (shouldForwardTouchEvent()) {
                         TouchEventData ted = new TouchEventData();
                         ted.mAction = action;
-                        ted.mX = contentX;
-                        ted.mY = contentY;
+                        ted.mPoints = new Point[1];
+                        ted.mPoints[0] = new Point(contentX, 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);
@@ -5192,24 +5205,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.mPoints = new Point[1];
+                    ted.mPoints[0] = new Point(contentX, 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,
@@ -5238,11 +5251,13 @@
                     break;
                 }
 
-                if (mTouchMode != TOUCH_DRAG_MODE) {
+                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
@@ -5252,9 +5267,9 @@
                     }
 
                     // Only lock dragging to one axis if we don't have a scale in progress.
-                    // Scaling implies free-roaming movement. Note we'll only ever get here
-                    // if mAllowPanAndScale is true.
-                    if (mScaleDetector != null && !mScaleDetector.isInProgress()) {
+                    // Scaling implies free-roaming movement. Note this is only ever a question
+                    // if mZoomManager.supportsPanDuringZoom() is true.
+                    if (detector != null && !detector.isInProgress()) {
                         // if it starts nearly horizontal or vertical, enforce it
                         int ax = Math.abs(deltaX);
                         int ay = Math.abs(deltaY);
@@ -5275,6 +5290,9 @@
                     deltaX = 0;
                     deltaY = 0;
 
+                    if (skipScaleGesture) {
+                        startScrollingLayer(gestureX, gestureY);
+                    }
                     startDrag();
                 }
 
@@ -5283,6 +5301,20 @@
                 }
 
                 // do pan
+                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;
                 if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
@@ -5349,7 +5381,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();
@@ -5364,18 +5398,15 @@
                 break;
             }
             case MotionEvent.ACTION_UP: {
+                if (!isFocused()) requestFocus();
                 // pass the touch events from UI thread to WebCore thread
                 if (shouldForwardTouchEvent()) {
                     TouchEventData ted = new TouchEventData();
                     ted.mAction = action;
-                    ted.mX = contentX;
-                    ted.mY = contentY;
+                    ted.mPoints = new Point[1];
+                    ted.mPoints[0] = new Point(contentX, contentY);
                     ted.mMetaState = ev.getMetaState();
                     ted.mReprocess = mDeferTouchProcess;
-                    if (mDeferTouchProcess) {
-                        ted.mViewX = x;
-                        ted.mViewY = y;
-                    }
                     mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
                 }
                 mLastTouchUpTime = eventTime;
@@ -5386,17 +5417,13 @@
                         if (inFullScreenMode() || mDeferTouchProcess) {
                             TouchEventData ted = new TouchEventData();
                             ted.mAction = WebViewCore.ACTION_DOUBLETAP;
-                            ted.mX = contentX;
-                            ted.mY = contentY;
+                            ted.mPoints = new Point[1];
+                            ted.mPoints[0] = new Point(contentX, 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;
@@ -5411,9 +5438,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();
@@ -5431,13 +5466,16 @@
                             }
                         } else {
                             if (mSelectingText) {
-                                if (nativeHitSelection(contentX, contentY)) {
-                                    copySelection();
+                                // tapping on selection or controls does nothing
+                                if (!nativeHitSelection(contentX, contentY)) {
+                                    selectionDone();
                                 }
-                                selectionDone();
                                 break;
                             }
-                            if (mTouchMode == TOUCH_INIT_MODE) {
+                            // only trigger double tap if the WebView is
+                            // scalable
+                            if (mTouchMode == TOUCH_INIT_MODE
+                                    && (canZoomIn() || canZoomOut())) {
                                 mPrivateHandler.sendEmptyMessageDelayed(
                                         RELEASE_SINGLE_TAP, ViewConfiguration
                                                 .getDoubleTapTimeout());
@@ -5466,18 +5504,13 @@
                             mHeldMotionless = MOTIONLESS_IGNORE;
                             doFling();
                             break;
-                        } else {
-                            if (mScroller.springback(mScrollX, mScrollY, 0,
-                                    computeMaxScrollX(), 0,
-                                    computeMaxScrollY())) {
-                                invalidate();
-                            }
                         }
                         // redraw in high-quality, as we're done dragging
                         mHeldMotionless = MOTIONLESS_TRUE;
                         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.
@@ -5491,8 +5524,6 @@
             }
             case MotionEvent.ACTION_CANCEL: {
                 if (mTouchMode == TOUCH_DRAG_MODE) {
-                    mScroller.springback(mScrollX, mScrollY, 0,
-                            computeMaxScrollX(), 0, computeMaxScrollY());
                     invalidate();
                 }
                 cancelWebCoreTouchEvent(contentX, contentY, false);
@@ -5503,14 +5534,32 @@
         return true;
     }
 
+    private void passMultiTouchToWebKit(MotionEvent ev) {
+        TouchEventData ted = new TouchEventData();
+        ted.mAction = ev.getAction() & MotionEvent.ACTION_MASK;
+        final int count = ev.getPointerCount();
+        ted.mPoints = new Point[count];
+        for (int c = 0; c < count; c++) {
+            int x = viewToContentX((int) ev.getX(c) + mScrollX);
+            int y = viewToContentY((int) ev.getY(c) + mScrollY);
+            ted.mPoints[c] = new Point(x, y);
+        }
+        ted.mMetaState = ev.getMetaState();
+        ted.mReprocess = mDeferTouchProcess;
+        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+        cancelLongPress();
+        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+        mPreventDefault = PREVENT_DEFAULT_IGNORE;
+    }
+
     private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) {
         if (shouldForwardTouchEvent()) {
             if (removeEvents) {
                 mWebViewCore.removeMessages(EventHub.TOUCH_EVENT);
             }
             TouchEventData ted = new TouchEventData();
-            ted.mX = x;
-            ted.mY = y;
+            ted.mPoints = new Point[1];
+            ted.mPoints[0] = new Point(x, y);
             ted.mAction = MotionEvent.ACTION_CANCEL;
             mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
             mPreventDefault = PREVENT_DEFAULT_IGNORE;
@@ -5536,79 +5585,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) {
-            final int oldX = mScrollX;
-            final int oldY = mScrollY;
-            final int rangeX = computeMaxScrollX();
-            final int rangeY = computeMaxScrollY();
-            overscrollBy(deltaX, deltaY, oldX, oldY,
-                    rangeX, rangeY,
-                    mOverscrollDistance, mOverscrollDistance, true);
-
-            if (mEdgeGlowTop != null) {
-                // Don't show left/right glows if we fit the whole content.
-                if (rangeX > 0) {
-                    final int pulledToX = oldX + deltaX;
-                    if (pulledToX < 0) {
-                        mEdgeGlowLeft.onPull((float) deltaX / getWidth());
-                        if (!mEdgeGlowRight.isFinished()) {
-                            mEdgeGlowRight.onRelease();
-                        }
-                    } else if (pulledToX > rangeX) {
-                        mEdgeGlowRight.onPull((float) deltaX / getWidth());
-                        if (!mEdgeGlowLeft.isFinished()) {
-                            mEdgeGlowLeft.onRelease();
-                        }
-                    }
+            if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
+                deltaX = viewToContentDimension(deltaX);
+                deltaY = viewToContentDimension(deltaY);
+                if (nativeScrollLayer(mScrollingLayer, deltaX, deltaY)) {
+                    invalidate();
                 }
-
-                if (rangeY > 0 || getOverscrollMode() == OVERSCROLL_ALWAYS) {
-                    final int pulledToY = oldY + deltaY;
-                    if (pulledToY < 0) {
-                        mEdgeGlowTop.onPull((float) deltaY / getHeight());
-                        if (!mEdgeGlowBottom.isFinished()) {
-                            mEdgeGlowBottom.onRelease();
-                        }
-                    } else if (pulledToY > rangeY) {
-                        mEdgeGlowBottom.onPull((float) deltaY / getHeight());
-                        if (!mEdgeGlowTop.isFinished()) {
-                            mEdgeGlowTop.onRelease();
-                        }
-                    }
-                }
+                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() {
@@ -5622,14 +5618,6 @@
             mVelocityTracker.recycle();
             mVelocityTracker = null;
         }
-
-        // Release any pulled glows
-        if (mEdgeGlowTop != null) {
-            mEdgeGlowTop.onRelease();
-            mEdgeGlowBottom.onRelease();
-            mEdgeGlowLeft.onRelease();
-            mEdgeGlowRight.onRelease();
-        }
     }
 
     private void cancelTouch() {
@@ -5643,16 +5631,8 @@
             mVelocityTracker.recycle();
             mVelocityTracker = null;
         }
-
-        // Release any pulled glows
-        if (mEdgeGlowTop != null) {
-            mEdgeGlowTop.onRelease();
-            mEdgeGlowBottom.onRelease();
-            mEdgeGlowLeft.onRelease();
-            mEdgeGlowRight.onRelease();
-        }
-
-        if (mTouchMode == TOUCH_DRAG_MODE) {
+        if (mTouchMode == TOUCH_DRAG_MODE ||
+                mTouchMode == TOUCH_DRAG_LAYER_MODE) {
             WebViewCore.resumePriority();
             WebViewCore.resumeUpdatePicture(mWebViewCore);
         }
@@ -5660,6 +5640,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();
@@ -5757,7 +5740,8 @@
             }
             return false; // let common code in onKeyUp at it
         }
-        if (mMapTrackballToArrowKeys && mShiftIsPressed == false) {
+        if ((mMapTrackballToArrowKeys && mShiftIsPressed == false) ||
+                (mAccessibilityInjector != null || mAccessibilityScriptInjected)) {
             if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
             return false;
         }
@@ -5925,11 +5909,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));
             }
@@ -5960,17 +5944,30 @@
     }
 
     private int computeMaxScrollX() {
-        return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0);
+        return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
     }
 
     private int computeMaxScrollY() {
-        return Math.max(computeRealVerticalScrollRange() + getTitleHeight()
+        return Math.max(computeVerticalScrollRange() + getTitleHeight()
                 - 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(), mOverflingDistance, mOverflingDistance);
+                computeMaxScrollY());
         invalidate();
     }
 
@@ -6000,20 +5997,19 @@
         if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
             WebViewCore.resumePriority();
             WebViewCore.resumeUpdatePicture(mWebViewCore);
-            if (mScroller.springback(mScrollX, mScrollY, 0, computeMaxScrollX(),
-                    0, computeMaxScrollY())) {
-                invalidate();
-            }
             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);
                 }
@@ -6027,67 +6023,17 @@
                     + " maxX=" + maxX + " maxY=" + maxY
                     + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY);
         }
-
-        // Allow sloppy flings without overscrolling at the edges.
-        if ((mScrollX == 0 || mScrollX == maxX) && Math.abs(vx) < Math.abs(vy)) {
-            vx = 0;
-        }
-        if ((mScrollY == 0 || mScrollY == maxY) && Math.abs(vy) < Math.abs(vx)) {
-            vy = 0;
-        }
-
-        if (mOverscrollDistance < mOverflingDistance) {
-            if (mScrollX == -mOverscrollDistance || mScrollX == maxX + mOverscrollDistance) {
-                vx = 0;
-            }
-            if (mScrollY == -mOverscrollDistance || mScrollY == maxY + mOverscrollDistance) {
-                vy = 0;
-            }
-        }
-
         mLastVelX = vx;
         mLastVelY = vy;
-        mLastVelocity = (float) Math.hypot(vx, vy);
+        mLastVelocity = velocity;
 
-        // no horizontal overscroll if the content just fits
-        mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY,
-                maxX == 0 ? 0 : mOverflingDistance, mOverflingDistance);
-        // Duration is calculated based on velocity. With range boundaries and overscroll
-        // we may not know how long the final animation will take. (Hence the deprecation
-        // warning on the call below.) It's not a big deal for scroll bars but if webcore
-        // resumes during this effect we will take a performance hit. See computeScroll;
-        // we resume webcore there when the animation is finished.
+        mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
         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
@@ -6107,81 +6053,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();
     }
 
     /**
@@ -6189,15 +6083,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();
     }
 
     /**
@@ -6205,14 +6091,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() {
@@ -6313,7 +6192,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();
@@ -6341,27 +6227,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;
     }
 
     /*
@@ -6370,22 +6245,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;
@@ -6396,9 +6268,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;
@@ -6410,109 +6282,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);
         }
     }
 
@@ -6524,6 +6297,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,
@@ -6611,6 +6387,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());
 
@@ -6753,7 +6535,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 {
@@ -6767,26 +6550,25 @@
                     break;
                 }
                 case SWITCH_TO_LONGPRESS: {
+                    if (getSettings().supportTouchOnly()) {
+                        removeTouchHighlight(false);
+                    }
                     if (inFullScreenMode() || mDeferTouchProcess) {
                         TouchEventData ted = new TouchEventData();
                         ted.mAction = WebViewCore.ACTION_LONGPRESS;
-                        ted.mX = viewToContentX((int) mLastTouchX + mScrollX);
-                        ted.mY = viewToContentY((int) mLastTouchY + mScrollY);
+                        ted.mPoints = new Point[1];
+                        ted.mPoints[0] = new Point(viewToContentX((int) mLastTouchX + mScrollX),
+                                                   viewToContentY((int) mLastTouchY + mScrollY));
                         // metaState for long press is tricky. Should it be the
                         // state when the press started or when the press was
                         // released? Or some intermediary key state? For
                         // 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;
                 }
@@ -6819,70 +6601,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();
-                            }
+                            clearTextEntry();
                         }
                     }
+
                     // 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
@@ -6890,45 +6638,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;
@@ -6962,16 +6690,16 @@
                     break;
                 case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID:
                     displaySoftKeyboard(true);
+                    // fall through to UPDATE_TEXT_SELECTION_MSG_ID
+                case UPDATE_TEXT_SELECTION_MSG_ID:
                     updateTextSelectionFromMessage(msg.arg1, msg.arg2,
                             (WebViewCore.TextSelectionData) msg.obj);
                     break;
-                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);
+                case FORM_DID_BLUR:
+                    if (inEditingMode()
+                            && mWebTextView.isSameTextField(msg.arg1)) {
+                        hideSoftKeyboard();
+                    }
                     break;
                 case RETURN_LABEL:
                     if (inEditingMode()
@@ -6987,20 +6715,19 @@
                         }
                     }
                     break;
-                case MOVE_OUT_OF_PLUGIN:
+                case UNHANDLED_NAV_KEY:
                     navHandledKey(msg.arg1, 1, false, 0);
                     break;
                 case UPDATE_TEXT_ENTRY_MSG_ID:
                     // this is sent after finishing resize in WebViewCore. Make
                     // sure the text edit box is still on the  screen.
-                    selectionDone();
                     if (inEditingMode() && nativeCursorIsTextInput()) {
                         mWebTextView.bringIntoView();
                         rebuildWebTextView();
                     }
                     break;
                 case CLEAR_TEXT_ENTRY:
-                    clearTextEntry(false);
+                    clearTextEntry();
                     break;
                 case INVAL_RECT_MSG_ID: {
                     Rect r = (Rect)msg.obj;
@@ -7013,23 +6740,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)) {
@@ -7073,43 +6783,46 @@
                             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.mPoints[0].x)
+                                        - mScrollX;
+                                mLastDeferTouchY = contentToViewY(ted.mPoints[0].y)
+                                        - mScrollY;
                                 mDeferTouchMode = TOUCH_INIT_MODE;
                                 break;
                             case MotionEvent.ACTION_MOVE: {
                                 // no snapping in defer process
+                                int x = contentToViewX(ted.mPoints[0].x) - mScrollX;
+                                int y = contentToViewY(ted.mPoints[0].y) - 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:
                             case MotionEvent.ACTION_CANCEL:
                                 if (mDeferTouchMode == TOUCH_DRAG_MODE) {
                                     // no fling in defer process
-                                    mScroller.springback(mScrollX, mScrollY, 0,
-                                            computeMaxScrollX(), 0,
-                                            computeMaxScrollY());
-                                    invalidate();
                                     WebViewCore.resumePriority();
                                     WebViewCore.resumeUpdatePicture(mWebViewCore);
                                 }
@@ -7117,9 +6830,9 @@
                                 break;
                             case WebViewCore.ACTION_DOUBLETAP:
                                 // doDoubleTap() needs mLastTouchX/Y as anchor
-                                mLastTouchX = ted.mViewX;
-                                mLastTouchY = ted.mViewY;
-                                doDoubleTap();
+                                mLastTouchX = contentToViewX(ted.mPoints[0].x) - mScrollX;
+                                mLastTouchY = contentToViewY(ted.mPoints[0].y) - mScrollY;
+                                mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
                                 mDeferTouchMode = TOUCH_DONE_MODE;
                                 break;
                             case WebViewCore.ACTION_LONGPRESS:
@@ -7127,7 +6840,6 @@
                                 if (hitTest != null && hitTest.mType
                                         != HitTestResult.UNKNOWN_TYPE) {
                                     performLongClick();
-                                    rebuildWebTextView();
                                 }
                                 mDeferTouchMode = TOUCH_DONE_MODE;
                                 break;
@@ -7145,8 +6857,8 @@
 
                 case FIND_AGAIN:
                     // Ignore if find has been dismissed.
-                    if (mFindIsUp) {
-                        findAll(mLastFind);
+                    if (mFindIsUp && mFindCallback != null) {
+                        mFindCallback.findAll();
                     }
                     break;
 
@@ -7174,9 +6886,9 @@
                     View view = (View) msg.obj;
                     int npp = msg.arg1;
 
-                    if (mFullScreenHolder != null) {
+                    if (inFullScreenMode()) {
                         Log.w(LOGTAG, "Should not have another full screen.");
-                        mFullScreenHolder.dismiss();
+                        dismissFullScreenMode();
                     }
                     mFullScreenHolder = new PluginFullScreenHolder(WebView.this, npp);
                     mFullScreenHolder.setContentView(view);
@@ -7187,10 +6899,7 @@
                     break;
                 }
                 case HIDE_FULLSCREEN:
-                    if (inFullScreenMode()) {
-                        mFullScreenHolder.dismiss();
-                        mFullScreenHolder = null;
-                    }
+                    dismissFullScreenMode();
                     break;
 
                 case DOM_FOCUS_CHANGED:
@@ -7251,7 +6960,6 @@
 
                 case CENTER_FIT_RECT:
                     Rect r = (Rect)msg.obj;
-                    mInZoomOverview = false;
                     centerFitRect(r.left, r.top, r.width(), r.height());
                     break;
 
@@ -7260,6 +6968,51 @@
                     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;
+
+                case SET_AUTOFILLABLE:
+                    mAutoFillQueryId = msg.arg1;
+                    if (mWebTextView != null) {
+                        mWebTextView.setAutoFillable(mAutoFillQueryId);
+                        rebuildWebTextView();
+                    }
+                    break;
+
                 default:
                     super.handleMessage(msg);
                     break;
@@ -7567,37 +7320,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.
@@ -7636,7 +7358,7 @@
      */
     private void sendMoveMouseIfLatest(boolean removeFocus) {
         if (removeFocus) {
-            clearTextEntry(true);
+            clearTextEntry();
         }
         mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST,
                 cursorData());
@@ -7680,7 +7402,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());
     }
 
     /**
@@ -7691,10 +7413,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) {
@@ -7769,6 +7491,16 @@
     }
 
     /**
+     * @return If the page should receive Shift and arrows.
+     */
+    private boolean pageShouldHandleShiftAndArrows() {
+        // TODO: Maybe the injected script should announce its presence in
+        // the page meta-tag so the nativePageShouldHandleShiftAndArrows
+        // will check that as one of the conditions it looks for
+        return (nativePageShouldHandleShiftAndArrows() || mAccessibilityScriptInjected);
+    }
+
+    /**
      * Set the background color. It's white by default. Pass
      * zero to make the view transparent.
      * @param color   the ARGB color described by Color.java
@@ -7791,7 +7523,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);
     }
 
     /**
@@ -7805,6 +7537,17 @@
     }
 
     /**
+     * Toggle whether multi touch events should be sent to webkit
+     * no matter if UI wants to handle it first.
+     *
+     * @hide This is only used by the webkit layout test.
+     */
+    public void setDeferMultiTouch(boolean value) {
+        mDeferMultitouch = value;
+        Log.v(LOGTAG, "set mDeferMultitouch to " + value);
+    }
+
+    /**
      *  Update our cache with updatedText.
      *  @param updatedText  The new text to put in our cache.
      */
@@ -7814,6 +7557,10 @@
         nativeUpdateCachedTextfield(updatedText, mTextGeneration);
     }
 
+    /*package*/ void autoFillForm(int autoFillQueryId) {
+        mWebViewCore.sendMessage(EventHub.AUTOFILL_FORM, autoFillQueryId, /* unused */0);
+    }
+
     private native int nativeCacheHitFramePointer();
     private native Rect nativeCacheHitNodeBounds();
     private native int nativeCacheHitNodePointer();
@@ -7835,10 +7582,18 @@
     private native boolean  nativeCursorWantsKeyEvents();
     private native void     nativeDebugDump();
     private native void     nativeDestroy();
+
+    /**
+     * 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 void     nativeDrawExtras(Canvas canvas, int extra);
-    private native void     nativeDumpDisplayTree(String urlOrNull);
     private native int      nativeFindAll(String findLower, String findUpper);
     private native void     nativeFindNext(boolean forward);
     /* package */ native int      nativeFocusCandidateFramePointer();
@@ -7876,6 +7631,14 @@
             boolean noScroll);
     private native int      nativeMoveGeneration();
     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.
@@ -7890,13 +7653,15 @@
     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     nativeShowCursorTimed();
+    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);
     private native boolean  nativeStartSelection(int x, int y);
-    private native void     nativeSetSelectionRegion(boolean set);
     private native Rect     nativeSubtractLayers(Rect content);
     private native int      nativeTextGeneration();
     // Never call this version except by updateCachedTextfield(String) -
@@ -7905,6 +7670,10 @@
             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/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 02c7210..1f8eeba 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -89,6 +89,7 @@
      * @deprecated This method is no longer called. When the WebView encounters
      *             a redirect loop, it will cancel the load.
      */
+    @Deprecated
     public void onTooManyRedirects(WebView view, Message cancelMsg,
             Message continueMsg) {
         cancelMsg.sendToTarget();
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index bf4d95b..7c089d8 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -17,30 +17,25 @@
 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;
+import android.media.MediaFile;
 import android.net.Uri;
 import android.os.Handler;
 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;
+import android.webkit.DeviceOrientationManager;
+import android.webkit.DeviceOrientationService;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -117,13 +112,16 @@
     private int mViewportDensityDpi = -1;
 
     private int mRestoredScale = 0;
-    private int mRestoredScreenWidthScale = 0;
+    private int mRestoredTextWrapScale = 0;
     private int mRestoredX = 0;
     private int mRestoredY = 0;
 
     private int mWebkitScrollX = 0;
     private int mWebkitScrollY = 0;
 
+    private DeviceOrientationManager mDeviceOrientationManager = new DeviceOrientationManager(this);
+    private DeviceOrientationService mDeviceOrientationService;
+
     // The thread name used to identify the WebCore thread and for use in
     // debugging other classes that require operation within the WebCore thread.
     /* package */ static final String THREAD_NAME = "WebViewCoreThread";
@@ -255,6 +253,13 @@
         return mSettings;
     }
 
+    /*
+     * Given mimeType, check whether it's supported in Android media framework.
+     * mimeType could be such as "audio/ogg" and "video/mp4".
+     */
+    /* package */ static boolean supportsMimeType(String mimeType) {
+        return MediaFile.getFileTypeForMimeType(mimeType) > 0;
+    }
     /**
      * Add an error message to the client's console.
      * @param message The message to add
@@ -276,37 +281,49 @@
         mCallbackProxy.onJsAlert(url, message);
     }
 
+    /**
+     * Called by JNI.  Send a message to the UI thread to hide the soft keyboard
+     * if the node pointed to by nodePointer is still in focus.
+     * @param nodePointer The node which just blurred.
+     */
+    private void formDidBlur(int nodePointer) {
+        if (mWebView == null) return;
+        Message.obtain(mWebView.mPrivateHandler, WebView.FORM_DID_BLUR,
+                nodePointer, 0).sendToTarget();
+    }
 
     /**
      * 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 +441,13 @@
         return mCallbackProxy.onJsTimeout();
     }
 
+    /**
+     * Notify the webview that this is an installable web app.
+     */
+    protected void setInstallableWebApp() {
+        mCallbackProxy.setInstallableWebApp();
+    }
+
     //-------------------------------------------------------------------------
     // JNI methods
     //-------------------------------------------------------------------------
@@ -435,36 +459,21 @@
      */
     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();
+    private native void nativeContentInvalidateAll();
 
     /**
      * 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 +489,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();
@@ -515,8 +524,8 @@
     private native void nativeTouchUp(int touchGeneration,
             int framePtr, int nodePtr, int x, int y);
 
-    private native boolean nativeHandleTouchEvent(int action, int x, int y,
-            int metaState);
+    private native boolean nativeHandleTouchEvent(int action, int[] x, int[] y,
+            int count, int metaState);
 
     private native void nativeUpdateFrameCache();
 
@@ -576,7 +585,17 @@
     /**
      * 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 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(int direction, int granularity);
 
     // EventHub for processing messages
     private final EventHub mEventHub;
@@ -697,6 +716,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;
@@ -704,12 +729,9 @@
 
     static class TouchEventData {
         int mAction;
-        int mX;
-        int mY;
+        Point[] mPoints;
         int mMetaState;
         boolean mReprocess;
-        float mViewX;
-        float mViewY;
     }
 
     static class GeolocationPermissionsData {
@@ -718,9 +740,8 @@
         boolean mRemember;
     }
 
-
-
         static final String[] HandlerDebugString = {
+            "REVEAL_SELECTION", // 96
             "REQUEST_LABEL", // 97
             "UPDATE_FRAME_CACHE_IF_LOADING", // = 98
             "SCROLL_TEXT_INPUT", // = 99
@@ -771,10 +792,12 @@
             "ON_RESUME",    // = 144
             "FREE_MEMORY",  // = 145
             "VALID_NODE_BOUNDS", // = 146
+            "SAVE_WEBARCHIVE", // = 147
         };
 
     class EventHub {
         // Message Ids
+        static final int REVEAL_SELECTION = 96;
         static final int REQUEST_LABEL = 97;
         static final int UPDATE_FRAME_CACHE_IF_LOADING = 98;
         static final int SCROLL_TEXT_INPUT = 99;
@@ -837,6 +860,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 +891,16 @@
         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;
+
+        static final int USE_MOCK_DEVICE_ORIENTATION = 191;
+
+        static final int AUTOFILL_FORM = 192;
+
         // private message ids
         private static final int DESTROY =     200;
 
@@ -885,6 +921,9 @@
          */
         private EventHub() {}
 
+        private static final int FIRST_PACKAGE_MSG_ID = REVEAL_SELECTION;
+        private static final int LAST_PACKAGE_MSG_ID = VALID_NODE_BOUNDS;
+
         /**
          * Transfer all messages to the newly created webcore thread handler.
          */
@@ -896,11 +935,11 @@
                 @Override
                 public void handleMessage(Message msg) {
                     if (DebugFlags.WEB_VIEW_CORE) {
-                        Log.v(LOGTAG, (msg.what < REQUEST_LABEL
-                                || msg.what
-                                > VALID_NODE_BOUNDS ? Integer.toString(msg.what)
+                        Log.v(LOGTAG, (msg.what < FIRST_PACKAGE_MSG_ID
+                                || msg.what > LAST_PACKAGE_MSG_ID
+                                ? Integer.toString(msg.what)
                                 : HandlerDebugString[msg.what
-                                        - REQUEST_LABEL])
+                                        - FIRST_PACKAGE_MSG_ID])
                                 + " arg1=" + msg.arg1 + " arg2=" + msg.arg2
                                 + " obj=" + msg.obj);
                     }
@@ -921,6 +960,10 @@
                             }
                             break;
 
+                        case REVEAL_SELECTION:
+                            nativeRevealSelection();
+                            break;
+
                         case REQUEST_LABEL:
                             if (mWebView != null) {
                                 int nodePointer = msg.arg2;
@@ -1154,12 +1197,19 @@
 
                         case TOUCH_EVENT: {
                             TouchEventData ted = (TouchEventData) msg.obj;
+                            final int count = ted.mPoints.length;
+                            int[] xArray = new int[count];
+                            int[] yArray = new int[count];
+                            for (int c = 0; c < count; c++) {
+                                xArray[c] = ted.mPoints[c].x;
+                                yArray[c] = ted.mPoints[c].y;
+                            }
                             Message.obtain(
                                     mWebView.mPrivateHandler,
                                     WebView.PREVENT_TOUCH_ID,
                                     ted.mAction,
-                                    nativeHandleTouchEvent(ted.mAction, ted.mX,
-                                            ted.mY, ted.mMetaState) ? 1 : 0,
+                                    nativeHandleTouchEvent(ted.mAction, xArray,
+                                            yArray, count, ted.mMetaState) ? 1 : 0,
                                     ted.mReprocess ? ted : null).sendToTarget();
                             break;
                         }
@@ -1238,6 +1288,12 @@
                             nativeSetSelection(msg.arg1, msg.arg2);
                             break;
 
+                        case MODIFY_SELECTION:
+                            String selectionString = nativeModifySelection(msg.arg1, msg.arg2);
+                            mWebView.mPrivateHandler.obtainMessage(WebView.SELECTION_STRING_CHANGED,
+                                    selectionString).sendToTarget();
+                            break;
+
                         case LISTBOX_CHOICES:
                             SparseBooleanArray choices = (SparseBooleanArray)
                                     msg.obj;
@@ -1278,6 +1334,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 +1356,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 +1424,29 @@
                             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;
+
+                        case USE_MOCK_DEVICE_ORIENTATION:
+                            useMockDeviceOrientation();
+                            break;
+
+                        case AUTOFILL_FORM:
+                            nativeAutoFillForm(msg.arg1);
+                            break;
                     }
                 }
             };
@@ -1562,6 +1652,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() + ", "
@@ -1582,11 +1679,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;
             }
@@ -1617,7 +1715,7 @@
         if (mSettings.getUseWideViewPort()) {
             if (mViewportWidth == -1) {
                 if (mSettings.getLayoutAlgorithm() ==
-                        WebSettings.LayoutAlgorithm.NORMAL) {
+                        WebSettings.LayoutAlgorithm.NORMAL || mSettings.getUseFixedViewport()) {
                     width = WebView.DEFAULT_VIEWPORT_WIDTH;
                 } else {
                     /*
@@ -1637,7 +1735,12 @@
                                     nativeGetContentMinPrefWidth())));
                 }
             } else if (mViewportWidth > 0) {
-                width = Math.max(w, mViewportWidth);
+                if (mSettings.getUseFixedViewport()) {
+                    // Use website specified or desired fixed viewport width.
+                    width = mViewportWidth;
+                } else {
+                    width = Math.max(w, mViewportWidth);
+                }
             } else {
                 width = textwrapWidth;
             }
@@ -1682,6 +1785,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;
 
@@ -1691,11 +1802,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;
@@ -1704,19 +1815,22 @@
         int mScrollX;
         int mScrollY;
         boolean mMobileSite;
+        int mViewportWidth;
     }
 
     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;
     }
 
@@ -1724,8 +1838,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;
         }
@@ -1741,9 +1855,18 @@
                                         : mViewportWidth),
                         nativeGetContentMinPrefWidth());
             }
-            if (mRestoreState != null) {
-                draw.mRestoreState = mRestoreState;
-                mRestoreState = null;
+            if (mInitialViewState != null) {
+                draw.mViewState = mInitialViewState;
+                if (mViewportWidth == -1 && mSettings.getUseFixedViewport() &&
+                    mSettings.getUseWideViewPort()) {
+                    final int fixedViewportMargin = mContext.getResources().getDimensionPixelSize(
+                      com.android.internal.R.dimen.fixed_viewport_margin);
+                    // Use website's initial preferred width as the fixed viewport width.
+                    mViewportWidth = Math.min(mSettings.getMaxFixedViewportWidth(),
+                        draw.mMinPrefWidth + 2 * fixedViewportMargin);
+                    draw.mViewState.mViewportWidth = mViewportWidth;
+                }
+                mInitialViewState = null;
             }
             if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
             Message.obtain(mWebView.mPrivateHandler,
@@ -1758,51 +1881,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);
@@ -1824,6 +1902,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) {
@@ -1836,12 +1916,16 @@
 
     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) {
-                    core.mDrawIsScheduled = false;
-                    core.contentDraw();
-                }
+                // always redraw on resume to reenable gif animations
+                core.mDrawIsScheduled = false;
+                core.nativeContentInvalidateAll();
+                core.contentDraw();
             }
         }
     }
@@ -1978,24 +2062,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;
     }
@@ -2012,18 +2078,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
@@ -2037,15 +2109,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
@@ -2087,17 +2161,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;
         }
 
@@ -2118,32 +2192,32 @@
         } 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);
+        mInitialViewState.mViewportWidth = 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;
             }
         }
 
@@ -2184,15 +2258,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);
@@ -2205,7 +2279,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
@@ -2218,20 +2292,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;
+            }
         }
     }
 
@@ -2281,6 +2347,7 @@
     }
 
     private native void nativeUpdateFrameCacheIfLoading();
+    private native void nativeRevealSelection();
     private native String nativeRequestLabel(int framePtr, int nodePtr);
     /**
      * Scroll the focused textfield to (xPercent, y) in document space
@@ -2329,6 +2396,13 @@
         }
     }
 
+    private void setWebTextViewAutoFillable(int queryId) {
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler, WebView.SET_AUTOFILLABLE, queryId,
+                    /* unused */0).sendToTarget();
+        }
+    }
+
     // called by JNI
     private Context getContext() {
         return mContext;
@@ -2469,6 +2543,24 @@
                 hMode, vMode).sendToTarget();
     }
 
+    private void useMockDeviceOrientation() {
+        mDeviceOrientationManager.useMock();
+    }
+
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        mDeviceOrientationManager.setMockOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
+    }
+
+    protected DeviceOrientationService getDeviceOrientationService() {
+        if (mDeviceOrientationService == null) {
+            mDeviceOrientationService =
+                    new DeviceOrientationService(mDeviceOrientationManager, mContext);
+        }
+        return mDeviceOrientationService;
+    }
+
     private native void nativePause();
     private native void nativeResume();
     private native void nativeFreeMemory();
@@ -2476,4 +2568,8 @@
     private native boolean nativeValidNodeAndBounds(int frame, int node,
             Rect bounds);
 
+    private native ArrayList<Rect> nativeGetTouchHighlightRects(int x, int y,
+            int slop);
+
+   private native void nativeAutoFillForm(int queryId);
 }
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 7acd9ba..8f89678 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -67,6 +67,9 @@
     private final Object mFormLock = new Object();
     private final Object mHttpAuthLock = new Object();
 
+    // TODO: The Chromium HTTP stack handles cookies independently.
+    // We should consider removing the cookies table if and when we switch to
+    // the Chromium HTTP stack for good.
     private static final String mTableNames[] = {
         "cookies", "password", "formurl", "formdata", "httpauth"
     };
@@ -173,112 +176,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 +422,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 +470,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 +529,7 @@
      *            deleted.
      */
     void deleteCookies(String domain, String path, String name) {
-        if (domain == null || mDatabase == null) {
+        if (domain == null || !checkInitialized()) {
             return;
         }
 
@@ -501,7 +549,7 @@
      */
     void addCookie(Cookie cookie) {
         if (cookie.domain == null || cookie.path == null || cookie.name == null
-                || mDatabase == null) {
+                || !checkInitialized()) {
             return;
         }
 
@@ -534,7 +582,7 @@
      * Clear cookie database
      */
     void clearCookies() {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -547,7 +595,7 @@
      * Clear session cookies, which means cookie doesn't have EXPIRES.
      */
     void clearSessionCookies() {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -564,7 +612,7 @@
      * @param now Time for now
      */
     void clearExpiredCookies(long now) {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -620,7 +668,7 @@
      * @return CacheResult The CacheManager.CacheResult
      */
     CacheResult getCache(String url) {
-        if (url == null || mCacheDatabase == null) {
+        if (url == null || !checkInitialized()) {
             return null;
         }
 
@@ -660,7 +708,7 @@
      * @param url The url
      */
     void removeCache(String url) {
-        if (url == null || mCacheDatabase == null) {
+        if (url == null || !checkInitialized()) {
             return;
         }
 
@@ -674,7 +722,7 @@
      * @param c The CacheManager.CacheResult
      */
     void addCache(String url, CacheResult c) {
-        if (url == null || mCacheDatabase == null) {
+        if (url == null || !checkInitialized()) {
             return;
         }
 
@@ -700,7 +748,7 @@
      * Clear cache database
      */
     void clearCache() {
-        if (mCacheDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -708,7 +756,7 @@
     }
 
     boolean hasCache() {
-        if (mCacheDatabase == null) {
+        if (!checkInitialized()) {
             return false;
         }
 
@@ -834,7 +882,7 @@
      */
     void setUsernamePassword(String schemePlusHost, String username,
                 String password) {
-        if (schemePlusHost == null || mDatabase == null) {
+        if (schemePlusHost == null || !checkInitialized()) {
             return;
         }
 
@@ -856,7 +904,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;
         }
 
@@ -902,7 +950,7 @@
      * Clear password database
      */
     public void clearUsernamePassword() {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -927,7 +975,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;
         }
 
@@ -952,7 +1000,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;
         }
 
@@ -999,7 +1047,7 @@
      * Clear HTTP authentication password database
      */
     public void clearHttpAuthUsernamePassword() {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -1020,7 +1068,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;
         }
 
@@ -1069,7 +1117,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;
         }
 
@@ -1129,7 +1177,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..2096fc4
--- /dev/null
+++ b/core/java/android/webkit/ZoomManager.java
@@ -0,0 +1,945 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+    /*
+     * For large screen devices, the defaultScale usually set to 1.0 and
+     * equal to the overview scale, to differentiate the zoom level for double tapping,
+     * a default reading level scale is used.
+     */
+    private static final float DEFAULT_READING_LEVEL_SCALE = 1.5f;
+
+    /*
+     * 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;
+    
+    /**
+     * True if we have a touch panel capable of detecting smooth pan/scale at the same time
+     */
+    private boolean mAllowPanAndScale;
+
+    // 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 getReadingLevelScale() {
+        // The reading scale is at least 0.5f apart from the overview scale.
+        final float MIN_SCALE_DIFF = 0.5f;
+        final float zoomOverviewScale = getZoomOverviewScale();
+        if (zoomOverviewScale > DEFAULT_READING_LEVEL_SCALE) {
+            return Math.min(DEFAULT_READING_LEVEL_SCALE,
+                zoomOverviewScale - MIN_SCALE_DIFF);
+        }
+        return Math.max(zoomOverviewScale + MIN_SCALE_DIFF,
+            DEFAULT_READING_LEVEL_SCALE);
+    }
+
+    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 reading level scale if it is close
+        if (!exceedsMinScaleIncrement(scale, getReadingLevelScale())) {
+            scale = getReadingLevelScale();
+        }
+
+        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)
+                // For tablet, not much need to reflow text w/o double tapping.
+                && !settings.getUseFixedViewport()) {
+            refreshZoomScale(true);
+        } else if (!mInZoomOverview) {
+            zoomToOverview();
+        } else {
+            zoomToReadingLevel();
+        }
+    }
+
+    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 zoomToReadingLevel() {
+        final float readingScale = getReadingLevelScale();
+        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 * readingScale / (readingScale - mActualScale);
+            } else {
+                mWebView.scrollBy(viewLeft, 0);
+                mZoomCenterX = 0;
+            }
+        }
+        startZoomAnimation(readingScale, true);
+    }
+
+    public void updateMultiTouchSupport(Context context) {
+        // check the preconditions
+        assert mWebView.getSettings() != null;
+
+        final WebSettings settings = mWebView.getSettings();
+        final PackageManager pm = context.getPackageManager();
+        mSupportMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
+                && settings.supportZoom() && settings.getBuiltInZoomControls();
+        mAllowPanAndScale = pm.hasSystemFeature(
+                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
+        if (mSupportMultiTouch && (mScaleDetector == null)) {
+            mScaleDetector = new ScaleGestureDetector(context, new ScaleDetectorListener());
+        } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
+            mScaleDetector = null;
+        }
+    }
+
+    public boolean supportsMultiTouchZoom() {
+        return mSupportMultiTouch;
+    }
+
+    public boolean supportsPanDuringZoom() {
+        return mAllowPanAndScale;
+    }
+
+    /**
+     * 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.
+                setZoomScale(Math.max(mActualScale, getZoomOverviewScale()),
+                    mUpdateTextWrap, true);
+                // 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;
+        }
+        if (viewState.mViewportWidth > 0 &&
+            mWebView.getSettings().getUseFixedViewport() &&
+            mWebView.getSettings().getUseWideViewPort()) {
+            // Use website specified viewport width.
+            setZoomOverviewWidth(viewState.mViewportWidth);
+        }
+    }
+
+    /**
+     * 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().getUseFixedViewport()
+            && 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() || settings.getUseFixedViewport())) {
+                    mInitialZoomOverview = true;
+                    scale = (float) mWebView.getViewWidth() / mZoomOverviewWidth;
+                } 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)
+                        && mWebView.getSettings().getDisplayZoomControls()) {
+                    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/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 4558854..c694ff1 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -19,7 +19,7 @@
 import com.android.internal.R;
 
 import android.content.Context;
-import android.content.res.Resources;
+import android.content.Intent;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -34,11 +34,16 @@
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseBooleanArray;
+import android.view.ActionMode;
 import android.view.ContextMenu.ContextMenuInfo;
 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.VelocityTracker;
 import android.view.View;
@@ -46,7 +51,6 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
-import android.view.animation.AnimationUtils;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -70,10 +74,12 @@
  * @attr ref android.R.styleable#AbsListView_cacheColorHint
  * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
  * @attr ref android.R.styleable#AbsListView_smoothScrollbar
+ * @attr ref android.R.styleable#AbsListView_choiceMode
  */
 public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
         ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
-        ViewTreeObserver.OnTouchModeChangeListener {
+        ViewTreeObserver.OnTouchModeChangeListener,
+        RemoteViewsAdapter.RemoteAdapterConnectionCallback {
 
     /**
      * Disables the transcript mode.
@@ -130,17 +136,6 @@
     static final int TOUCH_MODE_FLING = 4;
 
     /**
-     * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
-     */
-    static final int TOUCH_MODE_OVERSCROLL = 5;
-
-    /**
-     * Indicates the view is being flung outside of normal content bounds
-     * and will spring back.
-     */
-    static final int TOUCH_MODE_OVERFLING = 6;
-
-    /**
      * Regular layout - usually an unsolicited layout from the view system
      */
     static final int LAYOUT_NORMAL = 0;
@@ -178,6 +173,57 @@
     static final int LAYOUT_MOVE_SELECTION = 6;
 
     /**
+     * Normal list that does not indicate choices
+     */
+    public static final int CHOICE_MODE_NONE = 0;
+
+    /**
+     * The list allows up to one choice
+     */
+    public static final int CHOICE_MODE_SINGLE = 1;
+
+    /**
+     * The list allows multiple choices
+     */
+    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;
+
+    /**
+     * Controls if/how the user may choose/check items in the list
+     */
+    int mChoiceMode = CHOICE_MODE_NONE;
+
+    /**
+     * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
+     */
+    ActionMode mChoiceActionMode;
+
+    /**
+     * Wrapper for the multiple choice mode callback; AbsListView needs to perform
+     * a few extra actions around what application code does.
+     */
+    MultiChoiceModeWrapper mMultiChoiceModeCallback;
+
+    /**
+     * Running count of how many items are currently checked
+     */
+    int mCheckedItemCount;
+
+    /**
+     * Running state of which positions are currently checked
+     */
+    SparseBooleanArray mCheckStates;
+
+    /**
+     * Running state of which IDs are currently checked
+     */
+    LongSparseArray<Boolean> mCheckedIdStates;
+
+    /**
      * Controls how the next layout will happen
      */
     int mLayoutMode = LAYOUT_NORMAL;
@@ -193,6 +239,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;
@@ -382,16 +433,6 @@
     private ContextMenuInfo mContextMenuInfo = null;
     
     /**
-     * Maximum distance to record overscroll
-     */
-    int mOverscrollMax;
-
-    /**
-     * Content height divided by this is the overscroll limit.
-     */
-    static final int OVERSCROLL_LIMIT_DIVISOR = 3;
-
-    /**
      * Used to request a layout when we changed touch mode
      */
     private static final int TOUCH_MODE_UNKNOWN = -1;
@@ -484,48 +525,6 @@
     private static final int INVALID_POINTER = -1;
 
     /**
-     * Maximum distance to overscroll by during edge effects
-     */
-    int mOverscrollDistance;
-
-    /**
-     * Maximum distance to overfling during edge effects
-     */
-    int mOverflingDistance;
-
-    // These two EdgeGlows are always set and used together.
-    // Checking one for null is as good as checking both.
-
-    /**
-     * Tracks the state of the top edge glow.
-     */
-    private EdgeGlow mEdgeGlowTop;
-
-    /**
-     * Tracks the state of the bottom edge glow.
-     */
-    private EdgeGlow mEdgeGlowBottom;
-
-    /**
-     * An estimate of how many pixels are between the top of the list and
-     * the top of the first position in the adapter, based on the last time
-     * we saw it. Used to hint where to draw edge glows.
-     */
-    private int mFirstPositionDistanceGuess;
-
-    /**
-     * An estimate of how many pixels are between the bottom of the list and
-     * the bottom of the last position in the adapter, based on the last time
-     * we saw it. Used to hint where to draw edge glows.
-     */
-    private int mLastPositionDistanceGuess;
-
-    /**
-     * Used for determining when to cancel out of overscroll.
-     */
-    private int mDirection = 0;
-
-    /**
      * Interface definition for a callback to be invoked when the list or grid
      * has been scrolled.
      */
@@ -624,6 +623,18 @@
 
         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));
+                }
+            });
+        }
+
+        setChoiceMode(a.getInt(R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
 
         a.recycle();
     }
@@ -640,39 +651,312 @@
         mTouchSlop = configuration.getScaledTouchSlop();
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
-        mOverscrollDistance = configuration.getScaledOverscrollDistance();
-        mOverflingDistance = configuration.getScaledOverflingDistance();
-
         mDensityScale = getContext().getResources().getDisplayMetrics().density;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public void setOverscrollMode(int mode) {
-        if (mode != OVERSCROLL_NEVER) {
-            if (mEdgeGlowTop == null) {
-                final Resources res = getContext().getResources();
-                final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
-                final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
-                mEdgeGlowTop = new EdgeGlow(edge, glow);
-                mEdgeGlowBottom = new EdgeGlow(edge, glow);
+    public void setAdapter(ListAdapter adapter) {
+        if (adapter != null) {
+            if (mChoiceMode != CHOICE_MODE_NONE && mAdapter.hasStableIds() &&
+                    mCheckedIdStates == null) {
+                mCheckedIdStates = new LongSparseArray<Boolean>();
             }
-        } else {
-            mEdgeGlowTop = null;
-            mEdgeGlowBottom = null;
         }
-        super.setOverscrollMode(mode);
+
+        if (mCheckStates != null) {
+            mCheckStates.clear();
+        }
+
+        if (mCheckedIdStates != null) {
+            mCheckedIdStates.clear();
+        }
     }
 
     /**
-     * @return true if all list content currently fits within the view boundaries
+     * 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()
      */
-    private boolean contentFits() {
-        final int childCount = getChildCount();
-        if (childCount != mItemCount) {
-            return false;
+    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}.
+     *
+     * @param position The item whose checked state to return
+     * @return The item's checked state or <code>false</code> if choice mode
+     *         is invalid
+     *
+     * @see #setChoiceMode(int)
+     */
+    public boolean isItemChecked(int position) {
+        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
+            return mCheckStates.get(position);
         }
 
-        return getChildAt(0).getTop() >= 0 && getChildAt(childCount - 1).getBottom() <= mBottom;
+        return false;
+    }
+
+    /**
+     * Returns the currently checked item. The result is only valid if the choice
+     * mode has been set to {@link #CHOICE_MODE_SINGLE}.
+     *
+     * @return The position of the currently checked item or
+     *         {@link #INVALID_POSITION} if nothing is selected
+     *
+     * @see #setChoiceMode(int)
+     */
+    public int getCheckedItemPosition() {
+        if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
+            return mCheckStates.keyAt(0);
+        }
+
+        return INVALID_POSITION;
+    }
+
+    /**
+     * Returns the set of checked items in the list. The result is only valid if
+     * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
+     *
+     * @return  A SparseBooleanArray which will return true for each call to
+     *          get(int position) where position is a position in the list,
+     *          or <code>null</code> if the choice mode is set to
+     *          {@link #CHOICE_MODE_NONE}.
+     */
+    public SparseBooleanArray getCheckedItemPositions() {
+        if (mChoiceMode != CHOICE_MODE_NONE) {
+            return mCheckStates;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the set of checked items ids. The result is only valid if the
+     * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
+     * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
+     *
+     * @return A new array which contains the id of each checked item in the
+     *         list.
+     */
+    public long[] getCheckedItemIds() {
+        if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
+            return new long[0];
+        }
+
+        final LongSparseArray<Boolean> idStates = mCheckedIdStates;
+        final int count = idStates.size();
+        final long[] ids = new long[count];
+
+        for (int i = 0; i < count; i++) {
+            ids[i] = idStates.keyAt(i);
+        }
+
+        return ids;
+    }
+
+    /**
+     * Clear any choices previously set
+     */
+    public void clearChoices() {
+        if (mCheckStates != null) {
+            mCheckStates.clear();
+        }
+        if (mCheckedIdStates != null) {
+            mCheckedIdStates.clear();
+        }
+        mCheckedItemCount = 0;
+    }
+
+    /**
+     * Sets the checked state of the specified position. The is only valid if
+     * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
+     * {@link #CHOICE_MODE_MULTIPLE}.
+     *
+     * @param position The item whose checked state is to be checked
+     * @param value The new checked state for the item
+     */
+    public void setItemChecked(int position, boolean value) {
+        if (mChoiceMode == CHOICE_MODE_NONE) {
+            return;
+        }
+
+        // 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) {
+                    mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+                } else {
+                    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
+            // selected item
+            if (value || isItemChecked(position)) {
+                mCheckStates.clear();
+                if (updateIds) {
+                    mCheckedIdStates.clear();
+                }
+            }
+            // this may end up selecting the value we just cleared but this way
+            // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
+            if (value) {
+                mCheckStates.put(position, true);
+                if (updateIds) {
+                    mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+                }
+                mCheckedItemCount = 1;
+            } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
+                mCheckedItemCount = 0;
+            }
+        }
+
+        // Do not generate a data change while we are in the layout phase
+        if (!mInLayout && !mBlockLayoutRequests) {
+            mDataChanged = true;
+            rememberSyncState();
+            requestLayout();
+        }
+    }
+
+    @Override
+    public boolean performItemClick(View view, int position, long id) {
+        boolean handled = false;
+
+        if (mChoiceMode != CHOICE_MODE_NONE) {
+            handled = true;
+
+            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()) {
+                    if (newValue) {
+                        mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+                    } else {
+                        mCheckedIdStates.delete(mAdapter.getItemId(position));
+                    }
+                }
+                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();
+                    mCheckStates.put(position, true);
+                    if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
+                        mCheckedIdStates.clear();
+                        mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+                    }
+                    mCheckedItemCount = 1;
+                } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
+                    mCheckedItemCount = 0;
+                }
+            }
+
+            mDataChanged = true;
+            rememberSyncState();
+            requestLayout();
+        }
+
+        handled |= super.performItemClick(view, position, id);
+
+        return handled;
+    }
+
+    /**
+     * @see #setChoiceMode(int)
+     *
+     * @return The current choice mode
+     */
+    public int getChoiceMode() {
+        return mChoiceMode;
+    }
+
+    /**
+     * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
+     * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
+     * List allows up to one item to  be in a chosen state. By setting the choiceMode to
+     * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
+     *
+     * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
+     * {@link #CHOICE_MODE_MULTIPLE}
+     */
+    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();
+            }
+            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);
     }
 
     /**
@@ -893,6 +1177,8 @@
         int position;
         int height;
         String filter;
+        SparseBooleanArray checkState;
+        LongSparseArray<Boolean> checkIdState;
 
         /**
          * Constructor called from {@link AbsListView#onSaveInstanceState()}
@@ -912,6 +1198,13 @@
             position = in.readInt();
             height = in.readInt();
             filter = in.readString();
+            checkState = in.readSparseBooleanArray();
+            long[] idState = in.createLongArray();
+
+            if (idState.length > 0) {
+                checkIdState = new LongSparseArray<Boolean>();
+                checkIdState.setValues(idState, Boolean.TRUE);
+            }
         }
 
         @Override
@@ -923,6 +1216,8 @@
             out.writeInt(position);
             out.writeInt(height);
             out.writeString(filter);
+            out.writeSparseBooleanArray(checkState);
+            out.writeLongArray(checkIdState != null ? checkIdState.getKeys() : new long[0]);
         }
 
         @Override
@@ -934,7 +1229,8 @@
                     + " viewTop=" + viewTop
                     + " position=" + position
                     + " height=" + height
-                    + " filter=" + filter + "}";
+                    + " filter=" + filter
+                    + " checkState=" + checkState + "}";
         }
 
         public static final Parcelable.Creator<SavedState> CREATOR
@@ -998,6 +1294,9 @@
             }
         }
 
+        ss.checkState = mCheckStates;
+        ss.checkIdState = mCheckedIdStates;
+
         return ss;
     }
 
@@ -1029,6 +1328,14 @@
 
         setFilterText(ss.filter);
 
+        if (ss.checkState != null) {
+            mCheckStates = ss.checkState;
+        }
+
+        if (ss.checkIdState != null) {
+            mCheckedIdStates = ss.checkIdState;
+        }
+
         requestLayout();
     }
 
@@ -1171,10 +1478,6 @@
         int result;
         if (mSmoothScrollbarEnabled) {
             result = Math.max(mItemCount * 100, 0);
-            if (mScrollY != 0) {
-                // Compensate for overscroll
-                result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
-            }
         } else {
             result = mItemCount;
         }
@@ -1247,8 +1550,6 @@
 
         layoutChildren();
         mInLayout = false;
-
-        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
     }
 
     /**
@@ -1678,6 +1979,11 @@
                 treeObserver.addOnGlobalLayoutListener(this);
             }
         }
+        
+        if (mAdapter != null && mDataSetObserver == null) {
+            mDataSetObserver = new AdapterDataSetObserver();
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+        }
     }
 
     @Override
@@ -1698,6 +2004,11 @@
                 mGlobalLayoutListenerAddedFilter = false;
             }
         }
+
+        if (mAdapter != null) {
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
+            mDataSetObserver = null;
+        }
     }
 
     @Override
@@ -1715,7 +2026,6 @@
                 mFlingRunnable.endFling();
                 if (mScrollY != 0) {
                     mScrollY = 0;
-                    finishGlows();
                     invalidate();
                 }
             }
@@ -1849,10 +2159,21 @@
         }
     }
 
-    private boolean performLongPress(final View child,
+    boolean performLongPress(final View child,
             final int longPressPosition, final long longPressId) {
-        boolean handled = false;
+        // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
+        if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
+            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 true;
+        }
 
+        boolean handled = false;
         if (mOnItemLongClickListener != null) {
             handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
                     longPressPosition, longPressId);
@@ -2025,10 +2346,9 @@
         // Check if we have moved far enough that it looks more like a
         // scroll than a tap
         final int distance = Math.abs(deltaY);
-        final boolean overscroll = mScrollY != 0;
-        if (overscroll || distance > mTouchSlop) {
+        if (distance > mTouchSlop) {
             createScrollingCache();
-            mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL;
+            mTouchMode = TOUCH_MODE_SCROLL;
             mMotionCorrection = deltaY;
             final Handler handler = getHandler();
             // Handler should not be null unless the AbsListView is not attached to a
@@ -2064,19 +2384,6 @@
                 // touch mode). Force an initial layout to get rid of the selection.
                 layoutChildren();
             }
-        } else {
-            int touchMode = mTouchMode;
-            if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
-                if (mFlingRunnable != null) {
-                    mFlingRunnable.endFling();
-                }
-
-                if (mScrollY != 0) {
-                    mScrollY = 0;
-                    finishGlows();
-                    invalidate();
-                }
-            }
         }
     }
 
@@ -2107,63 +2414,49 @@
 
         switch (action & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN: {
-            switch (mTouchMode) {
-            case TOUCH_MODE_OVERFLING: {
-                mFlingRunnable.endFling();
-                mTouchMode = TOUCH_MODE_OVERSCROLL;
-                mMotionY = mLastY = (int) ev.getY();
-                mMotionCorrection = 0;
-                mActivePointerId = ev.getPointerId(0);
-                break;
-            }
+            mActivePointerId = ev.getPointerId(0);
+            final int x = (int) ev.getX();
+            final int y = (int) ev.getY();
+            int motionPosition = pointToPosition(x, y);
+            if (!mDataChanged) {
+                if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
+                        && (getAdapter().isEnabled(motionPosition))) {
+                    // User clicked on an actual view (and was not stopping a fling). It might be a
+                    // click or a scroll. Assume it is a click until proven otherwise
+                    mTouchMode = TOUCH_MODE_DOWN;
+                    // FIXME Debounce
+                    if (mPendingCheckForTap == null) {
+                        mPendingCheckForTap = new CheckForTap();
+                    }
+                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+                } else {
+                    if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
+                        // If we couldn't find a view to click on, but the down event was touching
+                        // the edge, we will bail out and try again. This allows the edge correcting
+                        // code in ViewRoot to try to find a nearby view to select
+                        return false;
+                    }
 
-            default: {
-                mActivePointerId = ev.getPointerId(0);
-                final int x = (int) ev.getX();
-                final int y = (int) ev.getY();
-                int motionPosition = pointToPosition(x, y);
-                if (!mDataChanged) {
-                    if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
-                            && (getAdapter().isEnabled(motionPosition))) {
-                        // User clicked on an actual view (and was not stopping a fling). It might be a
-                        // click or a scroll. Assume it is a click until proven otherwise
-                        mTouchMode = TOUCH_MODE_DOWN;
-                        // FIXME Debounce
-                        if (mPendingCheckForTap == null) {
-                            mPendingCheckForTap = new CheckForTap();
-                        }
-                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
-                    } else {
-                        if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
-                            // If we couldn't find a view to click on, but the down event was touching
-                            // the edge, we will bail out and try again. This allows the edge correcting
-                            // code in ViewRoot to try to find a nearby view to select
-                            return false;
-                        }
-
-                        if (mTouchMode == TOUCH_MODE_FLING) {
-                            // Stopped a fling. It is a scroll.
-                            createScrollingCache();
-                            mTouchMode = TOUCH_MODE_SCROLL;
-                            mMotionCorrection = 0;
-                            motionPosition = findMotionRow(y);
-                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
-                        }
+                    if (mTouchMode == TOUCH_MODE_FLING) {
+                        // Stopped a fling. It is a scroll.
+                        createScrollingCache();
+                        mTouchMode = TOUCH_MODE_SCROLL;
+                        mMotionCorrection = 0;
+                        motionPosition = findMotionRow(y);
+                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                     }
                 }
+            }
 
-                if (motionPosition >= 0) {
-                    // Remember where the motion event started
-                    v = getChildAt(motionPosition - mFirstPosition);
-                    mMotionViewOriginalTop = v.getTop();
-                }
-                mMotionX = x;
-                mMotionY = y;
-                mMotionPosition = motionPosition;
-                mLastY = Integer.MIN_VALUE;
-                break;
+            if (motionPosition >= 0) {
+                // Remember where the motion event started
+                v = getChildAt(motionPosition - mFirstPosition);
+                mMotionViewOriginalTop = v.getTop();
             }
-            }
+            mMotionX = x;
+            mMotionY = y;
+            mMotionPosition = motionPosition;
+            mLastY = Integer.MIN_VALUE;
             break;
         }
 
@@ -2196,25 +2489,9 @@
                         requestDisallowInterceptTouchEvent(true);
                     }
 
-                    final int rawDeltaY = deltaY;
                     deltaY -= mMotionCorrection;
                     int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
                     
-                    final int motionIndex;
-                    if (mMotionPosition >= 0) {
-                        motionIndex = mMotionPosition - mFirstPosition;
-                    } else {
-                        // If we don't have a motion position that we can reliably track,
-                        // pick something in the middle to make a best guess at things below.
-                        motionIndex = getChildCount() / 2;
-                    }
-
-                    int motionViewPrevTop = 0;
-                    View motionView = this.getChildAt(motionIndex);
-                    if (motionView != null) {
-                        motionViewPrevTop = motionView.getTop();
-                    }
-
                     // No need to do all this work if we're not going to move anyway
                     boolean atEdge = false;
                     if (incrementalDeltaY != 0) {
@@ -2222,105 +2499,23 @@
                     }
 
                     // Check to see if we have bumped into the scroll limit
-                    motionView = this.getChildAt(motionIndex);
-                    if (motionView != null) {
-                        // Check if the top of the motion view is where it is
-                        // supposed to be
-                        final int motionViewRealTop = motionView.getTop();
-                        if (atEdge) {
-                            // Apply overscroll
-
-                            int overscroll = -incrementalDeltaY -
-                                    (motionViewRealTop - motionViewPrevTop);
-                            overscrollBy(0, overscroll, 0, mScrollY, 0, 0,
-                                    0, mOverscrollDistance, true);
-                            if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
-                                // Don't allow overfling if we're at the edge.
-                                mVelocityTracker.clear();
-                            }
-
-                            final int overscrollMode = getOverscrollMode();
-                            if (overscrollMode == OVERSCROLL_ALWAYS ||
-                                    (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS &&
-                                            !contentFits())) {
-                                mDirection = 0; // Reset when entering overscroll.
-                                mTouchMode = TOUCH_MODE_OVERSCROLL;
-                                if (rawDeltaY > 0) {
-                                    mEdgeGlowTop.onPull((float) overscroll / getHeight());
-                                } else if (rawDeltaY < 0) {
-                                    mEdgeGlowBottom.onPull((float) overscroll / getHeight());
-                                }
-                            }
+                    if (atEdge && getChildCount() > 0) {
+                        // Treat this like we're starting a new scroll from the current
+                        // position. This will let the user start scrolling back into
+                        // content immediately rather than needing to scroll back to the
+                        // point where they hit the limit first.
+                        int motionPosition = findMotionRow(y);
+                        if (motionPosition >= 0) {
+                            final View motionView = getChildAt(motionPosition - mFirstPosition);
+                            mMotionViewOriginalTop = motionView.getTop();
                         }
                         mMotionY = y;
+                        mMotionPosition = motionPosition;
                         invalidate();
                     }
                     mLastY = y;
                 }
                 break;
-
-            case TOUCH_MODE_OVERSCROLL:
-                if (y != mLastY) {
-                    final int rawDeltaY = deltaY;
-                    deltaY -= mMotionCorrection;
-                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
-
-                    final int oldScroll = mScrollY;
-                    final int newScroll = oldScroll - incrementalDeltaY;
-                    int newDirection = y > mLastY ? 1 : -1;
-
-                    if (mDirection == 0) {
-                        mDirection = newDirection;
-                    }
-
-                    if (mDirection != newDirection) {
-                        // Coming back to 'real' list scrolling
-                        incrementalDeltaY = -newScroll;
-                        mScrollY = 0;
-
-                        // No need to do all this work if we're not going to move anyway
-                        if (incrementalDeltaY != 0) {
-                            trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
-                        }
-
-                        // Check to see if we are back in
-                        View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
-                        if (motionView != null) {
-                            mTouchMode = TOUCH_MODE_SCROLL;
-
-                            // We did not scroll the full amount. Treat this essentially like the
-                            // start of a new touch scroll
-                            final int motionPosition = findClosestMotionRow(y);
-
-                            mMotionCorrection = 0;
-                            motionView = getChildAt(motionPosition - mFirstPosition);
-                            mMotionViewOriginalTop = motionView.getTop();
-                            mMotionY = y;
-                            mMotionPosition = motionPosition;
-                        }
-                    } else {
-                        overscrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0,
-                                0, mOverscrollDistance, true);
-                        final int overscrollMode = getOverscrollMode();
-                        if (overscrollMode == OVERSCROLL_ALWAYS ||
-                                (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS &&
-                                        !contentFits())) {
-                            if (rawDeltaY > 0) {
-                                mEdgeGlowTop.onPull((float) -incrementalDeltaY / getHeight());
-                            } else if (rawDeltaY < 0) {
-                                mEdgeGlowBottom.onPull((float) -incrementalDeltaY / getHeight());
-                            }
-                            invalidate();
-                        }
-                        if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
-                            // Don't allow overfling if we're at the edge.
-                            mVelocityTracker.clear();
-                        }
-                    }
-                    mLastY = y;
-                    mDirection = newDirection;
-                }
-                break;
             }
 
             break;
@@ -2392,29 +2587,18 @@
             case TOUCH_MODE_SCROLL:
                 final int childCount = getChildCount();
                 if (childCount > 0) {
-                    final int firstChildTop = getChildAt(0).getTop();
-                    final int lastChildBottom = getChildAt(childCount - 1).getBottom();
-                    final int contentTop = mListPadding.top;
-                    final int contentBottom = getHeight() - mListPadding.bottom;
-                    if (mFirstPosition == 0 && firstChildTop >= contentTop &&
+                    if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top &&
                             mFirstPosition + childCount < mItemCount &&
-                            lastChildBottom <= getHeight() - contentBottom) {
+                            getChildAt(childCount - 1).getBottom() <=
+                                    getHeight() - mListPadding.bottom) {
                         mTouchMode = TOUCH_MODE_REST;
                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                     } else {
                         final VelocityTracker velocityTracker = mVelocityTracker;
                         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                         final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
-
-                        // Fling if we have enough velocity and we aren't at a boundary.
-                        // Since we can potentially overfling more than we can overscroll, don't
-                        // allow the weird behavior where you can scroll to a boundary then
-                        // fling further.
-                        if (Math.abs(initialVelocity) > mMinimumVelocity &&
-                                !((mFirstPosition == 0 &&
-                                        firstChildTop == contentTop - mOverscrollDistance) ||
-                                  (mFirstPosition + childCount == mItemCount &&
-                                        lastChildBottom == contentBottom + mOverscrollDistance))) {
+    
+                        if (Math.abs(initialVelocity) > mMinimumVelocity) {
                             if (mFlingRunnable == null) {
                                 mFlingRunnable = new FlingRunnable();
                             }
@@ -2431,32 +2615,10 @@
                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                 }
                 break;
-
-            case TOUCH_MODE_OVERSCROLL:
-                if (mFlingRunnable == null) {
-                    mFlingRunnable = new FlingRunnable();
-                }
-                final VelocityTracker velocityTracker = mVelocityTracker;
-                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
-
-                reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
-                if (Math.abs(initialVelocity) > mMinimumVelocity) {
-                    mFlingRunnable.startOverfling(-initialVelocity);
-                } else {
-                    mFlingRunnable.startSpringback();
-                }
-
-                break;
             }
 
             setPressed(false);
 
-            if (mEdgeGlowTop != null) {
-                mEdgeGlowTop.onRelease();
-                mEdgeGlowBottom.onRelease();
-            }
-
             // Need to redraw since we probably aren't drawing the selector anymore
             invalidate();
 
@@ -2482,42 +2644,24 @@
         }
 
         case MotionEvent.ACTION_CANCEL: {
-            switch (mTouchMode) {
-            case TOUCH_MODE_OVERSCROLL:
-                if (mFlingRunnable == null) {
-                    mFlingRunnable = new FlingRunnable();
-                }
-                mFlingRunnable.startSpringback();
-                break;
+            mTouchMode = TOUCH_MODE_REST;
+            setPressed(false);
+            View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
+            if (motionView != null) {
+                motionView.setPressed(false);
+            }
+            clearScrollingCache();
 
-            case TOUCH_MODE_OVERFLING:
-                // Do nothing - let it play out.
-                break;
+            final Handler handler = getHandler();
+            if (handler != null) {
+                handler.removeCallbacks(mPendingCheckForLongPress);
+            }
 
-            default:
-                mTouchMode = TOUCH_MODE_REST;
-                setPressed(false);
-                View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
-                if (motionView != null) {
-                    motionView.setPressed(false);
-                }
-                clearScrollingCache();
-
-                final Handler handler = getHandler();
-                if (handler != null) {
-                    handler.removeCallbacks(mPendingCheckForLongPress);
-                }
-
-                if (mVelocityTracker != null) {
-                    mVelocityTracker.recycle();
-                    mVelocityTracker = null;
-                }
+            if (mVelocityTracker != null) {
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
             }
             
-            if (mEdgeGlowTop != null) {
-                mEdgeGlowTop.onRelease();
-                mEdgeGlowBottom.onRelease();
-            }
             mActivePointerId = INVALID_POINTER;
             break;
         }
@@ -2542,61 +2686,10 @@
     }
 
     @Override
-    protected void onOverscrolled(int scrollX, int scrollY,
-            boolean clampedX, boolean clampedY) {
-        mScrollY = scrollY;
-
-        if (clampedY) {
-            // Velocity is broken by hitting the limit; don't start a fling off of this.
-            if (mVelocityTracker != null) {
-                mVelocityTracker.clear();
-            }
-        }
-        awakenScrollBars();
-    }
-
-    @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
-        if (mEdgeGlowTop != null) {
-            final int scrollY = mScrollY;
-            if (!mEdgeGlowTop.isFinished()) {
-                final int restoreCount = canvas.save();
-                final int width = getWidth();
-
-                canvas.translate(-width / 2, Math.min(0, scrollY + mFirstPositionDistanceGuess));
-                mEdgeGlowTop.setSize(width * 2, getHeight());
-                if (mEdgeGlowTop.draw(canvas)) {
-                    invalidate();
-                }
-                canvas.restoreToCount(restoreCount);
-            }
-            if (!mEdgeGlowBottom.isFinished()) {
-                final int restoreCount = canvas.save();
-                final int width = getWidth();
-                final int height = getHeight();
-
-                canvas.translate(-width / 2,
-                        Math.max(height, scrollY + mLastPositionDistanceGuess));
-                canvas.rotate(180, width, 0);
-                mEdgeGlowBottom.setSize(width * 2, height);
-                if (mEdgeGlowBottom.draw(canvas)) {
-                    invalidate();
-                }
-                canvas.restoreToCount(restoreCount);
-            }
-        }
         if (mFastScroller != null) {
-            final int scrollY = mScrollY;
-            if (scrollY != 0) {
-                // Pin to the top/bottom during overscroll
-                int restoreCount = canvas.save();
-                canvas.translate(0, (float) scrollY);
-                mFastScroller.draw(canvas);
-                canvas.restoreToCount(restoreCount);
-            } else {
-                mFastScroller.draw(canvas);
-            }
+            mFastScroller.draw(canvas);
         }
     }
 
@@ -2615,10 +2708,6 @@
         switch (action & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN: {
             int touchMode = mTouchMode;
-            if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
-                mMotionCorrection = 0;
-                return true;
-            }
             
             final int x = (int) ev.getX();
             final int y = (int) ev.getY();
@@ -2683,7 +2772,6 @@
             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
             mMotionX = (int) ev.getX(newPointerIndex);
             mMotionY = (int) ev.getY(newPointerIndex);
-            mMotionCorrection = 0;
             mActivePointerId = ev.getPointerId(newPointerIndex);
             if (mVelocityTracker != null) {
                 mVelocityTracker.clear();
@@ -2739,7 +2827,7 @@
         /**
          * Tracks the decay of a fling scroll
          */
-        private final OverScroller mScroller;
+        private final Scroller mScroller;
 
         /**
          * Y value reported by mScroller on the previous fling
@@ -2747,7 +2835,7 @@
         private int mLastFlingY;
 
         FlingRunnable() {
-            mScroller = new OverScroller(getContext());
+            mScroller = new Scroller(getContext());
         }
 
         void start(int initialVelocity) {
@@ -2766,42 +2854,6 @@
             }
         }
 
-        void startSpringback() {
-            if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) {
-                mTouchMode = TOUCH_MODE_OVERFLING;
-                invalidate();
-                post(this);
-            } else {
-                mTouchMode = TOUCH_MODE_REST;
-            }
-        }
-
-        void startOverfling(int initialVelocity) {
-            final int min = mScrollY > 0 ? Integer.MIN_VALUE : 0;
-            final int max = mScrollY > 0 ? 0 : Integer.MAX_VALUE;
-            mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, min, max, 0, getHeight());
-            mTouchMode = TOUCH_MODE_OVERFLING;
-            invalidate();
-            post(this);
-        }
-
-        void edgeReached(int delta) {
-            mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
-            final int overscrollMode = getOverscrollMode();
-            if (overscrollMode == OVERSCROLL_ALWAYS ||
-                    (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
-                mTouchMode = TOUCH_MODE_OVERFLING;
-                final int vel = (int) mScroller.getCurrVelocity();
-                if (delta > 0) {
-                    mEdgeGlowTop.onAbsorb(vel);
-                } else {
-                    mEdgeGlowBottom.onAbsorb(vel);
-                }
-            }
-            invalidate();
-            post(this);
-        }
-
         void startScroll(int distance, int duration) {
             int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
             mLastFlingY = initialY;
@@ -2834,7 +2886,7 @@
                     return;
                 }
 
-                final OverScroller scroller = mScroller;
+                final Scroller scroller = mScroller;
                 boolean more = scroller.computeScrollOffset();
                 final int y = scroller.getCurrY();
 
@@ -2863,24 +2915,7 @@
                     delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
                 }
 
-                // Check to see if we have bumped into the scroll limit
-                View motionView = getChildAt(mMotionPosition - mFirstPosition);
-                int oldTop = 0;
-                if (motionView != null) {
-                    oldTop = motionView.getTop();
-                }
-
                 final boolean atEnd = trackMotionScroll(delta, delta);
-                if (atEnd) {
-                    if (motionView != null) {
-                        // Tweak the scroll for how far we overshot
-                        int overshoot = -(delta - (motionView.getTop() - oldTop));
-                        overscrollBy(0, overshoot, 0, mScrollY, 0, 0,
-                                0, mOverflingDistance, false);
-                    }
-                    edgeReached(delta);
-                    break;
-                }
 
                 if (more && !atEnd) {
                     invalidate();
@@ -2898,24 +2933,6 @@
                 }
                 break;
             }
-
-            case TOUCH_MODE_OVERFLING: {
-                final OverScroller scroller = mScroller;
-                if (scroller.computeScrollOffset()) {
-                    final int scrollY = mScrollY;
-                    final int deltaY = scroller.getCurrY() - scrollY;
-                    if (overscrollBy(0, deltaY, 0, scrollY, 0, 0,
-                            0, mOverflingDistance, false)) {
-                        startSpringback();
-                    } else {
-                        invalidate();
-                        post(this);
-                    }
-                } else {
-                    endFling();
-                }
-                break;
-            }
             }
 
         }
@@ -2929,6 +2946,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;
@@ -2936,6 +2954,8 @@
         private int mLastSeenPos;
         private int mScrollDuration;
         private final int mExtraScroll;
+
+        private int mOffsetFromTop;
         
         PositionScroller() {
             mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
@@ -3027,12 +3047,48 @@
             
             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 = (float) viewTravelCount / childCount;
+            mScrollDuration = screenTravelCount < 1 ? (int) (screenTravelCount * SCROLL_DURATION) :
+                    (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;
             
@@ -3157,6 +3213,35 @@
                 break;
             }
 
+            case MOVE_OFFSET: {
+                if (mLastSeenPos == firstPos) {
+                    // No new views, let things keep going.
+                    post(this);
+                    return;
+                }
+
+                mLastSeenPos = firstPos;
+
+                final int childCount = getChildCount();
+                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();
+                    final int distance = targetTop - mOffsetFromTop;
+                    smoothScrollBy(distance,
+                            (int) (mScrollDuration * ((float) distance / getHeight())));
+                }
+                break;
+            }
+
             default:
                 break;
             }
@@ -3176,6 +3261,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
@@ -3205,6 +3308,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);
@@ -3272,29 +3411,16 @@
 
         final int firstPosition = mFirstPosition;
 
-        // Update our guesses for where the first and last views are
-        if (firstPosition == 0) {
-            mFirstPositionDistanceGuess = firstTop - mListPadding.top;
-        } else {
-            mFirstPositionDistanceGuess += incrementalDeltaY;
-        }
-        if (firstPosition + childCount == mItemCount) {
-            mLastPositionDistanceGuess = lastBottom + mListPadding.bottom;
-        } else {
-            mLastPositionDistanceGuess += incrementalDeltaY;
-        }
-
-        if (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0) {
+        if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {
             // Don't need to move views down if the top of the first position
             // is already visible
-            return incrementalDeltaY != 0;
+            return true;
         }
 
-        if (firstPosition + childCount == mItemCount && lastBottom <= end &&
-                incrementalDeltaY <= 0) {
+        if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {
             // Don't need to move views up if the bottom of the last position
             // is already visible
-            return incrementalDeltaY != 0;
+            return true;
         }
 
         final boolean down = incrementalDeltaY < 0;
@@ -3576,6 +3702,7 @@
 
         mResurrectToPosition = INVALID_POSITION;
         removeCallbacks(mFlingRunnable);
+        removeCallbacks(mPositionScroller);
         mTouchMode = TOUCH_MODE_REST;
         clearScrollingCache();
         mSpecificTop = selectedTop;
@@ -4118,7 +4245,11 @@
 
     /**
      * When set to a non-zero value, the cache color hint indicates that this list is always drawn
-     * on top of a solid, single-color, opaque background
+     * on top of a solid, single-color, opaque background.
+     *
+     * Zero means that what's behind this object is translucent (non solid) or is not made of a
+     * single color. This hint will not affect any existing background drawable set on this view (
+     * typically set via {@link #setBackgroundDrawable(Drawable)}).
      *
      * @param color The background color
      */
@@ -4228,10 +4359,43 @@
         return result;
     }
 
-    private void finishGlows() {
-        if (mEdgeGlowTop != null) {
-            mEdgeGlowTop.finish();
-            mEdgeGlowBottom.finish();
+    /**
+     * 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) {
+        // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
+        // service handling the specified intent.
+        if (mRemoteAdapter != null) {
+            Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
+            Intent.FilterComparison fcOld = new Intent.FilterComparison(
+                    mRemoteAdapter.getRemoteViewsServiceIntent());
+            if (fcNew.equals(fcOld)) {
+                return;
+            }
+        }
+
+        // Otherwise, create a new RemoteViewsAdapter for binding
+        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);
         }
     }
 
@@ -4251,6 +4415,75 @@
     }
 
     /**
+     * A MultiChoiceModeListener receives events for {@link AbsListView#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);
+    }
+
+    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();
+            }
+        }
+    }
+
+    /**
      * AbsListView extends LayoutParams to provide a place to hold the view type.
      */
     public static class LayoutParams extends ViewGroup.LayoutParams {
@@ -4618,7 +4851,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/Adapter.java b/core/java/android/widget/Adapter.java
index f2b3e2a..9b6c5a4 100644
--- a/core/java/android/widget/Adapter.java
+++ b/core/java/android/widget/Adapter.java
@@ -72,7 +72,7 @@
     long getItemId(int position);
     
     /**
-     * Indicated whether the item ids are stable across changes to the
+     * Indicates whether the item ids are stable across changes to the
      * underlying data.
      * 
      * @return True if the same id always refers to the same object.
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 8fcc2e8..f5afb94 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -172,7 +172,7 @@
     int mItemCount;
 
     /**
-     * The number of items in the adapter before a data changed event occured.
+     * The number of items in the adapter before a data changed event occurred.
      */
     int mOldItemCount;
 
@@ -228,7 +228,6 @@
         super(context, attrs, defStyle);
     }
 
-
     /**
      * Interface definition for a callback to be invoked when an item in this
      * AdapterView has been clicked.
@@ -558,7 +557,7 @@
     /**
      * @return The number of items owned by the Adapter associated with this
      *         AdapterView. (This is the number of data items, which may be
-     *         larger than the number of visible view.)
+     *         larger than the number of visible views.)
      */
     @ViewDebug.CapturedViewProperty
     public int getCount() {
@@ -629,6 +628,7 @@
     /**
      * Sets the view to show if the adapter is empty
      */
+    @android.view.RemotableViewMethod
     public void setEmptyView(View emptyView) {
         mEmptyView = emptyView;
 
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
new file mode 100644
index 0000000..b7b1a23
--- /dev/null
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -0,0 +1,853 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.ArrayList;
+import java.util.HashMap;
+
+import android.animation.AnimatorInflater;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+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
+ * @attr ref android.R.styleable#AdapterViewAnimator_loopViews
+ */
+public abstract class AdapterViewAnimator extends AdapterView<Adapter>
+        implements RemoteViewsAdapter.RemoteAdapterConnectionCallback {
+    private static final String TAG = "RemoteViewAnimator";
+
+    /**
+     * The index of the current child, which appears anywhere from the beginning
+     * to the end of the current set of children, as specified by {@link #mActiveOffset}
+     */
+    int mWhichChild = 0;
+
+    /**
+     * Whether or not the first view(s) should be animated in
+     */
+    boolean mAnimateFirstTime = true;
+
+    /**
+     *  Represents where the in the current window of
+     *  views the current <code>mDisplayedChild</code> sits
+     */
+    int mActiveOffset = 0;
+
+    /**
+     * The number of views that the {@link AdapterViewAnimator} keeps as children at any
+     * given time (not counting views that are pending removal, see {@link #mPreviousViews}).
+     */
+    int mNumActiveViews = 1;
+
+    /**
+     * Map of the children of the {@link AdapterViewAnimator}.
+     */
+    private HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>();
+
+    /**
+     * List of views pending removal from the {@link AdapterViewAnimator}
+     */
+    ArrayList<Integer> mPreviousViews;
+
+    /**
+     * The index, relative to the adapter, of the beginning of the window of views
+     */
+    int mCurrentWindowStart = 0;
+
+    /**
+     * The index, relative to the adapter, of the end of the window of views
+     */
+    int mCurrentWindowEnd = -1;
+
+    /**
+     * The same as {@link #mCurrentWindowStart}, except when the we have bounded
+     * {@link #mCurrentWindowStart} to be non-negative
+     */
+    int mCurrentWindowStartUnbounded = 0;
+
+    /**
+     * Handler to post events to the main thread
+     */
+    Handler mMainQueue;
+
+    /**
+     * Listens for data changes from the adapter
+     */
+    AdapterDataSetObserver mDataSetObserver;
+
+    /**
+     * The {@link Adapter} for this {@link AdapterViewAnimator}
+     */
+    Adapter mAdapter;
+
+    /**
+     * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator}
+     */
+    RemoteViewsAdapter mRemoteViewsAdapter;
+
+    /**
+     * Specifies whether this is the first time the animator is showing views
+     */
+    boolean mFirstTime = true;
+
+    /**
+     * Specifies if the animator should wrap from 0 to the end and vice versa
+     * or have hard boundaries at the beginning and end
+     */
+    boolean mLoopViews = true;
+
+    /**
+     * The width and height of some child, used as a size reference in-case our
+     * dimensions are unspecified by the parent.
+     */
+    int mReferenceChildWidth = -1;
+    int mReferenceChildHeight = -1;
+
+    /**
+     * In and out animations.
+     */
+    ObjectAnimator<?> mInAnimation;
+    ObjectAnimator<?> mOutAnimation;
+
+    private  ArrayList<View> mViewsToBringToFront;
+
+    private static final int DEFAULT_ANIMATION_DURATION = 200;
+
+    public AdapterViewAnimator(Context context) {
+        super(context);
+        initViewAnimator();
+    }
+
+    public AdapterViewAnimator(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.AdapterViewAnimator);
+        int resource = a.getResourceId(
+                com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
+        if (resource > 0) {
+            setInAnimation(context, resource);
+        } else {
+            setInAnimation(getDefaultInAnimation());
+        }
+
+        resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
+        if (resource > 0) {
+            setOutAnimation(context, resource);
+        } else {
+            setOutAnimation(getDefaultOutAnimation());
+        }
+
+        boolean flag = a.getBoolean(
+                com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true);
+        setAnimateFirstView(flag);
+
+        mLoopViews = a.getBoolean(
+                com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false);
+
+        a.recycle();
+
+        initViewAnimator();
+    }
+
+    /**
+     * Initialize this {@link AdapterViewAnimator}
+     */
+    private void initViewAnimator() {
+        mMainQueue = new Handler(Looper.myLooper());
+        mPreviousViews = new ArrayList<Integer>();
+        mViewsToBringToFront = new ArrayList<View>();
+    }
+
+    private class ViewAndIndex {
+        ViewAndIndex(View v, int i) {
+            view = v;
+            index = i;
+        }
+        View view;
+        int index;
+    }
+
+    /**
+     * This method is used by subclasses to configure the animator to display the
+     * desired number of views, and specify the offset
+     *
+     * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
+     * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild})
+     *        sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
+     *        and {@link #setDisplayedChild(int)} is called with 10, then the effective window will
+     *        be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
+     *        window would instead contain indexes 10, 11 and 12.
+     * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we
+     *        we loop back to the end, or do we do nothing
+     */
+     void configureViewAnimator(int numVisibleViews, int activeOffset) {
+        if (activeOffset > numVisibleViews - 1) {
+            // Throw an exception here.
+        }
+        mNumActiveViews = numVisibleViews;
+        mActiveOffset = activeOffset;
+        mPreviousViews.clear();
+        mViewsMap.clear();
+        removeAllViewsInLayout();
+        mCurrentWindowStart = 0;
+        mCurrentWindowEnd = -1;
+    }
+
+    /**
+     * This class should be overridden by subclasses to customize view transitions within
+     * the set of visible views
+     *
+     * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
+     *        in the window
+     * @param toIndex The relative index within the window that the view is going to, -1 if it is
+     *        being removed
+     * @param view The view that is being animated
+     */
+    void animateViewForTransition(int fromIndex, int toIndex, View view) {
+        if (fromIndex == -1) {
+            mInAnimation.setTarget(view);
+            mInAnimation.start();
+        } else if (toIndex == -1) {
+            mOutAnimation.setTarget(view);
+            mOutAnimation.start();
+        }
+    }
+
+    ObjectAnimator<?> getDefaultInAnimation() {
+        return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 0.0f, 1.0f);
+    }
+
+    ObjectAnimator<?> getDefaultOutAnimation() {
+        return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 1.0f, 0.0f);
+    }
+
+    /**
+     * 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 = mLoopViews ? 0 : mAdapter.getCount() - 1;
+            } else if (whichChild < 0) {
+                mWhichChild = mLoopViews ? mAdapter.getCount() - 1 : 0;
+            }
+
+            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);
+            }
+        }
+    }
+
+    /**
+     * To be overridden by subclasses. This method applies a view / index specific
+     * transform to the child view.
+     *
+     * @param child
+     * @param relativeIndex
+     */
+    void applyTransformForChildAtIndex(View child, int relativeIndex) {
+    }
+
+    /**
+     * 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 int modulo(int pos, int size) {
+        if (size > 0) {
+            return (size + (pos % size)) % size;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Get the view at this index relative to the current window's start
+     *
+     * @param relativeIndex Position relative to the current window's start
+     * @return View at this index, null if the index is outside the bounds
+     */
+    View getViewAtRelativeIndex(int relativeIndex) {
+        if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1 && mAdapter != null) {
+            int adapterCount =  mAdapter.getCount();
+            int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, adapterCount);
+            return mViewsMap.get(i).view;
+        }
+        return null;
+    }
+
+    LayoutParams createOrReuseLayoutParams(View v) {
+        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
+        if (currentLp instanceof ViewGroup.LayoutParams) {
+            LayoutParams lp = (LayoutParams) currentLp;
+            return lp;
+        }
+        return new ViewGroup.LayoutParams(0, 0);
+    }
+
+    void refreshChildren() {
+        for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
+            int index = modulo(i, mNumActiveViews);
+
+            // get the fresh child from the adapter
+            View updatedChild = mAdapter.getView(i, null, this);
+
+            if (mViewsMap.containsKey(index)) {
+                FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
+                // flush out the old child
+                fl.removeAllViewsInLayout();
+                // add the new child to the frame, if it exists
+                if (updatedChild != null) {
+                    fl.addView(updatedChild);
+                }
+            }
+        }
+    }
+
+    /**
+     * This method can be overridden so that subclasses can provide a custom frame in which their
+     * children can live. For example, StackView adds padding to its childrens' frames so as to
+     * accomodate for the highlight effect.
+     *
+     * @return The FrameLayout into which children can be placed.
+     */
+    FrameLayout getFrameForChild() {
+        return new FrameLayout(mContext);
+    }
+
+    void showOnly(int childIndex, boolean animate, boolean onLayout) {
+        if (mAdapter == null) return;
+        final int adapterCount = mAdapter.getCount();
+        if (adapterCount == 0) return;
+
+        for (int i = 0; i < mPreviousViews.size(); i++) {
+            View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
+            mViewsMap.remove(mPreviousViews.get(i));
+            viewToRemove.clearAnimation();
+            if (viewToRemove instanceof ViewGroup) {
+                ViewGroup vg = (ViewGroup) viewToRemove;
+                vg.removeAllViewsInLayout();
+            }
+            // applyTransformForChildAtIndex here just allows for any cleanup
+            // associated with this view that may need to be done by a subclass
+            applyTransformForChildAtIndex(viewToRemove, -1);
+
+            removeViewInLayout(viewToRemove);
+        }
+        mPreviousViews.clear();
+        int newWindowStartUnbounded = childIndex - mActiveOffset;
+        int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
+        int newWindowStart = Math.max(0, newWindowStartUnbounded);
+        int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
+
+        if (mLoopViews) {
+            newWindowStart = newWindowStartUnbounded;
+            newWindowEnd = newWindowEndUnbounded;
+        }
+        int rangeStart = modulo(newWindowStart, adapterCount);
+        int rangeEnd = modulo(newWindowEnd, adapterCount);
+
+        boolean wrap = false;
+        if (rangeStart > rangeEnd) {
+            wrap = true;
+        }
+
+        // This section clears out any items that are in our active views list
+        // but are outside the effective bounds of our window (this is becomes an issue
+        // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
+        // newWindowEndUnbounded > mAdapter.getCount() - 1
+        for (Integer index : mViewsMap.keySet()) {
+            boolean remove = false;
+            if (!wrap && (index < rangeStart || index > rangeEnd)) {
+                remove = true;
+            } else if (wrap && (index > rangeEnd && index < rangeStart)) {
+                remove = true;
+            }
+
+            if (remove) {
+                View previousView = mViewsMap.get(index).view;
+                int oldRelativeIndex = mViewsMap.get(index).index;
+
+                mPreviousViews.add(index);
+                animateViewForTransition(oldRelativeIndex, -1, previousView);
+            }
+        }
+
+        // If the window has changed
+        if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
+            // Run through the indices in the new range
+            for (int i = newWindowStart; i <= newWindowEnd; i++) {
+
+                int index = modulo(i, adapterCount);
+                int oldRelativeIndex;
+                if (mViewsMap.containsKey(index)) {
+                    oldRelativeIndex = mViewsMap.get(index).index;
+                } else {
+                    oldRelativeIndex = -1;
+                }
+                int newRelativeIndex = i - newWindowStartUnbounded;
+
+                // If this item is in the current window, great, we just need to apply
+                // the transform for it's new relative position in the window, and animate
+                // between it's current and new relative positions
+                boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
+
+                if (inOldRange) {
+                    View view = mViewsMap.get(index).view;
+                    mViewsMap.get(index).index = newRelativeIndex;
+                    applyTransformForChildAtIndex(view, newRelativeIndex);
+                    animateViewForTransition(oldRelativeIndex, newRelativeIndex, view);
+
+                // Otherwise this view is new to the window
+                } else {
+                    // Get the new view from the adapter, add it and apply any transform / animation
+                    View newView = mAdapter.getView(modulo(i, adapterCount), null, this);
+
+                    // We wrap the new view in a FrameLayout so as to respect the contract
+                    // with the adapter, that is, that we don't modify this view directly
+                    FrameLayout fl = getFrameForChild();
+
+                    // If the view from the adapter is null, we still keep an empty frame in place
+                    if (newView != null) {
+                       fl.addView(newView);
+                    }
+                    mViewsMap.put(index, new ViewAndIndex(fl, newRelativeIndex));
+                    addChild(fl);
+                    applyTransformForChildAtIndex(fl, newRelativeIndex);
+                    animateViewForTransition(-1, newRelativeIndex, fl);
+                }
+                mViewsMap.get(index).view.bringToFront();
+            }
+
+            for (int i = 0; i < mViewsToBringToFront.size(); i++) {
+                View v = mViewsToBringToFront.get(i);
+                v.bringToFront();
+            }
+            mViewsToBringToFront.clear();
+
+            mCurrentWindowStart = newWindowStart;
+            mCurrentWindowEnd = newWindowEnd;
+            mCurrentWindowStartUnbounded = newWindowStartUnbounded;
+        }
+
+        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() {
+                    requestLayout();
+                    invalidate();
+                }
+            });
+        }
+    }
+
+    private void addChild(View child) {
+        addViewInLayout(child, -1, createOrReuseLayoutParams(child));
+
+        // This code is used to obtain a reference width and height of a child in case we need
+        // to decide our own size. TODO: Do we want to update the size of the child that we're
+        // using for reference size? If so, when?
+        if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
+            int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            child.measure(measureSpec, measureSpec);
+            mReferenceChildWidth = child.getMeasuredWidth();
+            mReferenceChildHeight = child.getMeasuredHeight();
+        }
+    }
+
+    private void measureChildren() {
+        final int count = getChildCount();
+        final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+        final int childHeight = mMeasuredHeight - mPaddingTop - mPaddingBottom;
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
+
+        // We need to deal with the case where our parent hasn't told us how
+        // big we should be. In this case we try to use the desired size of the first
+        // child added.
+        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
+            heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
+                    mPaddingBottom : 0;
+        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+            heightSpecSize = haveChildRefSize ? Math.min(mReferenceChildHeight + mPaddingTop +
+                    mPaddingBottom, heightSpecSize) : 0;
+        }
+
+        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
+            widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
+                    mPaddingRight : 0;
+        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+            widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
+                    mPaddingRight, widthSpecSize) : 0;
+        }
+
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+        measureChildren();
+    }
+
+    @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);
+            }
+            refreshChildren();
+        }
+
+        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;
+    }
+
+    static class SavedState extends BaseSavedState {
+        int whichChild;
+
+        /**
+         * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState, int whichChild) {
+            super(superState);
+            this.whichChild = whichChild;
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            this.whichChild = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(this.whichChild);
+        }
+
+        @Override
+        public String toString() {
+            return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }";
+        }
+
+        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];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        return new SavedState(superState, mWhichChild);
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        // Here we set mWhichChild in addition to setDisplayedChild
+        // We do the former in case mAdapter is null, and hence setDisplayedChild won't
+        // set mWhichChild
+        mWhichChild = ss.whichChild;
+        setDisplayedChild(mWhichChild);
+    }
+
+    /**
+     * 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 getViewAtRelativeIndex(mActiveOffset);
+    }
+
+    /**
+     * 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 ObjectAnimator<?> 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(ObjectAnimator<?> 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 ObjectAnimator<?> 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(ObjectAnimator<?> 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((ObjectAnimator<?>) AnimatorInflater.loadAnimator(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((ObjectAnimator<?>) AnimatorInflater.loadAnimator(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) {
+        if (mAdapter != null && mDataSetObserver != null) {
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
+        }
+
+        mAdapter = adapter;
+        checkFocus();
+
+        if (mAdapter != null) {
+            mDataSetObserver = new AdapterDataSetObserver();
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+        }
+        setFocusable(true);
+    }
+
+    /**
+     * 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) {
+        // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
+        // service handling the specified intent.
+        if (mRemoteViewsAdapter != null) {
+            Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
+            Intent.FilterComparison fcOld = new Intent.FilterComparison(
+                    mRemoteViewsAdapter.getRemoteViewsServiceIntent());
+            if (fcNew.equals(fcOld)) {
+                return;
+            }
+        }
+
+        // Otherwise, create a new RemoteViewsAdapter for binding
+        mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
+    }
+
+    @Override
+    public void setSelection(int position) {
+        setDisplayedChild(position);
+    }
+
+    @Override
+    public View getSelectedView() {
+        return getViewAtRelativeIndex(mActiveOffset);
+    }
+
+    /**
+     * 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..b09ade7
--- /dev/null
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.animation.ObjectAnimator;
+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.RemotableViewMethod;
+import android.view.animation.AlphaAnimation;
+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 int mFlipInterval = DEFAULT_INTERVAL;
+    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);
+    }
+
+    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);
+
+        // By default we want the flipper to loop
+        mLoopViews = a.getBoolean(
+                com.android.internal.R.styleable.AdapterViewAnimator_loopViews, true);
+
+        a.recycle();
+    }
+
+    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();
+    }
+
+    /**
+    * {@inheritDoc}
+    */
+   @Override
+   @RemotableViewMethod
+   public void showNext() {
+       // if the flipper is currently flipping automatically, and showNext() is called
+       // we should we should make sure to reset the timer
+       if (mRunning) {
+           mHandler.removeMessages(FLIP_MSG);
+           Message msg = mHandler.obtainMessage(FLIP_MSG);
+           mHandler.sendMessageDelayed(msg, mFlipInterval);
+       }
+       super.showNext();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   @RemotableViewMethod
+   public void showPrevious() {
+       // if the flipper is currently flipping automatically, and showPrevious() is called
+       // we should we should make sure to reset the timer
+       if (mRunning) {
+           mHandler.removeMessages(FLIP_MSG);
+           Message msg = mHandler.obtainMessage(FLIP_MSG);
+           mHandler.sendMessageDelayed(msg, mFlipInterval);
+       }
+       super.showPrevious();
+   }
+
+    /**
+     * 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();
+                }
+            }
+        }
+    };
+}
diff --git a/core/java/android/widget/Adapters.java b/core/java/android/widget/Adapters.java
new file mode 100644
index 0000000..80d8ec1
--- /dev/null
+++ b/core/java/android/widget/Adapters.java
@@ -0,0 +1,1233 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 <" +
+                        assertName + " />");
+            }
+
+            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 3971487..e07befa 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;
 
@@ -93,45 +92,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;
@@ -140,10 +115,6 @@
 
     private boolean mBlockCompletion;
 
-    private ListSelectorHider mHideSelector;
-    private Runnable mShowDropDownRunnable;
-    private Runnable mResizePopupRunnable = new ResizePopupRunnable();
-
     private PassThroughClickListener mPassThroughClickListener;
     private PopupDataSetObserver mObserver;
 
@@ -158,9 +129,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(
@@ -169,14 +141,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.
@@ -187,13 +156,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.
@@ -241,6 +215,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;
+        }
     }
     
     /**
@@ -253,7 +241,7 @@
      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
      */
     public int getDropDownWidth() {
-        return mDropDownWidth;
+        return mPopup.getWidth();
     }
     
     /**
@@ -266,7 +254,7 @@
      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
      */
     public void setDropDownWidth(int width) {
-        mDropDownWidth = width;
+        mPopup.setWidth(width);
     }
 
     /**
@@ -280,7 +268,7 @@
      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
      */
     public int getDropDownHeight() {
-        return mDropDownHeight;
+        return mPopup.getHeight();
     }
 
     /**
@@ -294,7 +282,7 @@
      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
      */
     public void setDropDownHeight(int height) {
-        mDropDownHeight = height;
+        mPopup.setHeight(height);
     }
     
     /**
@@ -319,7 +307,7 @@
      */
     public void setDropDownAnchor(int id) {
         mDropDownAnchorId = id;
-        mDropDownAnchorView = null;
+        mPopup.setAnchorView(null);
     }
     
     /**
@@ -361,7 +349,7 @@
      * @param offset the vertical offset
      */
     public void setDropDownVerticalOffset(int offset) {
-        mDropDownVerticalOffset = offset;
+        mPopup.setVerticalOffset(offset);
     }
     
     /**
@@ -370,7 +358,7 @@
      * @return the vertical offset
      */
     public int getDropDownVerticalOffset() {
-        return mDropDownVerticalOffset;
+        return mPopup.getVerticalOffset();
     }
     
     /**
@@ -379,7 +367,7 @@
      * @param offset the horizontal offset
      */
     public void setDropDownHorizontalOffset(int offset) {
-        mDropDownHorizontalOffset = offset;
+        mPopup.setHorizontalOffset(offset);
     }
     
     /**
@@ -388,7 +376,7 @@
      * @return the horizontal offset
      */
     public int getDropDownHorizontalOffset() {
-        return mDropDownHorizontalOffset;
+        return mPopup.getHorizontalOffset();
     }
 
      /**
@@ -425,7 +413,7 @@
      * @hide Pending API council approval
      */
     public boolean isDropDownAlwaysVisible() {
-        return mDropDownAlwaysVisible;
+        return mPopup.isDropDownAlwaysVisible();
     }
 
     /**
@@ -442,7 +430,7 @@
      * @hide Pending API council approval
      */
     public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
-        mDropDownAlwaysVisible = dropDownAlwaysVisible;
+        mPopup.setDropDownAlwaysVisible(dropDownAlwaysVisible);
     }
    
     /**
@@ -609,15 +597,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) {
@@ -636,18 +622,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);
@@ -655,87 +639,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();
@@ -746,7 +654,7 @@
         boolean handled = super.onKeyDown(keyCode, event);
         mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
 
-        if (handled && isPopupShowing() && mDropDownList != null) {
+        if (handled && isPopupShowing()) {
             clearListSelection();
         }
 
@@ -807,11 +715,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);
             }
@@ -844,13 +753,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();
     }
     
     /**
@@ -859,11 +762,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);
     }
 
     /**
@@ -877,10 +776,7 @@
      * @see ListView#getSelectedItemPosition()
      */
     public int getListSelection() {
-        if (mPopup.isShowing() && (mDropDownList != null)) {
-            return mDropDownList.getSelectedItemPosition();
-        }
-        return ListView.INVALID_POSITION;
+        return mPopup.getSelectedItemPosition();
     }
 
     /**
@@ -914,13 +810,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());
         }
     }
 
@@ -928,7 +818,7 @@
         if (isPopupShowing()) {
             Object selectedItem;
             if (position < 0) {
-                selectedItem = mDropDownList.getSelectedItem();
+                selectedItem = mPopup.getSelectedItem();
             } else {
                 selectedItem = mAdapter.getItem(position);
             }
@@ -942,18 +832,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();
         }
     }
@@ -1003,7 +893,6 @@
     /** {@inheritDoc} */
     public void onFilterComplete(int count) {
         updateDropDownForFilter(count);
-
     }
 
     private void updateDropDownForFilter(int count) {
@@ -1017,11 +906,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();
         }
     }
@@ -1029,7 +919,7 @@
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
-        if (!hasWindowFocus && !mDropDownAlwaysVisible) {
+        if (!hasWindowFocus && !mPopup.isDropDownAlwaysVisible()) {
             dismissDropDown();
         }
     }
@@ -1039,7 +929,7 @@
         super.onDisplayHint(hint);
         switch (hint) {
             case INVISIBLE:
-                if (!mDropDownAlwaysVisible) {
+                if (!mPopup.isDropDownAlwaysVisible()) {
                     dismissDropDown();
                 }
                 break;
@@ -1053,7 +943,7 @@
         if (!focused) {
             performValidation();
         }
-        if (!focused && !mDropDownAlwaysVisible) {
+        if (!focused && !mPopup.isDropDownAlwaysVisible()) {
             dismissDropDown();
         }
     }
@@ -1078,8 +968,6 @@
             imm.displayCompletions(this, null);
         }
         mPopup.dismiss();
-        mPopup.setContentView(null);
-        mDropDownList = null;
     }
 
     @Override
@@ -1092,18 +980,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.
@@ -1111,7 +987,7 @@
      * @hide internal used only by SearchDialog
      */
     public void showDropDownAfterLayout() {
-        post(mShowDropDownRunnable);
+        mPopup.postShow();
     }
     
     /**
@@ -1122,7 +998,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();
     }
 
@@ -1130,89 +1006,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();
     }
     
     /**
@@ -1223,19 +1031,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();
@@ -1263,135 +1062,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;
-        }
     }
 
     /**
@@ -1443,47 +1113,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);
@@ -1491,123 +1120,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
@@ -1655,10 +1167,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
@@ -1673,14 +1182,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/EdgeGlow.java b/core/java/android/widget/EdgeGlow.java
deleted file mode 100644
index 1f7daab..0000000
--- a/core/java/android/widget/EdgeGlow.java
+++ /dev/null
@@ -1,314 +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;
-
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-
-/**
- * This class performs the glow effect used at the edges of scrollable widgets.
- * @hide
- */
-public class EdgeGlow {
-    private static final String TAG = "EdgeGlow";
-
-    // Time it will take the effect to fully recede in ms
-    private static final int RECEDE_TIME = 1000;
-
-    // Time it will take before a pulled glow begins receding
-    private static final int PULL_TIME = 250;
-
-    // Time it will take for a pulled glow to decay to partial strength before release
-    private static final int PULL_DECAY_TIME = 10000;
-
-    private static final float MAX_ALPHA = 1.f;
-    private static final float HELD_EDGE_ALPHA = 0.7f;
-    private static final float HELD_EDGE_SCALE_Y = 0.5f;
-    private static final float HELD_GLOW_ALPHA = 0.5f;
-    private static final float HELD_GLOW_SCALE_Y = 0.5f;
-
-    private static final float MAX_GLOW_HEIGHT = 3.f;
-
-    private static final float PULL_GLOW_BEGIN = 1.f;
-    private static final float PULL_EDGE_BEGIN = 0.6f;
-
-    // Minimum velocity that will be absorbed
-    private static final int MIN_VELOCITY = 100;
-
-    private static final float EPSILON = 0.001f;
-
-    private final Drawable mEdge;
-    private final Drawable mGlow;
-    private int mWidth;
-    private int mHeight;
-
-    private float mEdgeAlpha;
-    private float mEdgeScaleY;
-    private float mGlowAlpha;
-    private float mGlowScaleY;
-
-    private float mEdgeAlphaStart;
-    private float mEdgeAlphaFinish;
-    private float mEdgeScaleYStart;
-    private float mEdgeScaleYFinish;
-    private float mGlowAlphaStart;
-    private float mGlowAlphaFinish;
-    private float mGlowScaleYStart;
-    private float mGlowScaleYFinish;
-
-    private long mStartTime;
-    private float mDuration;
-
-    private final Interpolator mInterpolator;
-
-    private static final int STATE_IDLE = 0;
-    private static final int STATE_PULL = 1;
-    private static final int STATE_ABSORB = 2;
-    private static final int STATE_RECEDE = 3;
-    private static final int STATE_PULL_DECAY = 4;
-
-    // How much dragging should effect the height of the edge image.
-    // Number determined by user testing.
-    private static final int PULL_DISTANCE_EDGE_FACTOR = 5;
-
-    // How much dragging should effect the height of the glow image.
-    // Number determined by user testing.
-    private static final int PULL_DISTANCE_GLOW_FACTOR = 5;
-
-    private static final int VELOCITY_EDGE_FACTOR = 8;
-    private static final int VELOCITY_GLOW_FACTOR = 16;
-
-    private int mState = STATE_IDLE;
-
-    private float mPullDistance;
-
-    public EdgeGlow(Drawable edge, Drawable glow) {
-        mEdge = edge;
-        mGlow = glow;
-
-        mInterpolator = new DecelerateInterpolator();
-    }
-
-    public void setSize(int width, int height) {
-        mWidth = width;
-        mHeight = height;
-    }
-
-    public boolean isFinished() {
-        return mState == STATE_IDLE;
-    }
-
-    public void finish() {
-        mState = STATE_IDLE;
-    }
-
-    /**
-     * Call when the object is pulled by the user.
-     *
-     * @param deltaDistance Change in distance since the last call
-     */
-    public void onPull(float deltaDistance) {
-        final long now = AnimationUtils.currentAnimationTimeMillis();
-        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
-            return;
-        }
-        if (mState != STATE_PULL) {
-            mGlowScaleY = PULL_GLOW_BEGIN;
-        }
-        mState = STATE_PULL;
-
-        mStartTime = now;
-        mDuration = PULL_TIME;
-
-        mPullDistance += deltaDistance;
-        float distance = Math.abs(mPullDistance);
-
-        mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA));
-        mEdgeScaleY = mEdgeScaleYStart = Math.max(
-                HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f));
-
-        mGlowAlpha = mGlowAlphaStart = Math.max(
-                0.5f, Math.min(mGlowAlpha + Math.abs(deltaDistance), MAX_ALPHA));
-
-        float glowChange = Math.abs(deltaDistance);
-        if (deltaDistance > 0 && mPullDistance < 0) {
-            glowChange = -glowChange;
-        }
-        if (mPullDistance == 0) {
-            mGlowScaleY = 0;
-        }
-        mGlowScaleY = mGlowScaleYStart = Math.max(
-                0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR);
-
-        mEdgeAlphaFinish = mEdgeAlpha;
-        mEdgeScaleYFinish = mEdgeScaleY;
-        mGlowAlphaFinish = mGlowAlpha;
-        mGlowScaleYFinish = mGlowScaleY;
-    }
-
-    /**
-     * Call when the object is released after being pulled.
-     */
-    public void onRelease() {
-        mPullDistance = 0;
-
-        if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
-            return;
-        }
-
-        mState = STATE_RECEDE;
-        mEdgeAlphaStart = mEdgeAlpha;
-        mEdgeScaleYStart = mEdgeScaleY;
-        mGlowAlphaStart = mGlowAlpha;
-        mGlowScaleYStart = mGlowScaleY;
-
-        mEdgeAlphaFinish = 0.f;
-        mEdgeScaleYFinish = 0.f;
-        mGlowAlphaFinish = 0.f;
-        mGlowScaleYFinish = 0.f;
-
-        mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        mDuration = RECEDE_TIME;
-    }
-
-    /**
-     * Call when the effect absorbs an impact at the given velocity.
-     *
-     * @param velocity Velocity at impact in pixels per second.
-     */
-    public void onAbsorb(int velocity) {
-        mState = STATE_ABSORB;
-        velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
-
-        mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        mDuration = 0.1f + (velocity * 0.03f);
-
-        // The edge should always be at least partially visible, regardless
-        // of velocity.
-        mEdgeAlphaStart = 0.5f;
-        mEdgeScaleYStart = 0.2f;
-        // The glow depends more on the velocity, and therefore starts out
-        // nearly invisible.
-        mGlowAlphaStart = 0.5f;
-        mGlowScaleYStart = 0.f;
-
-        // Factor the velocity by 8. Testing on device shows this works best to
-        // reflect the strength of the user's scrolling.
-        mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1));
-        // Edge should never get larger than the size of its asset.
-        mEdgeScaleYFinish = 1.f;
-
-        // Growth for the size of the glow should be quadratic to properly
-        // respond
-        // to a user's scrolling speed. The faster the scrolling speed, the more
-        // intense the effect should be for both the size and the saturation.
-        mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f);
-        // Alpha should change for the glow as well as size.
-        mGlowAlphaFinish = Math.max(
-                mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
-    }
-
-
-    /**
-     * Draw into the provided canvas. Assumes that the canvas has been rotated
-     * accordingly and the size has been set. The effect will be drawn the full
-     * width of X=0 to X=width, emitting from Y=0 and extending to some factor <
-     * 1.f of height.
-     *
-     * @param canvas Canvas to draw into
-     * @return true if drawing should continue beyond this frame to continue the
-     *         animation
-     */
-    public boolean draw(Canvas canvas) {
-        update();
-
-        final int edgeHeight = mEdge.getIntrinsicHeight();
-        final int glowHeight = mGlow.getIntrinsicHeight();
-
-        final float distScale = (float) mHeight / mWidth;
-
-        mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
-        // Width of the image should be 3 * the width of the screen.
-        // Should start off screen to the left.
-        mGlow.setBounds(-mWidth, 0, mWidth * 2, (int) Math.min(
-                glowHeight * mGlowScaleY * distScale * 0.6f, mHeight * MAX_GLOW_HEIGHT));
-        mGlow.draw(canvas);
-
-        mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
-        mEdge.setBounds(0, 0, mWidth, (int) (edgeHeight * mEdgeScaleY));
-        mEdge.draw(canvas);
-
-        return mState != STATE_IDLE;
-    }
-
-    private void update() {
-        final long time = AnimationUtils.currentAnimationTimeMillis();
-        final float t = Math.min((time - mStartTime) / mDuration, 1.f);
-
-        final float interp = mInterpolator.getInterpolation(t);
-
-        mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp;
-        mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp;
-        mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
-        mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
-
-        if (t >= 1.f - EPSILON) {
-            switch (mState) {
-                case STATE_ABSORB:
-                    mState = STATE_RECEDE;
-                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
-                    mDuration = RECEDE_TIME;
-
-                    mEdgeAlphaStart = mEdgeAlpha;
-                    mEdgeScaleYStart = mEdgeScaleY;
-                    mGlowAlphaStart = mGlowAlpha;
-                    mGlowScaleYStart = mGlowScaleY;
-
-                    mEdgeAlphaFinish = 0.f;
-                    mEdgeScaleYFinish = mEdgeScaleY;
-                    mGlowAlphaFinish = 0.f;
-                    mGlowScaleYFinish = mGlowScaleY;
-                    break;
-                case STATE_PULL:
-                    mState = STATE_PULL_DECAY;
-                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
-                    mDuration = PULL_DECAY_TIME;
-
-                    mEdgeAlphaStart = mEdgeAlpha;
-                    mEdgeScaleYStart = mEdgeScaleY;
-                    mGlowAlphaStart = mGlowAlpha;
-                    mGlowScaleYStart = mGlowScaleY;
-
-                    // After a pull, the glow should fade to nothing.
-                    mEdgeAlphaFinish = 0.f;
-                    mEdgeScaleYFinish = 0.f;
-                    mGlowAlphaFinish = 0.f;
-                    mGlowScaleYFinish = 0.f;
-                    break;
-                case STATE_PULL_DECAY:
-                    // Do nothing; wait for release
-                    break;
-                case STATE_RECEDE:
-                    mState = STATE_IDLE;
-                    break;
-            }
-        }
-    }
-}
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 8bd797b..3d21048 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -185,7 +185,6 @@
     
     /** Drawable to be used as a divider when it is adjacent to any children */
     private Drawable mChildDivider;
-    private boolean mClipChildDivider;
 
     // Bounds of the indicator to be drawn
     private final Rect mIndicatorRect = new Rect();
@@ -379,7 +378,6 @@
      */
     public void setChildDivider(Drawable childDivider) {
         mChildDivider = childDivider;
-        mClipChildDivider = childDivider != null && childDivider instanceof ColorDrawable;
     }
 
     @Override
@@ -396,17 +394,8 @@
                     pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
                 // These are the cases where we draw the child divider
                 final Drawable divider = mChildDivider;
-                final boolean clip = mClipChildDivider;
-                if (!clip) {
-                    divider.setBounds(bounds);
-                } else {
-                    canvas.save();
-                    canvas.clipRect(bounds);
-                }
+                divider.setBounds(bounds);
                 divider.draw(canvas);
-                if (clip) {
-                    canvas.restore();
-                }
                 pos.recycle();
                 return;
             }
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index e445180..559a5fe 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -32,8 +32,8 @@
 
 /**
  * FrameLayout is designed to block out an area on the screen to display
- * a single item. You can add multiple children to a FrameLayout, but all
- * children are pegged to the top left of the screen.
+ * a single item. You can add multiple children to a FrameLayout and control their
+ * position within the FrameLayout using {@link android.widget.FrameLayout.LayoutParams#gravity}.
  * Children are drawn in a stack, with the most recently added child on top.
  * The size of the frame layout is the size of its largest child (plus padding), visible
  * or not (if the FrameLayout's parent permits). Views that are GONE are used for sizing
@@ -440,6 +440,8 @@
      * Per-child layout information for layouts that support margins.
      * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
      * for a list of all child view attributes that this class supports.
+     * 
+     * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
      */
     public static class LayoutParams extends MarginLayoutParams {
         /**
@@ -447,6 +449,8 @@
          * are associated.
          *
          * @see android.view.Gravity
+         * 
+         * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
          */
         public int gravity = -1;
 
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index ccd37d3..9789658 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -1210,7 +1210,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);
@@ -1266,6 +1266,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 a7300c2..46c7d33 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -17,15 +17,18 @@
 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;
 
 
 /**
@@ -35,6 +38,7 @@
  * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-gridview.html">Grid
  * View tutorial</a>.</p>
  */
+@RemoteView
 public class GridView extends AbsListView {
     public static final int NO_STRETCH = 0;
     public static final int STRETCH_SPACING = 1;
@@ -103,19 +107,49 @@
         a.recycle();
     }
 
+    /**
+     * Set how the user may select items from the grid.
+     *
+     * <p>GridView only supports {@link AbsListView#CHOICE_MODE_NONE} and
+     * {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}. Attempting to set an unsupported choice
+     * mode will throw an UnsupportedOperationException.
+     */
+    @Override
+    public void setChoiceMode(int choiceMode) {
+        switch (choiceMode) {
+        case CHOICE_MODE_NONE:
+        case CHOICE_MODE_MULTIPLE_MODAL:
+            super.setChoiceMode(choiceMode);
+            break;
+
+        default:
+            throw new UnsupportedOperationException("Unsupported choice mode " + choiceMode);
+        }
+    }
+
     @Override
     public ListAdapter getAdapter() {
         return mAdapter;
     }
 
     /**
+     * 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);
         }
 
@@ -125,7 +159,10 @@
 
         mOldSelectedPosition = INVALID_POSITION;
         mOldSelectedRowId = INVALID_ROW_ID;
-        
+
+        // AbsListView#setAdapter will update choice mode states.
+        super.setAdapter(adapter);
+
         if (mAdapter != null) {
             mOldItemCount = mItemCount;
             mItemCount = mAdapter.getCount();
@@ -742,6 +779,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.
@@ -1281,6 +1338,15 @@
             child.setPressed(isPressed);
         }
 
+        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
+            if (child instanceof Checkable) {
+                ((Checkable) child).setChecked(mCheckStates.get(position));
+            } else if (getContext().getApplicationInfo().targetSdkVersion
+                    >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+                child.setActivated(mCheckStates.get(position));
+            }
+        }
+
         if (needToMeasure) {
             int childHeightSpec = ViewGroup.getChildMeasureSpec(
                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
@@ -1491,9 +1557,9 @@
         int nextPage = -1;
 
         if (direction == FOCUS_UP) {
-            nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
+            nextPage = Math.max(0, mSelectedPosition - getChildCount());
         } else if (direction == FOCUS_DOWN) {
-            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
+            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
         }
 
         if (nextPage >= 0) {
@@ -1777,6 +1843,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
@@ -1876,12 +1955,7 @@
         // TODO: Account for vertical spacing too
         final int numColumns = mNumColumns;
         final int rowCount = (mItemCount + numColumns - 1) / numColumns;
-        int result = Math.max(rowCount * 100, 0);
-        if (mScrollY != 0) {
-            // Compensate for overscroll
-            result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
-        }
-        return result;
+        return Math.max(rowCount * 100, 0);
     }
 }
 
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index cd10a59..e30d4c8 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -16,24 +16,19 @@
 
 package android.widget;
 
-import com.android.internal.R;
-
-import android.util.AttributeSet;
-import android.graphics.Canvas;
+import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.View;
+import android.util.AttributeSet;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.VelocityTracker;
+import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.KeyEvent;
-import android.view.FocusFinder;
-import android.view.MotionEvent;
 import android.view.ViewParent;
 import android.view.animation.AnimationUtils;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
 
 import java.util.List;
 
@@ -58,6 +53,8 @@
  * within a larger container.
  *
  * <p>HorizontalScrollView only supports horizontal scrolling.
+ * 
+ * @attr ref android.R.styleable#HorizontalScrollView_fillViewport 
  */
 public class HorizontalScrollView extends FrameLayout {
     private static final int ANIMATED_SCROLL_GAP = ScrollView.ANIMATED_SCROLL_GAP;
@@ -68,9 +65,7 @@
     private long mLastScroll;
 
     private final Rect mTempRect = new Rect();
-    private OverScroller mScroller;
-    private EdgeGlow mEdgeGlowLeft;
-    private EdgeGlow mEdgeGlowRight;
+    private Scroller mScroller;
 
     /**
      * Flag to indicate that we are moving focus ourselves. This is so the
@@ -124,9 +119,6 @@
     private int mMinimumVelocity;
     private int mMaximumVelocity;
     
-    private int mOverscrollDistance;
-    private int mOverflingDistance;
-
     /**
      * ID of the active pointer. This is used to retain consistency during
      * drags/flings if multiple pointers are used.
@@ -199,7 +191,7 @@
 
 
     private void initScrollView() {
-        mScroller = new OverScroller(getContext());
+        mScroller = new Scroller(getContext());
         setFocusable(true);
         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
         setWillNotDraw(false);
@@ -207,8 +199,6 @@
         mTouchSlop = configuration.getScaledTouchSlop();
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
-        mOverscrollDistance = configuration.getScaledOverscrollDistance();
-        mOverflingDistance = configuration.getScaledOverflingDistance();
     }
 
     @Override
@@ -260,20 +250,25 @@
     }
 
     /**
-     * Indicates whether this ScrollView's content is stretched to fill the viewport.
+     * Indicates whether this HorizontalScrollView's content is stretched to
+     * fill the viewport.
      *
      * @return True if the content fills the viewport, false otherwise.
+     *
+     * @attr ref android.R.styleable#HorizontalScrollView_fillViewport
      */
     public boolean isFillViewport() {
         return mFillViewport;
     }
 
     /**
-     * Indicates this ScrollView whether it should stretch its content width to fill
-     * the viewport or not.
+     * Indicates this HorizontalScrollView whether it should stretch its content width
+     * to fill the viewport or not.
      *
      * @param fillViewport True to stretch the content's width to the viewport's
      *        boundaries, false otherwise.
+     * 
+     * @attr ref android.R.styleable#HorizontalScrollView_fillViewport
      */
     public void setFillViewport(boolean fillViewport) {
         if (fillViewport != mFillViewport) {
@@ -468,9 +463,6 @@
                 /* Release the drag */
                 mIsBeingDragged = false;
                 mActivePointerId = INVALID_POINTER;
-                if (mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
-                    invalidate();
-                }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
                 onSecondaryPointerUp(ev);
@@ -503,7 +495,9 @@
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 final float x = ev.getX();
-                mIsBeingDragged = true;
+                if (!(mIsBeingDragged = inChild((int) x, (int) ev.getY()))) {
+                    return false;
+                }
 
                 /*
                  * If being flinged and user touches, stop the fling. isFinished
@@ -526,26 +520,7 @@
                     final int deltaX = (int) (mLastMotionX - x);
                     mLastMotionX = x;
 
-                    final int oldX = mScrollX;
-                    final int oldY = mScrollY;
-                    final int range = getScrollRange();
-                    if (overscrollBy(deltaX, 0, mScrollX, 0, range, 0,
-                            mOverscrollDistance, 0, true)) {
-                        // Break our velocity if we hit a scroll barrier.
-                        mVelocityTracker.clear();
-                    }
-                    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
-
-                    final int overscrollMode = getOverscrollMode();
-                    if (overscrollMode == OVERSCROLL_ALWAYS ||
-                            (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) {
-                        final int pulledToX = oldX + deltaX;
-                        if (pulledToX < 0) {
-                            mEdgeGlowLeft.onPull((float) deltaX / getWidth());
-                        } else if (pulledToX > range) {
-                            mEdgeGlowRight.onPull((float) deltaX / getWidth());
-                        }
-                    }
+                    scrollBy(deltaX, 0);
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -554,15 +529,8 @@
                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                     int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
 
-                    if (getChildCount() > 0) {
-                        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
-                            fling(-initialVelocity);
-                        } else {
-                            final int right = getScrollRange();
-                            if (mScroller.springback(mScrollX, mScrollY, 0, right, 0, 0)) {
-                                invalidate();
-                            }
-                        }
+                    if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
+                        fling(-initialVelocity);
                     }
                     
                     mActivePointerId = INVALID_POINTER;
@@ -572,27 +540,16 @@
                         mVelocityTracker.recycle();
                         mVelocityTracker = null;
                     }
-                    if (mEdgeGlowLeft != null) {
-                        mEdgeGlowLeft.onRelease();
-                        mEdgeGlowRight.onRelease();
-                    }
                 }
                 break;
             case MotionEvent.ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
-                    if (mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
-                        invalidate();
-                    }
                     mActivePointerId = INVALID_POINTER;
                     mIsBeingDragged = false;
                     if (mVelocityTracker != null) {
                         mVelocityTracker.recycle();
                         mVelocityTracker = null;
                     }
-                    if (mEdgeGlowLeft != null) {
-                        mEdgeGlowLeft.onRelease();
-                        mEdgeGlowRight.onRelease();
-                    }
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -619,28 +576,12 @@
         }
     }
     
-    @Override
-    protected void onOverscrolled(int scrollX, int scrollY,
-            boolean clampedX, boolean clampedY) {
-        // Treat animating scrolls differently; see #computeScroll() for why.
-        if (!mScroller.isFinished()) {
-            mScrollX = scrollX;
-            mScrollY = scrollY;
-            if (clampedX) {
-                mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0);
-            }
-        } else {
-            super.scrollTo(scrollX, scrollY);
-        }
-        awakenScrollBars();
-    }
-
     private int getScrollRange() {
         int scrollRange = 0;
         if (getChildCount() > 0) {
             View child = getChildAt(0);
             scrollRange = Math.max(0,
-                    child.getWidth() - (getWidth() - mPaddingLeft - mPaddingRight));
+                    child.getWidth() - getWidth() - mPaddingLeft - mPaddingRight);
         }
         return scrollRange;
     }
@@ -1017,16 +958,7 @@
             return contentWidth;
         }
         
-        int scrollRange = getChildAt(0).getRight();
-        final int scrollX = mScrollX;
-        final int overscrollRight = Math.max(0, scrollRange - contentWidth);
-        if (scrollX < 0) {
-            scrollRange -= scrollX;
-        } else if (scrollX > overscrollRight) {
-            scrollRange += scrollX - overscrollRight;
-        }
-
-        return scrollRange;
+        return getChildAt(0).getRight();
     }
     
     @Override
@@ -1087,20 +1019,14 @@
             int x = mScroller.getCurrX();
             int y = mScroller.getCurrY();
 
-            if (oldX != x || oldY != y) {
-                overscrollBy(x - oldX, y - oldY, oldX, oldY, getScrollRange(), 0,
-                        mOverflingDistance, 0, false);
-                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
-
-                final int range = getScrollRange();
-                final int overscrollMode = getOverscrollMode();
-                if (overscrollMode == OVERSCROLL_ALWAYS ||
-                        (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) {
-                    if (x < 0 && oldX >= 0) {
-                        mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
-                    } else if (x > range && oldX <= range) {
-                        mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
-                    }
+            if (getChildCount() > 0) {
+                View child = getChildAt(0);
+                x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+                y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+                if (x != oldX || y != oldY) {
+                    mScrollX = x;
+                    mScrollY = y;
+                    onScrollChanged(x, y, oldX, oldY);
                 }
             }
             awakenScrollBars();
@@ -1337,7 +1263,7 @@
             int right = getChildAt(0).getWidth();
     
             mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, 
-                    Math.max(0, right - width), 0, 0, width/2, 0);
+                    Math.max(0, right - width), 0, 0);
     
             final boolean movingRight = velocityX > 0;
     
@@ -1375,56 +1301,6 @@
         }
     }
 
-    @Override
-    public void setOverscrollMode(int mode) {
-        if (mode != OVERSCROLL_NEVER) {
-            if (mEdgeGlowLeft == null) {
-                final Resources res = getContext().getResources();
-                final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
-                final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
-                mEdgeGlowLeft = new EdgeGlow(edge, glow);
-                mEdgeGlowRight = new EdgeGlow(edge, glow);
-            }
-        } else {
-            mEdgeGlowLeft = null;
-            mEdgeGlowRight = null;
-        }
-        super.setOverscrollMode(mode);
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-        if (mEdgeGlowLeft != null) {
-            final int scrollX = mScrollX;
-            if (!mEdgeGlowLeft.isFinished()) {
-                final int restoreCount = canvas.save();
-                final int height = getHeight();
-
-                canvas.rotate(270);
-                canvas.translate(-height * 1.5f, Math.min(0, scrollX));
-                mEdgeGlowLeft.setSize(getHeight() * 2, getWidth());
-                if (mEdgeGlowLeft.draw(canvas)) {
-                    invalidate();
-                }
-                canvas.restoreToCount(restoreCount);
-            }
-            if (!mEdgeGlowRight.isFinished()) {
-                final int restoreCount = canvas.save();
-                final int width = getWidth();
-                final int height = getHeight();
-
-                canvas.rotate(90);
-                canvas.translate(-height / 2, -(Math.max(getScrollRange(), scrollX) + width));
-                mEdgeGlowRight.setSize(height * 2, width);
-                if (mEdgeGlowRight.draw(canvas)) {
-                    invalidate();
-                }
-                canvas.restoreToCount(restoreCount);
-            }
-        }
-    }
-
     private int clamp(int n, int my, int child) {
         if (my >= child || n < 0) {
             return 0;
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index 12a68db..d680fad 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -32,7 +32,7 @@
  * {@link android.widget.Button}, with the standard button background
  * that changes color during different button states. The image on the surface
  * of the button is defined either by the {@code android:src} attribute in the
- * {@code &lt;ImageButton&gt;} XML element or by the 
+ * {@code <ImageButton>} XML element or by the
  * {@link #setImageResource(int)} method.</p>
  * 
  * <p>To remove the standard button background image, define your own 
@@ -57,7 +57,7 @@
  * based on the state of the button and the corresponding images
  * defined in the XML.</p>
  *
- * <p>The order of the {@code &lt;item>} elements is important because they are
+ * <p>The order of the {@code <item>} elements is important because they are
  * evaluated in order. This is why the "normal" button image comes last, because
  * it will only be applied after {@code android:state_pressed} and {@code
  * android:state_focused} have both evaluated false.</p>
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index faf082d..1e5489a 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -43,6 +43,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 {
@@ -117,7 +124,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);
@@ -142,8 +153,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();
     }
@@ -172,6 +182,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..8b21612
--- /dev/null
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -0,0 +1,1238 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.
+ * 
+ * <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, com.android.internal.R.attr.listPopupWindowStyle, 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;
+    }
+
+    /**
+     * Set a listener to receive a callback when the popup is dismissed.
+     *
+     * @param listener Listener that will be notified when the popup is dismissed.
+     */
+    public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
+        mPopup.setOnDismissListener(listener);
+    }
+
+    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());
+                final ListAdapter adapter = list.getAdapter();
+                mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
+            }
+            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 46cd45a..7c4897a 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;
@@ -27,10 +28,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.AttributeSet;
-import android.util.LongSparseArray;
 import android.util.SparseBooleanArray;
 import android.view.FocusFinder;
 import android.view.KeyEvent;
@@ -41,6 +39,7 @@
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.RemoteViews.RemoteView;
 
 import java.util.ArrayList;
 
@@ -64,10 +63,10 @@
  * @attr ref android.R.styleable#ListView_entries
  * @attr ref android.R.styleable#ListView_divider
  * @attr ref android.R.styleable#ListView_dividerHeight
- * @attr ref android.R.styleable#ListView_choiceMode
  * @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.
@@ -75,21 +74,6 @@
     static final int NO_POSITION = -1;
 
     /**
-     * Normal list that does not indicate choices
-     */
-    public static final int CHOICE_MODE_NONE = 0;
-
-    /**
-     * The list allows up to one choice
-     */
-    public static final int CHOICE_MODE_SINGLE = 1;
-
-    /**
-     * The list allows multiple choices
-     */
-    public static final int CHOICE_MODE_MULTIPLE = 2;
-
-    /**
      * When arrow scrolling, ListView will never scroll more than this factor
      * times the height of the list.
      */
@@ -121,12 +105,8 @@
     Drawable mDivider;
     int mDividerHeight;
     
-    Drawable mOverscrollHeader;
-    Drawable mOverscrollFooter;
-
     private boolean mIsCacheColorOpaque;
     private boolean mDividerIsOpaque;
-    private boolean mClipDivider;
 
     private boolean mHeaderDividersEnabled;
     private boolean mFooterDividersEnabled;
@@ -135,11 +115,6 @@
 
     private boolean mItemsCanFocus = false;
 
-    private int mChoiceMode = CHOICE_MODE_NONE;
-
-    private SparseBooleanArray mCheckStates;
-    private LongSparseArray<Boolean> mCheckedIdStates;
-
     // used for temporary calculations.
     private final Rect mTempRect = new Rect();
     private Paint mDividerPaint;
@@ -150,7 +125,7 @@
 
     // Keeps focused children visible through resizes
     private FocusSelector mFocusSelector;
-    
+
     public ListView(Context context) {
         this(context, null);
     }
@@ -178,16 +153,6 @@
             setDivider(d);
         }
         
-        final Drawable osHeader = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollHeader);
-        if (osHeader != null) {
-            setOverscrollHeader(osHeader);
-        }
-
-        final Drawable osFooter = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollFooter);
-        if (osFooter != null) {
-            setOverscrollFooter(osFooter);
-        }
-
         // Use the height specified, zero being the default
         final int dividerHeight = a.getDimensionPixelSize(
                 com.android.internal.R.styleable.ListView_dividerHeight, 0);
@@ -195,8 +160,6 @@
             setDividerHeight(dividerHeight);
         }
 
-        setChoiceMode(a.getInt(R.styleable.ListView_choiceMode, CHOICE_MODE_NONE));
-        
         mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
         mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
 
@@ -417,6 +380,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},
@@ -431,7 +404,7 @@
      */
     @Override
     public void setAdapter(ListAdapter adapter) {
-        if (null != mAdapter) {
+        if (mAdapter != null && mDataSetObserver != null) {
             mAdapter.unregisterDataSetObserver(mDataSetObserver);
         }
 
@@ -446,6 +419,10 @@
 
         mOldSelectedPosition = INVALID_POSITION;
         mOldSelectedRowId = INVALID_ROW_ID;
+
+        // AbsListView#setAdapter will update choice mode states.
+        super.setAdapter(adapter);
+
         if (mAdapter != null) {
             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
             mOldItemCount = mItemCount;
@@ -470,13 +447,6 @@
                 // Nothing selected
                 checkSelectionChanged();
             }
-
-            if (mChoiceMode != CHOICE_MODE_NONE &&
-                    mAdapter.hasStableIds() &&
-                    mCheckedIdStates == null) {
-                mCheckedIdStates = new LongSparseArray<Boolean>();
-            }
-
         } else {
             mAreAllItemsSelectable = true;
             checkFocus();
@@ -484,14 +454,6 @@
             checkSelectionChanged();
         }
 
-        if (mCheckStates != null) {
-            mCheckStates.clear();
-        }
-        
-        if (mCheckedIdStates != null) {
-            mCheckedIdStates.clear();
-        }
-
         requestLayout();
     }
 
@@ -879,6 +841,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
@@ -1803,6 +1784,9 @@
         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
             if (child instanceof Checkable) {
                 ((Checkable) child).setChecked(mCheckStates.get(position));
+            } else if (getContext().getApplicationInfo().targetSdkVersion
+                    >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+                child.setActivated(mCheckStates.get(position));
             }
         }
 
@@ -2958,52 +2942,14 @@
         }
         super.setCacheColorHint(color);
     }
-
-    void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
-        final int height = drawable.getMinimumHeight();
-
-        canvas.save();
-        canvas.clipRect(bounds);
-
-        final int span = bounds.bottom - bounds.top;
-        if (span < height) {
-            bounds.top = bounds.bottom - height;
-        }
-
-        drawable.setBounds(bounds);
-        drawable.draw(canvas);
-
-        canvas.restore();
-    }
-
-    void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
-        final int height = drawable.getMinimumHeight();
-
-        canvas.save();
-        canvas.clipRect(bounds);
-
-        final int span = bounds.bottom - bounds.top;
-        if (span < height) {
-            bounds.bottom = bounds.top + height;
-        }
-
-        drawable.setBounds(bounds);
-        drawable.draw(canvas);
-
-        canvas.restore();
-    }
-
+    
     @Override
     protected void dispatchDraw(Canvas canvas) {
         // Draw the dividers
         final int dividerHeight = mDividerHeight;
-        final Drawable overscrollHeader = mOverscrollHeader;
-        final Drawable overscrollFooter = mOverscrollFooter;
-        final boolean drawOverscrollHeader = overscrollHeader != null;
-        final boolean drawOverscrollFooter = overscrollFooter != null;
         final boolean drawDividers = dividerHeight > 0 && mDivider != null;
 
-        if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
+        if (drawDividers) {
             // Only modify the top and bottom in the loop, we set the left and right here
             final Rect bounds = mTempRect;
             bounds.left = mPaddingLeft;
@@ -3022,7 +2968,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();
@@ -3032,72 +2978,41 @@
 
             final int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY;
             if (!mStackFromBottom) {
-                int bottom = 0;
+                int bottom;
                 
-                // Draw top divider or header for overscroll
                 final int scrollY = mScrollY;
-                if (count > 0 && scrollY < 0) {
-                    if (drawOverscrollHeader) {
-                        bounds.bottom = 0;
-                        bounds.top = scrollY;
-                        drawOverscrollHeader(canvas, overscrollHeader, bounds);
-                    } else if (drawDividers) {
-                        bounds.bottom = 0;
-                        bounds.top = -dividerHeight;
-                        drawDivider(canvas, bounds, -1);
-                    }
-                }
-
                 for (int i = 0; i < count; i++) {
                     if ((headerDividers || first + i >= headerCount) &&
                             (footerDividers || first + i < footerLimit)) {
                         View child = getChildAt(i);
                         bottom = child.getBottom();
                         // Don't draw dividers next to items that are not enabled
-                        if (drawDividers &&
-                                (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
-                            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);
                         }
                     }
                 }
-
-                final int overFooterBottom = mBottom + mScrollY;
-                if (drawOverscrollFooter && first + count == itemCount &&
-                        overFooterBottom > bottom) {
-                    bounds.top = bottom;
-                    bounds.bottom = overFooterBottom;
-                    drawOverscrollFooter(canvas, overscrollFooter, bounds);
-                }
             } else {
                 int top;
                 int listTop = mListPadding.top;
 
                 final int scrollY = mScrollY;
 
-                if (count > 0 && drawOverscrollHeader) {
-                    bounds.top = scrollY;
-                    bounds.bottom = getChildAt(0).getTop();
-                    drawOverscrollHeader(canvas, overscrollHeader, bounds);
-                }
-
-                final int start = drawOverscrollHeader ? 1 : 0;
-                for (int i = start; i < count; i++) {
+                for (int i = 0; i < count; i++) {
                     if ((headerDividers || first + i >= headerCount) &&
                             (footerDividers || first + i < footerLimit)) {
                         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))))) {
@@ -3118,16 +3033,9 @@
                 }
                 
                 if (count > 0 && scrollY > 0) {
-                    if (drawOverscrollFooter) {
-                        final int absListBottom = mBottom;
-                        bounds.top = absListBottom;
-                        bounds.bottom = absListBottom + scrollY;
-                        drawOverscrollFooter(canvas, overscrollFooter, bounds);
-                    } else if (drawDividers) {
-                        bounds.top = listBottom;
-                        bounds.bottom = listBottom + dividerHeight;
-                        drawDivider(canvas, bounds, -1);
-                    }
+                    bounds.top = listBottom;
+                    bounds.bottom = listBottom + dividerHeight;
+                    drawDivider(canvas, bounds, -1);
                 }
             }
         }
@@ -3148,20 +3056,9 @@
     void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
         // This widget draws the same divider for all children
         final Drawable divider = mDivider;
-        final boolean clipDivider = mClipDivider;
 
-        if (!clipDivider) {
-            divider.setBounds(bounds);
-        } else {
-            canvas.save();
-            canvas.clipRect(bounds);
-        }
-
+        divider.setBounds(bounds);
         divider.draw(canvas);
-
-        if (clipDivider) {
-            canvas.restore();
-        }
     }
 
     /**
@@ -3182,10 +3079,8 @@
     public void setDivider(Drawable divider) {
         if (divider != null) {
             mDividerHeight = divider.getIntrinsicHeight();
-            mClipDivider = divider instanceof ColorDrawable;
         } else {
             mDividerHeight = 0;
-            mClipDivider = false;
         }
         mDivider = divider;
         mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
@@ -3236,45 +3131,6 @@
         invalidate();
     }
     
-    /**
-     * Sets the drawable that will be drawn above all other list content.
-     * This area can become visible when the user overscrolls the list.
-     *
-     * @param header The drawable to use
-     */
-    public void setOverscrollHeader(Drawable header) {
-        mOverscrollHeader = header;
-        if (mScrollY < 0) {
-            invalidate();
-        }
-    }
-
-    /**
-     * @return The drawable that will be drawn above all other list content
-     */
-    public Drawable getOverscrollHeader() {
-        return mOverscrollHeader;
-    }
-
-    /**
-     * Sets the drawable that will be drawn below all other list content.
-     * This area can become visible when the user overscrolls the list,
-     * or when the list's content does not fully fill the container area.
-     *
-     * @param footer The drawable to use
-     */
-    public void setOverscrollFooter(Drawable footer) {
-        mOverscrollFooter = footer;
-        invalidate();
-    }
-
-    /**
-     * @return The drawable that will be drawn below all other list content
-     */
-    public Drawable getOverscrollFooter() {
-        return mOverscrollFooter;
-    }
-
     @Override
     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
@@ -3445,177 +3301,6 @@
     }
 
     /**
-     * @see #setChoiceMode(int)
-     *
-     * @return The current choice mode
-     */
-    public int getChoiceMode() {
-        return mChoiceMode;
-    }
-
-    /**
-     * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
-     * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
-     * List allows up to one item to  be in a chosen state. By setting the choiceMode to
-     * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
-     *
-     * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
-     * {@link #CHOICE_MODE_MULTIPLE}
-     */
-    public void setChoiceMode(int choiceMode) {
-        mChoiceMode = choiceMode;
-        if (mChoiceMode != CHOICE_MODE_NONE) {
-            if (mCheckStates == null) {
-                mCheckStates = new SparseBooleanArray();
-            }
-            if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
-                mCheckedIdStates = new LongSparseArray<Boolean>();
-            }
-        }
-    }
-
-    @Override
-    public boolean performItemClick(View view, int position, long id) {
-        boolean handled = false;
-
-        if (mChoiceMode != CHOICE_MODE_NONE) {
-            handled = true;
-
-            if (mChoiceMode == CHOICE_MODE_MULTIPLE) {
-                boolean newValue = !mCheckStates.get(position, false);
-                mCheckStates.put(position, newValue);
-                if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
-                    if (newValue) {
-                        mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
-                    } else {
-                        mCheckedIdStates.delete(mAdapter.getItemId(position));
-                    }
-                }
-            } else {
-                boolean newValue = !mCheckStates.get(position, false);
-                if (newValue) {
-                    mCheckStates.clear();
-                    mCheckStates.put(position, true);
-                    if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
-                        mCheckedIdStates.clear();
-                        mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
-                    }
-                } 
-            }
-
-            mDataChanged = true;
-            rememberSyncState();
-            requestLayout();
-        }
-
-        handled |= super.performItemClick(view, position, id);
-
-        return handled;
-    }
-
-    /**
-     * Sets the checked state of the specified position. The is only valid if
-     * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
-     * {@link #CHOICE_MODE_MULTIPLE}.
-     * 
-     * @param position The item whose checked state is to be checked
-     * @param value The new checked state for the item
-     */
-    public void setItemChecked(int position, boolean value) {
-        if (mChoiceMode == CHOICE_MODE_NONE) {
-            return;
-        }
-
-        if (mChoiceMode == CHOICE_MODE_MULTIPLE) {
-            mCheckStates.put(position, value);
-            if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
-                if (value) {
-                    mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
-                } else {
-                    mCheckedIdStates.delete(mAdapter.getItemId(position));
-                }
-            }
-        } else {
-            boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
-            // Clear all values if we're checking something, or unchecking the currently
-            // selected item
-            if (value || isItemChecked(position)) {
-                mCheckStates.clear();
-                if (updateIds) {
-                    mCheckedIdStates.clear();
-                }
-            }
-            // this may end up selecting the value we just cleared but this way
-            // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
-            if (value) {
-                mCheckStates.put(position, true);
-                if (updateIds) {
-                    mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
-                }
-            }
-        }
-
-        // Do not generate a data change while we are in the layout phase
-        if (!mInLayout && !mBlockLayoutRequests) {
-            mDataChanged = true;
-            rememberSyncState();
-            requestLayout();
-        }
-    }
-
-    /**
-     * 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}.
-     *
-     * @param position The item whose checked state to return
-     * @return The item's checked state or <code>false</code> if choice mode
-     *         is invalid
-     *
-     * @see #setChoiceMode(int)
-     */
-    public boolean isItemChecked(int position) {
-        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
-            return mCheckStates.get(position);
-        }
-
-        return false;
-    }
-
-    /**
-     * Returns the currently checked item. The result is only valid if the choice
-     * mode has been set to {@link #CHOICE_MODE_SINGLE}.
-     *
-     * @return The position of the currently checked item or
-     *         {@link #INVALID_POSITION} if nothing is selected
-     *
-     * @see #setChoiceMode(int)
-     */
-    public int getCheckedItemPosition() {
-        if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
-            return mCheckStates.keyAt(0);
-        }
-
-        return INVALID_POSITION;
-    }
-
-    /**
-     * Returns the set of checked items in the list. The result is only valid if
-     * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
-     *
-     * @return  A SparseBooleanArray which will return true for each call to
-     *          get(int position) where position is a position in the list,
-     *          or <code>null</code> if the choice mode is set to
-     *          {@link #CHOICE_MODE_NONE}.
-     */
-    public SparseBooleanArray getCheckedItemPositions() {
-        if (mChoiceMode != CHOICE_MODE_NONE) {
-            return mCheckStates;
-        }
-        return null;
-    }
-
-    /**
      * Returns the set of checked items ids. The result is only valid if the
      * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
      * 
@@ -3624,6 +3309,7 @@
      *         
      * @deprecated Use {@link #getCheckedItemIds()} instead.
      */
+    @Deprecated
     public long[] getCheckItemIds() {
         // Use new behavior that correctly handles stable ID mapping.
         if (mAdapter != null && mAdapter.hasStableIds()) {
@@ -3658,115 +3344,4 @@
         }
         return new long[0];
     }
-    
-    /**
-     * Returns the set of checked items ids. The result is only valid if the
-     * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
-     * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
-     * 
-     * @return A new array which contains the id of each checked item in the
-     *         list.
-     */
-    public long[] getCheckedItemIds() {
-        if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
-            return new long[0];
-        }
-        
-        final LongSparseArray<Boolean> idStates = mCheckedIdStates;
-        final int count = idStates.size();
-        final long[] ids = new long[count];
-        
-        for (int i = 0; i < count; i++) {
-            ids[i] = idStates.keyAt(i);
-        }
-        
-        return ids;
-    }
-
-    /**
-     * Clear any choices previously set
-     */
-    public void clearChoices() {
-        if (mCheckStates != null) {
-            mCheckStates.clear();
-        }
-        if (mCheckedIdStates != null) {
-            mCheckedIdStates.clear();
-        }
-    }
-
-    static class SavedState extends BaseSavedState {
-        SparseBooleanArray checkState;
-        LongSparseArray<Boolean> checkIdState;
-
-        /**
-         * Constructor called from {@link ListView#onSaveInstanceState()}
-         */
-        SavedState(Parcelable superState, SparseBooleanArray checkState,
-                LongSparseArray<Boolean> checkIdState) {
-            super(superState);
-            this.checkState = checkState;
-            this.checkIdState = checkIdState;
-        }
-
-        /**
-         * Constructor called from {@link #CREATOR}
-         */
-        private SavedState(Parcel in) {
-            super(in);
-            checkState = in.readSparseBooleanArray();
-            long[] idState = in.createLongArray();
-
-            if (idState.length > 0) {
-                checkIdState = new LongSparseArray<Boolean>();
-                checkIdState.setValues(idState, Boolean.TRUE);
-            }
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            super.writeToParcel(out, flags);
-            out.writeSparseBooleanArray(checkState);
-            out.writeLongArray(checkIdState != null ? checkIdState.getKeys() : new long[0]);
-        }
-
-        @Override
-        public String toString() {
-            return "ListView.SavedState{"
-                    + Integer.toHexString(System.identityHashCode(this))
-                    + " checkState=" + checkState + "}";
-        }
-
-        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];
-            }
-        };
-    }
-
-    @Override
-    public Parcelable onSaveInstanceState() {
-        Parcelable superState = super.onSaveInstanceState();
-        return new SavedState(superState, mCheckStates, mCheckedIdStates);
-    }
-
-    @Override
-    public void onRestoreInstanceState(Parcelable state) {
-        SavedState ss = (SavedState) state;
-
-        super.onRestoreInstanceState(ss.getSuperState());
-
-        if (ss.checkState != null) {
-           mCheckStates = ss.checkState;
-        }
-
-        if (ss.checkIdState != null) {
-            mCheckedIdStates = ss.checkIdState;
-        }
-    }
 }
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
deleted file mode 100644
index 78973ad..0000000
--- a/core/java/android/widget/OverScroller.java
+++ /dev/null
@@ -1,849 +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;
-
-import android.content.Context;
-import android.hardware.SensorManager;
-import android.util.FloatMath;
-import android.view.ViewConfiguration;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-/**
- * This class encapsulates scrolling with the ability to overshoot the bounds
- * of a scrolling operation. This class is a drop-in replacement for
- * {@link android.widget.Scroller} in most cases.
- */
-public class OverScroller {
-    private int mMode;
-
-    private MagneticOverScroller mScrollerX;
-    private MagneticOverScroller mScrollerY;
-
-    private final Interpolator mInterpolator;
-
-    private static final int DEFAULT_DURATION = 250;
-    private static final int SCROLL_MODE = 0;
-    private static final int FLING_MODE = 1;
-
-    /**
-     * Creates an OverScroller with a viscous fluid scroll interpolator.
-     * @param context
-     */
-    public OverScroller(Context context) {
-        this(context, null);
-    }
-
-    /**
-     * Creates an OverScroller with default edge bounce coefficients.
-     * @param context The context of this application.
-     * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
-     * be used.
-     */
-    public OverScroller(Context context, Interpolator interpolator) {
-        this(context, interpolator, MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT,
-                MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT);
-    }
-
-    /**
-     * Creates an OverScroller.
-     * @param context The context of this application.
-     * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
-     * be used.
-     * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
-     * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
-     * means no bounce.
-     * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
-     */
-    public OverScroller(Context context, Interpolator interpolator,
-            float bounceCoefficientX, float bounceCoefficientY) {
-        mInterpolator = interpolator;
-        mScrollerX = new MagneticOverScroller();
-        mScrollerY = new MagneticOverScroller();
-        MagneticOverScroller.initializeFromContext(context);
-
-        mScrollerX.setBounceCoefficient(bounceCoefficientX);
-        mScrollerY.setBounceCoefficient(bounceCoefficientY);
-    }
-
-    /**
-     *
-     * Returns whether the scroller has finished scrolling.
-     *
-     * @return True if the scroller has finished scrolling, false otherwise.
-     */
-    public final boolean isFinished() {
-        return mScrollerX.mFinished && mScrollerY.mFinished;
-    }
-
-    /**
-     * Force the finished field to a particular value. Contrary to
-     * {@link #abortAnimation()}, forcing the animation to finished
-     * does NOT cause the scroller to move to the final x and y
-     * position.
-     *
-     * @param finished The new finished value.
-     */
-    public final void forceFinished(boolean finished) {
-        mScrollerX.mFinished = mScrollerY.mFinished = finished;
-    }
-
-    /**
-     * Returns the current X offset in the scroll.
-     *
-     * @return The new X offset as an absolute distance from the origin.
-     */
-    public final int getCurrX() {
-        return mScrollerX.mCurrentPosition;
-    }
-
-    /**
-     * Returns the current Y offset in the scroll.
-     *
-     * @return The new Y offset as an absolute distance from the origin.
-     */
-    public final int getCurrY() {
-        return mScrollerY.mCurrentPosition;
-    }
-
-    /**
-     * @hide
-     * Returns the current velocity.
-     *
-     * @return The original velocity less the deceleration, norm of the X and Y velocity vector.
-     */
-    public float getCurrVelocity() {
-        float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
-        squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
-        return FloatMath.sqrt(squaredNorm);
-    }
-
-    /**
-     * Returns the start X offset in the scroll.
-     *
-     * @return The start X offset as an absolute distance from the origin.
-     */
-    public final int getStartX() {
-        return mScrollerX.mStart;
-    }
-
-    /**
-     * Returns the start Y offset in the scroll.
-     *
-     * @return The start Y offset as an absolute distance from the origin.
-     */
-    public final int getStartY() {
-        return mScrollerY.mStart;
-    }
-
-    /**
-     * Returns where the scroll will end. Valid only for "fling" scrolls.
-     *
-     * @return The final X offset as an absolute distance from the origin.
-     */
-    public final int getFinalX() {
-        return mScrollerX.mFinal;
-    }
-
-    /**
-     * Returns where the scroll will end. Valid only for "fling" scrolls.
-     *
-     * @return The final Y offset as an absolute distance from the origin.
-     */
-    public final int getFinalY() {
-        return mScrollerY.mFinal;
-    }
-
-    /**
-     * Returns how long the scroll event will take, in milliseconds.
-     *
-     * @return The duration of the scroll in milliseconds.
-     *
-     * @hide Pending removal once nothing depends on it
-     * @deprecated OverScrollers don't necessarily have a fixed duration.
-     *             This function will lie to the best of its ability.
-     */
-    public final int getDuration() {
-        return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
-    }
-
-    /**
-     * Extend the scroll animation. This allows a running animation to scroll
-     * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
-     *
-     * @param extend Additional time to scroll in milliseconds.
-     * @see #setFinalX(int)
-     * @see #setFinalY(int)
-     *
-     * @hide Pending removal once nothing depends on it
-     * @deprecated OverScrollers don't necessarily have a fixed duration.
-     *             Instead of setting a new final position and extending
-     *             the duration of an existing scroll, use startScroll
-     *             to begin a new animation.
-     */
-    public void extendDuration(int extend) {
-        mScrollerX.extendDuration(extend);
-        mScrollerY.extendDuration(extend);
-    }
-
-    /**
-     * Sets the final position (X) for this scroller.
-     *
-     * @param newX The new X offset as an absolute distance from the origin.
-     * @see #extendDuration(int)
-     * @see #setFinalY(int)
-     *
-     * @hide Pending removal once nothing depends on it
-     * @deprecated OverScroller's final position may change during an animation.
-     *             Instead of setting a new final position and extending
-     *             the duration of an existing scroll, use startScroll
-     *             to begin a new animation.
-     */
-    public void setFinalX(int newX) {
-        mScrollerX.setFinalPosition(newX);
-    }
-
-    /**
-     * Sets the final position (Y) for this scroller.
-     *
-     * @param newY The new Y offset as an absolute distance from the origin.
-     * @see #extendDuration(int)
-     * @see #setFinalX(int)
-     *
-     * @hide Pending removal once nothing depends on it
-     * @deprecated OverScroller's final position may change during an animation.
-     *             Instead of setting a new final position and extending
-     *             the duration of an existing scroll, use startScroll
-     *             to begin a new animation.
-     */
-    public void setFinalY(int newY) {
-        mScrollerY.setFinalPosition(newY);
-    }
-
-    /**
-     * Call this when you want to know the new location. If it returns true, the
-     * animation is not yet finished.
-     */
-    public boolean computeScrollOffset() {
-        if (isFinished()) {
-            return false;
-        }
-
-        switch (mMode) {
-            case SCROLL_MODE:
-                long time = AnimationUtils.currentAnimationTimeMillis();
-                // Any scroller can be used for time, since they were started
-                // together in scroll mode. We use X here.
-                final long elapsedTime = time - mScrollerX.mStartTime;
-
-                final int duration = mScrollerX.mDuration;
-                if (elapsedTime < duration) {
-                    float q = (float) (elapsedTime) / duration;
-
-                    if (mInterpolator == null)
-                        q = Scroller.viscousFluid(q);
-                    else
-                        q = mInterpolator.getInterpolation(q);
-
-                    mScrollerX.updateScroll(q);
-                    mScrollerY.updateScroll(q);
-                } else {
-                    abortAnimation();
-                }
-                break;
-
-            case FLING_MODE:
-                if (!mScrollerX.mFinished) {
-                    if (!mScrollerX.update()) {
-                        if (!mScrollerX.continueWhenFinished()) {
-                            mScrollerX.finish();
-                        }
-                    }
-                }
-
-                if (!mScrollerY.mFinished) {
-                    if (!mScrollerY.update()) {
-                        if (!mScrollerY.continueWhenFinished()) {
-                            mScrollerY.finish();
-                        }
-                    }
-                }
-
-                break;
-        }
-
-        return true;
-    }
-
-    /**
-     * Start scrolling by providing a starting point and the distance to travel.
-     * The scroll will use the default value of 250 milliseconds for the
-     * duration.
-     *
-     * @param startX Starting horizontal scroll offset in pixels. Positive
-     *        numbers will scroll the content to the left.
-     * @param startY Starting vertical scroll offset in pixels. Positive numbers
-     *        will scroll the content up.
-     * @param dx Horizontal distance to travel. Positive numbers will scroll the
-     *        content to the left.
-     * @param dy Vertical distance to travel. Positive numbers will scroll the
-     *        content up.
-     */
-    public void startScroll(int startX, int startY, int dx, int dy) {
-        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
-    }
-
-    /**
-     * Start scrolling by providing a starting point and the distance to travel.
-     *
-     * @param startX Starting horizontal scroll offset in pixels. Positive
-     *        numbers will scroll the content to the left.
-     * @param startY Starting vertical scroll offset in pixels. Positive numbers
-     *        will scroll the content up.
-     * @param dx Horizontal distance to travel. Positive numbers will scroll the
-     *        content to the left.
-     * @param dy Vertical distance to travel. Positive numbers will scroll the
-     *        content up.
-     * @param duration Duration of the scroll in milliseconds.
-     */
-    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
-        mMode = SCROLL_MODE;
-        mScrollerX.startScroll(startX, dx, duration);
-        mScrollerY.startScroll(startY, dy, duration);
-    }
-
-    /**
-     * Call this when you want to 'spring back' into a valid coordinate range.
-     *
-     * @param startX Starting X coordinate
-     * @param startY Starting Y coordinate
-     * @param minX Minimum valid X value
-     * @param maxX Maximum valid X value
-     * @param minY Minimum valid Y value
-     * @param maxY Minimum valid Y value
-     * @return true if a springback was initiated, false if startX and startY were
-     *          already within the valid range.
-     */
-    public boolean springback(int startX, int startY, int minX, int maxX, int minY, int maxY) {
-        mMode = FLING_MODE;
-
-        // Make sure both methods are called.
-        final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
-        final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
-        return spingbackX || spingbackY;
-    }
-
-    public void fling(int startX, int startY, int velocityX, int velocityY,
-            int minX, int maxX, int minY, int maxY) {
-        fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
-    }
-
-    /**
-     * Start scrolling based on a fling gesture. The distance traveled will
-     * depend on the initial velocity of the fling.
-     *
-     * @param startX Starting point of the scroll (X)
-     * @param startY Starting point of the scroll (Y)
-     * @param velocityX Initial velocity of the fling (X) measured in pixels per
-     *            second.
-     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
-     *            second
-     * @param minX Minimum X value. The scroller will not scroll past this point
-     *            unless overX > 0. If overfling is allowed, it will use minX as
-     *            a springback boundary.
-     * @param maxX Maximum X value. The scroller will not scroll past this point
-     *            unless overX > 0. If overfling is allowed, it will use maxX as
-     *            a springback boundary.
-     * @param minY Minimum Y value. The scroller will not scroll past this point
-     *            unless overY > 0. If overfling is allowed, it will use minY as
-     *            a springback boundary.
-     * @param maxY Maximum Y value. The scroller will not scroll past this point
-     *            unless overY > 0. If overfling is allowed, it will use maxY as
-     *            a springback boundary.
-     * @param overX Overfling range. If > 0, horizontal overfling in either
-     *            direction will be possible.
-     * @param overY Overfling range. If > 0, vertical overfling in either
-     *            direction will be possible.
-     */
-    public void fling(int startX, int startY, int velocityX, int velocityY,
-            int minX, int maxX, int minY, int maxY, int overX, int overY) {
-        mMode = FLING_MODE;
-        mScrollerX.fling(startX, velocityX, minX, maxX, overX);
-        mScrollerY.fling(startY, velocityY, minY, maxY, overY);
-    }
-
-    /**
-     * Notify the scroller that we've reached a horizontal boundary.
-     * Normally the information to handle this will already be known
-     * when the animation is started, such as in a call to one of the
-     * fling functions. However there are cases where this cannot be known
-     * in advance. This function will transition the current motion and
-     * animate from startX to finalX as appropriate.
-     *
-     * @param startX Starting/current X position
-     * @param finalX Desired final X position
-     * @param overX Magnitude of overscroll allowed. This should be the maximum
-     *              desired distance from finalX. Absolute value - must be positive.
-     */
-    public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
-        mScrollerX.notifyEdgeReached(startX, finalX, overX);
-    }
-
-    /**
-     * Notify the scroller that we've reached a vertical boundary.
-     * Normally the information to handle this will already be known
-     * when the animation is started, such as in a call to one of the
-     * fling functions. However there are cases where this cannot be known
-     * in advance. This function will animate a parabolic motion from
-     * startY to finalY.
-     *
-     * @param startY Starting/current Y position
-     * @param finalY Desired final Y position
-     * @param overY Magnitude of overscroll allowed. This should be the maximum
-     *              desired distance from finalY.
-     */
-    public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
-        mScrollerY.notifyEdgeReached(startY, finalY, overY);
-    }
-
-    /**
-     * Returns whether the current Scroller is currently returning to a valid position.
-     * Valid bounds were provided by the
-     * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
-     *
-     * One should check this value before calling
-     * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
-     * to restore a valid position will then be stopped. The caller has to take into account
-     * the fact that the started scroll will start from an overscrolled position.
-     *
-     * @return true when the current position is overscrolled and in the process of
-     *         interpolating back to a valid value.
-     */
-    public boolean isOverscrolled() {
-        return ((!mScrollerX.mFinished &&
-                mScrollerX.mState != MagneticOverScroller.TO_EDGE) ||
-                (!mScrollerY.mFinished &&
-                        mScrollerY.mState != MagneticOverScroller.TO_EDGE));
-    }
-
-    /**
-     * Stops the animation. Contrary to {@link #forceFinished(boolean)},
-     * aborting the animating causes the scroller to move to the final x and y
-     * positions.
-     *
-     * @see #forceFinished(boolean)
-     */
-    public void abortAnimation() {
-        mScrollerX.finish();
-        mScrollerY.finish();
-    }
-
-    /**
-     * Returns the time elapsed since the beginning of the scrolling.
-     *
-     * @return The elapsed time in milliseconds.
-     *
-     * @hide
-     */
-    public int timePassed() {
-        final long time = AnimationUtils.currentAnimationTimeMillis();
-        final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
-        return (int) (time - startTime);
-    }
-
-    static class MagneticOverScroller {
-        // Initial position
-        int mStart;
-
-        // Current position
-        int mCurrentPosition;
-
-        // Final position
-        int mFinal;
-
-        // Initial velocity
-        int mVelocity;
-
-        // Current velocity
-        float mCurrVelocity;
-
-        // Constant current deceleration
-        float mDeceleration;
-
-        // Animation starting time, in system milliseconds
-        long mStartTime;
-
-        // Animation duration, in milliseconds
-        int mDuration;
-
-        // Whether the animation is currently in progress
-        boolean mFinished;
-
-        // Constant gravity value, used to scale deceleration
-        static float GRAVITY;
-
-        static void initializeFromContext(Context context) {
-            final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
-            GRAVITY = SensorManager.GRAVITY_EARTH // g (m/s^2)
-                    * 39.37f // inch/meter
-                    * ppi // pixels per inch
-                    * ViewConfiguration.getScrollFriction();
-        }
-
-        private static final int TO_EDGE = 0;
-        private static final int TO_BOUNDARY = 1;
-        private static final int TO_BOUNCE = 2;
-
-        private int mState = TO_EDGE;
-
-        // The allowed overshot distance before boundary is reached.
-        private int mOver;
-
-        // Duration in milliseconds to go back from edge to edge. Springback is half of it.
-        private static final int OVERSCROLL_SPRINGBACK_DURATION = 200;
-
-        // Oscillation period
-        private static final float TIME_COEF =
-            1000.0f * (float) Math.PI / OVERSCROLL_SPRINGBACK_DURATION;
-
-        // If the velocity is smaller than this value, no bounce is triggered
-        // when the edge limits are reached (would result in a zero pixels
-        // displacement anyway).
-        private static final float MINIMUM_VELOCITY_FOR_BOUNCE = Float.MAX_VALUE;//140.0f;
-
-        // Proportion of the velocity that is preserved when the edge is reached.
-        private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.16f;
-
-        private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT;
-
-        MagneticOverScroller() {
-            mFinished = true;
-        }
-
-        void updateScroll(float q) {
-            mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
-        }
-
-        /*
-         * Get a signed deceleration that will reduce the velocity.
-         */
-        static float getDeceleration(int velocity) {
-            return velocity > 0 ? -GRAVITY : GRAVITY;
-        }
-
-        /*
-         * Returns the time (in milliseconds) it will take to go from start to end.
-         */
-        static int computeDuration(int start, int end, float initialVelocity, float deceleration) {
-            final int distance = start - end;
-            final float discriminant = initialVelocity * initialVelocity - 2.0f * deceleration
-                    * distance;
-            if (discriminant >= 0.0f) {
-                float delta = (float) Math.sqrt(discriminant);
-                if (deceleration < 0.0f) {
-                    delta = -delta;
-                }
-                return (int) (1000.0f * (-initialVelocity - delta) / deceleration);
-            }
-
-            // End position can not be reached
-            return 0;
-        }
-
-        void startScroll(int start, int distance, int duration) {
-            mFinished = false;
-
-            mStart = start;
-            mFinal = start + distance;
-
-            mStartTime = AnimationUtils.currentAnimationTimeMillis();
-            mDuration = duration;
-
-            // Unused
-            mDeceleration = 0.0f;
-            mVelocity = 0;
-        }
-
-        void fling(int start, int velocity, int min, int max) {
-            mFinished = false;
-
-            mStart = start;
-            mStartTime = AnimationUtils.currentAnimationTimeMillis();
-
-            mVelocity = velocity;
-
-            mDeceleration = getDeceleration(velocity);
-
-            // A start from an invalid position immediately brings back to a valid position
-            if (mStart < min) {
-                mDuration = 0;
-                mFinal = min;
-                return;
-            }
-
-            if (mStart > max) {
-                mDuration = 0;
-                mFinal = max;
-                return;
-            }
-
-            // Duration are expressed in milliseconds
-            mDuration = (int) (-1000.0f * velocity / mDeceleration);
-
-            mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration));
-
-            // Clamp to a valid final position
-            if (mFinal < min) {
-                mFinal = min;
-                mDuration = computeDuration(mStart, min, mVelocity, mDeceleration);
-            }
-
-            if (mFinal > max) {
-                mFinal = max;
-                mDuration = computeDuration(mStart, max, mVelocity, mDeceleration);
-            }
-        }
-
-        void finish() {
-            mCurrentPosition = mFinal;
-            // Not reset since WebView relies on this value for fast fling.
-            // mCurrVelocity = 0.0f;
-            mFinished = true;
-        }
-
-        void setFinalPosition(int position) {
-            mFinal = position;
-            mFinished = false;
-        }
-
-        void extendDuration(int extend) {
-            final long time = AnimationUtils.currentAnimationTimeMillis();
-            final int elapsedTime = (int) (time - mStartTime);
-            mDuration = elapsedTime + extend;
-            mFinished = false;
-        }
-
-        void setBounceCoefficient(float coefficient) {
-            mBounceCoefficient = coefficient;
-        }
-
-        boolean springback(int start, int min, int max) {
-            mFinished = true;
-
-            mStart = start;
-            mVelocity = 0;
-
-            mStartTime = AnimationUtils.currentAnimationTimeMillis();
-            mDuration = 0;
-
-            if (start < min) {
-                startSpringback(start, min, false);
-            } else if (start > max) {
-                startSpringback(start, max, true);
-            }
-
-            return !mFinished;
-        }
-
-        private void startSpringback(int start, int end, boolean positive) {
-            mFinished = false;
-            mState = TO_BOUNCE;
-            mStart = mFinal = end;
-            mDuration = OVERSCROLL_SPRINGBACK_DURATION;
-            mStartTime -= OVERSCROLL_SPRINGBACK_DURATION / 2;
-            mVelocity = (int) (Math.abs(end - start) * TIME_COEF * (positive ? 1.0 : -1.0f));
-        }
-
-        void fling(int start, int velocity, int min, int max, int over) {
-            mState = TO_EDGE;
-            mOver = over;
-
-            mFinished = false;
-
-            mStart = start;
-            mStartTime = AnimationUtils.currentAnimationTimeMillis();
-
-            mVelocity = velocity;
-
-            mDeceleration = getDeceleration(velocity);
-
-            // Duration are expressed in milliseconds
-            mDuration = (int) (-1000.0f * velocity / mDeceleration);
-
-            mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration));
-
-            // Clamp to a valid final position
-            if (mFinal < min) {
-                mFinal = min;
-                mDuration = computeDuration(mStart, min, mVelocity, mDeceleration);
-            }
-
-            if (mFinal > max) {
-                mFinal = max;
-                mDuration = computeDuration(mStart, max, mVelocity, mDeceleration);
-            }
-
-            if (start > max) {
-                if (start >= max + over) {
-                    springback(max + over, min, max);
-                } else {
-                    if (velocity <= 0) {
-                        springback(start, min, max);
-                    } else {
-                        long time = AnimationUtils.currentAnimationTimeMillis();
-                        final double durationSinceEdge =
-                            Math.atan((start-max) * TIME_COEF / velocity) / TIME_COEF;
-                        mStartTime = (int) (time - 1000.0f * durationSinceEdge);
-
-                        // Simulate a bounce that started from edge
-                        mStart = max;
-
-                        mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF));
-
-                        onEdgeReached();
-                    }
-                }
-            } else {
-                if (start < min) {
-                    if (start <= min - over) {
-                        springback(min - over, min, max);
-                    } else {
-                        if (velocity >= 0) {
-                            springback(start, min, max);
-                        } else {
-                            long time = AnimationUtils.currentAnimationTimeMillis();
-                            final double durationSinceEdge =
-                                Math.atan((start-min) * TIME_COEF / velocity) / TIME_COEF;
-                            mStartTime = (int) (time - 1000.0f * durationSinceEdge);
-
-                            // Simulate a bounce that started from edge
-                            mStart = min;
-
-                            mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF));
-
-                            onEdgeReached();
-                        }
-
-                    }
-                }
-            }
-        }
-
-        void notifyEdgeReached(int start, int end, int over) {
-            mDeceleration = getDeceleration(mVelocity);
-
-            // Local time, used to compute edge crossing time.
-            float timeCurrent = mCurrVelocity / mDeceleration;
-            final int distance = end - start;
-            float timeEdge = -(float) Math.sqrt((2.0f * distance / mDeceleration)
-                    + (timeCurrent * timeCurrent));
-
-            mVelocity = (int) (mDeceleration * timeEdge);
-
-            // Simulate a symmetric bounce that started from edge
-            mStart = end;
-
-            mOver = over;
-
-            long time = AnimationUtils.currentAnimationTimeMillis();
-            mStartTime = (int) (time - 1000.0f * (timeCurrent - timeEdge));
-
-            onEdgeReached();
-        }
-
-        private void onEdgeReached() {
-            // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
-            final float distance = mVelocity / TIME_COEF;
-
-            if (Math.abs(distance) < mOver) {
-                // Spring force will bring us back to final position
-                mState = TO_BOUNCE;
-                mFinal = mStart;
-                mDuration = OVERSCROLL_SPRINGBACK_DURATION;
-            } else {
-                // Velocity is too high, we will hit the boundary limit
-                mState = TO_BOUNDARY;
-                int over = mVelocity > 0 ? mOver : -mOver;
-                mFinal = mStart + over;
-                mDuration = (int) (1000.0f * Math.asin(over / distance) / TIME_COEF);
-            }
-        }
-
-        boolean continueWhenFinished() {
-            switch (mState) {
-                case TO_EDGE:
-                    // Duration from start to null velocity
-                    int duration = (int) (-1000.0f * mVelocity / mDeceleration);
-                    if (mDuration < duration) {
-                        // If the animation was clamped, we reached the edge
-                        mStart = mFinal;
-                        // Speed when edge was reached
-                        mVelocity = (int) (mVelocity + mDeceleration * mDuration / 1000.0f);
-                        mStartTime += mDuration;
-                        onEdgeReached();
-                    } else {
-                        // Normal stop, no need to continue
-                        return false;
-                    }
-                    break;
-                case TO_BOUNDARY:
-                    mStartTime += mDuration;
-                    startSpringback(mFinal, mFinal - (mVelocity > 0 ? mOver:-mOver), mVelocity > 0);
-                    break;
-                case TO_BOUNCE:
-                    //mVelocity = (int) (mVelocity * BOUNCE_COEFFICIENT);
-                    mVelocity = (int) (mVelocity * mBounceCoefficient);
-                    if (Math.abs(mVelocity) < MINIMUM_VELOCITY_FOR_BOUNCE) {
-                        return false;
-                    }
-                    mStartTime += mDuration;
-                    break;
-            }
-
-            update();
-            return true;
-        }
-
-        /*
-         * Update the current position and velocity for current time. Returns
-         * true if update has been done and false if animation duration has been
-         * reached.
-         */
-        boolean update() {
-            final long time = AnimationUtils.currentAnimationTimeMillis();
-            final long duration = time - mStartTime;
-
-            if (duration > mDuration) {
-                return false;
-            }
-
-            double distance;
-            final float t = duration / 1000.0f;
-            if (mState == TO_EDGE) {
-                mCurrVelocity = mVelocity + mDeceleration * t;
-                distance = mVelocity * t + mDeceleration * t * t / 2.0f;
-            } else {
-                final float d = t * TIME_COEF;
-                mCurrVelocity = mVelocity * (float)Math.cos(d);
-                distance = mVelocity / TIME_COEF * Math.sin(d);
-            }
-
-            mCurrentPosition = mStart + (int) distance;
-            return true;
-        }
-    }
-}
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 055ba87..d6c2db1 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -19,22 +19,24 @@
 import com.android.internal.R;
 
 import android.content.Context;
+import android.content.res.Resources;
 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.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnScrollChangedListener;
+import android.view.WindowManager;
 
 import java.lang.ref.WeakReference;
 
@@ -123,7 +125,7 @@
     private OnScrollChangedListener mOnScrollChangedListener =
         new OnScrollChangedListener() {
             public void onScrollChanged() {
-                View anchor = mAnchor.get();
+                View anchor = mAnchor != null ? mAnchor.get() : null;
                 if (anchor != null && mPopupView != null) {
                     WindowManager.LayoutParams p = (WindowManager.LayoutParams)
                             mPopupView.getLayoutParams();
@@ -159,12 +161,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);
 
@@ -1088,7 +1099,9 @@
         
         int bottomEdge = displayFrame.bottom;
         if (ignoreBottomDecorations) {
-            bottomEdge = anchor.getContext().getResources().getDisplayMetrics().heightPixels;
+            Resources res = anchor.getContext().getResources();
+            bottomEdge = res.getDisplayMetrics().heightPixels -
+                    (int) res.getDimension(com.android.internal.R.dimen.screen_margin_bottom);
         }
         final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
         final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
@@ -1381,6 +1394,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 ec7d927..3a4487c 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -716,8 +716,8 @@
             mAnimation.setDuration(mDuration);
             mAnimation.setInterpolator(mInterpolator);
             mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
-            postInvalidate();
         }
+        postInvalidate();
     }
 
     /**
@@ -730,6 +730,7 @@
             ((Animatable) mIndeterminateDrawable).stop();
             mShouldStartAnimationDrawable = false;
         }
+        postInvalidate();
     }
 
     /**
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 4bbb540..5598c65 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..9d214fc 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,7 +16,15 @@
 
 package android.widget;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
 import android.app.PendingIntent;
+import android.appwidget.AppWidgetHostView;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -38,13 +46,6 @@
 import android.view.LayoutInflater.Filter;
 import android.view.View.OnClickListener;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-
 
 /**
  * A class that describes a view hierarchy that can be displayed in
@@ -60,12 +61,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
@@ -73,8 +74,22 @@
      */
     private ArrayList<Action> mActions;
     
+    /**
+     * A class to keep track of memory usage by this RemoteViews
+     */
+    private MemoryUsageCounter mMemoryUsageCounter;
+
     
     /**
+     * This flag indicates whether this RemoteViews object is being created from a
+     * RemoteViewsService for use as a child of a widget collection. This flag is used
+     * to determine whether or not certain features are available, in particular,
+     * setting on click extras and setting on click pending intents. The former is enabled,
+     * and the latter disabled when this flag is true.
+     */
+     private boolean mIsWidgetCollectionChild = false;
+
+    /**
      * This annotation indicates that a subclass of View is alllowed to be used
      * with the {@link RemoteViews} mechanism.
      */
@@ -108,6 +123,266 @@
         public int describeContents() {
             return 0;
         }
+
+        /**
+         * Overridden by each class to report on it's own memory usage
+         */
+        public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
+            // We currently only calculate Bitmap memory usage, so by default, don't do anything
+            // here
+            return;
+        }
+    }
+
+    private class SetEmptyView extends Action {
+        int viewId;
+        int emptyViewId;
+
+        public final static int TAG = 6;
+
+        SetEmptyView(int viewId, int emptyViewId) {
+            this.viewId = viewId;
+            this.emptyViewId = emptyViewId;
+        }
+
+        SetEmptyView(Parcel in) {
+            this.viewId = in.readInt();
+            this.emptyViewId = in.readInt();
+        }
+
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(TAG);
+            out.writeInt(this.viewId);
+            out.writeInt(this.emptyViewId);
+        }
+
+        @Override
+        public void apply(View root) {
+            final View view = root.findViewById(viewId);
+            if (!(view instanceof AdapterView<?>)) return;
+
+            AdapterView<?> adapterView = (AdapterView<?>) view;
+
+            final View emptyView = root.findViewById(emptyViewId);
+            if (emptyView == null) return;
+
+            adapterView.setEmptyView(emptyView);
+        }
+    }
+
+    private class SetOnClickFillInIntent extends Action {
+        public SetOnClickFillInIntent(int id, Intent fillInIntent) {
+            this.viewId = id;
+            this.fillInIntent = fillInIntent;
+        }
+
+        public SetOnClickFillInIntent(Parcel parcel) {
+            viewId = parcel.readInt();
+            fillInIntent = Intent.CREATOR.createFromParcel(parcel);
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            fillInIntent.writeToParcel(dest, 0 /* no flags */);
+        }
+
+        @Override
+        public void apply(View root) {
+            final View target = root.findViewById(viewId);
+
+            if (!mIsWidgetCollectionChild) {
+                Log.e("RemoteViews", "The method setOnClickFillInIntent is available " +
+                        "only from RemoteViewsFactory (ie. on collection items).");
+                return;
+            }
+
+            if (target != null && fillInIntent != null) {
+                OnClickListener listener = new OnClickListener() {
+                    public void onClick(View v) {
+                        // Insure that this view is a child of an AdapterView
+                        View parent = (View) v.getParent();
+                        while (!(parent instanceof AdapterView<?>)
+                                && !(parent instanceof AppWidgetHostView)) {
+                            parent = (View) parent.getParent();
+                        }
+
+                        if (parent instanceof AppWidgetHostView) {
+                            // Somehow they've managed to get this far without having
+                            // and AdapterView as a parent.
+                            Log.e("RemoteViews", "Collection item doesn't have AdapterView parent");
+                            return;
+                        }
+
+                        // Insure that a template pending intent has been set on an ancestor
+                        if (!(parent.getTag() instanceof PendingIntent)) {
+                            Log.e("RemoteViews", "Attempting setOnClickFillInIntent without" +
+					" calling setPendingIntentTemplate on parent.");
+                            return;
+                        }
+
+                        PendingIntent pendingIntent = (PendingIntent) parent.getTag();
+
+                        final float appScale = v.getContext().getResources()
+                        .getCompatibilityInfo().applicationScale;
+                        final int[] pos = new int[2];
+                        v.getLocationOnScreen(pos);
+
+                        final Rect rect = new Rect();
+                        rect.left = (int) (pos[0] * appScale + 0.5f);
+                        rect.top = (int) (pos[1] * appScale + 0.5f);
+                        rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
+                        rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
+
+                        fillInIntent.setSourceBounds(rect);
+                        try {
+                            // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
+                            v.getContext().startIntentSender(
+                                    pendingIntent.getIntentSender(), fillInIntent,
+                                    Intent.FLAG_ACTIVITY_NEW_TASK,
+                                    Intent.FLAG_ACTIVITY_NEW_TASK, 0);
+                        } catch (IntentSender.SendIntentException e) {
+                            android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
+                        }
+                    }
+
+                };
+                target.setOnClickListener(listener);
+            }
+        }
+
+        int viewId;
+        Intent fillInIntent;
+
+        public final static int TAG = 9;
+    }
+
+    private class SetOnClickExtras extends Action {
+        public SetOnClickExtras(int id, Bundle extras) {
+            this.viewId = id;
+            this.extras = extras;
+        }
+
+        public SetOnClickExtras(Parcel parcel) {
+            viewId = parcel.readInt();
+            extras = Bundle.CREATOR.createFromParcel(parcel);
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            extras.writeToParcel(dest, 0 /* no flags */);
+        }
+
+        @Override
+        public void apply(View root) {
+            final View target = root.findViewById(viewId);
+
+            if (!mIsWidgetCollectionChild) {
+                Log.e("RemoteViews", "The method setOnClickExtras is available " +
+                        "only from RemoteViewsFactory (ie. on collection items).");
+                return;
+            }
+
+            if (target != null && extras != null) {
+                OnClickListener listener = new OnClickListener() {
+                    public void onClick(View v) {
+                        // Insure that this view is a child of an AdapterView
+                        View parent = (View) v.getParent();
+                        while (!(parent instanceof AdapterView<?>)
+                                && !(parent instanceof AppWidgetHostView)) {
+                            parent = (View) parent.getParent();
+                        }
+
+                        if (parent instanceof AppWidgetHostView) {
+                            // Somehow they've managed to get this far without having
+                            // and AdapterView as a parent.
+                            Log.e("RemoteViews", "Collection item doesn't have AdapterView parent");
+                            return;
+                        }
+
+                        // Insure that a template pending intent has been set on an ancestor
+                        if (!(parent.getTag() instanceof PendingIntent)) {
+                            Log.e("RemoteViews", "Attempting setOnClickExtras without calling " +
+                                "setPendingIntentTemplate on parent.");
+                            return;
+                        }
+
+                        PendingIntent pendingIntent = (PendingIntent) parent.getTag();
+
+                        final float appScale = v.getContext().getResources()
+                        .getCompatibilityInfo().applicationScale;
+                        final int[] pos = new int[2];
+                        v.getLocationOnScreen(pos);
+
+                        final Rect rect = new Rect();
+                        rect.left = (int) (pos[0] * appScale + 0.5f);
+                        rect.top = (int) (pos[1] * appScale + 0.5f);
+                        rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
+                        rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
+
+                        final Intent intent = new Intent();
+                        intent.setSourceBounds(rect);
+                        intent.putExtras(extras);
+
+                        try {
+                            // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
+                            v.getContext().startIntentSender(
+                                    pendingIntent.getIntentSender(), intent,
+                                    Intent.FLAG_ACTIVITY_NEW_TASK,
+                                    Intent.FLAG_ACTIVITY_NEW_TASK, 0);
+                        } catch (IntentSender.SendIntentException e) {
+                            android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
+                        }
+                    }
+
+                };
+                target.setOnClickListener(listener);
+            }
+        }
+
+        int viewId;
+        Bundle extras;
+
+        public final static int TAG = 7;
+    }
+
+    private class SetPendingIntentTemplate extends Action {
+        public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) {
+            this.viewId = id;
+            this.pendingIntentTemplate = pendingIntentTemplate;
+        }
+
+        public SetPendingIntentTemplate(Parcel parcel) {
+            viewId = parcel.readInt();
+            pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
+        }
+
+        @Override
+        public void apply(View root) {
+            final View target = root.findViewById(viewId);
+
+            // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense
+            if (target instanceof AdapterView<?>) {
+                // The PendingIntent template is stored in the view's tag.
+                target.setTag(pendingIntentTemplate);
+            } else {
+                Log.e("RemoteViews", "Cannot setPendingIntentTemplate on a view which is not" +
+                        "an AdapterView (id: " + viewId + ")");
+                return;
+            }
+        }
+
+        int viewId;
+        PendingIntent pendingIntentTemplate;
+
+        public final static int TAG = 8;
     }
 
     /**
@@ -120,21 +395,30 @@
             this.viewId = id;
             this.pendingIntent = pendingIntent;
         }
-        
+
         public SetOnClickPendingIntent(Parcel parcel) {
             viewId = parcel.readInt();
             pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
         }
-        
+
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(TAG);
             dest.writeInt(viewId);
             pendingIntent.writeToParcel(dest, 0 /* no flags */);
         }
-        
+
         @Override
         public void apply(View root) {
             final View target = root.findViewById(viewId);
+
+            // If the view is an AdapterView, setting a PendingIntent on click doesn't make much
+            // sense, do they mean to set a PendingIntent template for the AdapterView's children?
+            if (mIsWidgetCollectionChild) {
+                Log.e("RemoteViews", "Cannot setOnClickPendingIntent for collection item " +
+				"(id: " + viewId + ")");
+                // TODO: return; We'll let this slide until apps are up to date.
+            }
+
             if (target != null && pendingIntent != null) {
                 OnClickListener listener = new OnClickListener() {
                     public void onClick(View v) {
@@ -255,7 +539,7 @@
                 }
             }
         }
-        
+
         int viewId;
         boolean targetBackground;
         int alpha;
@@ -266,6 +550,63 @@
         public final static int TAG = 3;
     }
     
+    private class ReflectionActionWithoutParams extends Action {
+        int viewId;
+        String methodName;
+
+        public final static int TAG = 5;
+
+        ReflectionActionWithoutParams(int viewId, String methodName) {
+            this.viewId = viewId;
+            this.methodName = methodName;
+        }
+
+        ReflectionActionWithoutParams(Parcel in) {
+            this.viewId = in.readInt();
+            this.methodName = in.readString();
+        }
+
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(TAG);
+            out.writeInt(this.viewId);
+            out.writeString(this.methodName);
+        }
+
+        @Override
+        public void apply(View root) {
+            final View view = root.findViewById(viewId);
+            if (view == null) {
+                throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
+            }
+
+            Class klass = view.getClass();
+            Method method;
+            try {
+                method = klass.getMethod(this.methodName);
+            } catch (NoSuchMethodException ex) {
+                throw new ActionException("view: " + klass.getName() + " doesn't have method: "
+                        + this.methodName + "()");
+            }
+
+            if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
+                throw new ActionException("view: " + klass.getName()
+                        + " can't use method with RemoteViews: "
+                        + this.methodName + "()");
+            }
+
+            try {
+                //noinspection ConstantIfStatement
+                if (false) {
+                    Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+                        + this.methodName + "()");
+                }
+                method.invoke(view);
+            } catch (Exception ex) {
+                throw new ActionException(ex);
+            }
+        }
+    }
+
     /**
      * Base class for the reflection actions.
      */
@@ -285,6 +626,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 +689,9 @@
                 case BUNDLE:
                     this.value = in.readBundle();
                     break;
+                case INTENT:
+                    this.value = Intent.CREATOR.createFromParcel(in);
+                    break;
                 default:
                     break;
             }
@@ -402,6 +747,9 @@
                 case BUNDLE:
                     out.writeBundle((Bundle) this.value);
                     break;
+                case INTENT:
+                    ((Intent)this.value).writeToParcel(out, flags);
+                    break;
                 default:
                     break;
             }
@@ -435,6 +783,8 @@
                     return Bitmap.class;
                 case BUNDLE:
                     return Bundle.class;
+                case INTENT:
+                    return Intent.class;
                 default:
                     return null;
             }
@@ -481,6 +831,35 @@
                 throw new ActionException(ex);
             }
         }
+
+        @Override
+        public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
+            // We currently only calculate Bitmap memory usage
+            switch (this.type) {
+                case BITMAP:
+                    if (this.value != null) {
+                        final Bitmap b = (Bitmap) this.value;
+                        final Bitmap.Config c = b.getConfig();
+                        int bpp = 4;
+                        switch (c) {
+                        case ALPHA_8:
+                            bpp = 1;
+                            break;
+                        case RGB_565:
+                        case ARGB_4444:
+                            bpp = 2;
+                            break;
+                        case ARGB_8888:
+                            bpp = 4;
+                            break;
+                        }
+                        counter.bitmapIncrement(b.getWidth() * b.getHeight() * bpp);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
     }
 
     /**
@@ -518,6 +897,13 @@
             }
         }
 
+        @Override
+        public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
+            if (nestedViews != null) {
+                counter.bitmapIncrement(nestedViews.estimateBitmapMemoryUsage());
+            }
+        }
+
         int viewId;
         RemoteViews nestedViews;
 
@@ -525,6 +911,26 @@
     }
 
     /**
+     * Simple class used to keep track of memory usage in a RemoteViews.
+     *
+     */
+    private class MemoryUsageCounter {
+        public void clear() {
+            mBitmapHeapMemoryUsage = 0;
+        }
+
+        public void bitmapIncrement(int numBytes) {
+            mBitmapHeapMemoryUsage += numBytes;
+        }
+
+        public int getBitmapHeapMemoryUsage() {
+            return mBitmapHeapMemoryUsage;
+        }
+
+        int mBitmapHeapMemoryUsage;
+    }
+
+    /**
      * Create a new RemoteViews object that will display the views contained
      * in the specified layout file.
      * 
@@ -534,6 +940,10 @@
     public RemoteViews(String packageName, int layoutId) {
         mPackage = packageName;
         mLayoutId = layoutId;
+
+        // setup the memory usage statistics
+        mMemoryUsageCounter = new MemoryUsageCounter();
+        recalculateMemoryUsage();
     }
 
     /**
@@ -544,6 +954,8 @@
     public RemoteViews(Parcel parcel) {
         mPackage = parcel.readString();
         mLayoutId = parcel.readInt();
+        mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false;
+
         int count = parcel.readInt();
         if (count > 0) {
             mActions = new ArrayList<Action>(count);
@@ -562,18 +974,41 @@
                 case ViewGroupAction.TAG:
                     mActions.add(new ViewGroupAction(parcel));
                     break;
+                case ReflectionActionWithoutParams.TAG:
+                    mActions.add(new ReflectionActionWithoutParams(parcel));
+                    break;
+                case SetEmptyView.TAG:
+                    mActions.add(new SetEmptyView(parcel));
+                    break;
+                case SetOnClickExtras.TAG:
+                    mActions.add(new SetOnClickExtras(parcel));
+                    break;
+                case SetPendingIntentTemplate.TAG:
+                    mActions.add(new SetPendingIntentTemplate(parcel));
+                    break;
+                case SetOnClickFillInIntent.TAG:
+                    mActions.add(new SetOnClickFillInIntent(parcel));
+                    break;
                 default:
                     throw new ActionException("Tag " + tag + " not found");
                 }
             }
         }
+
+        // setup the memory usage statistics
+        mMemoryUsageCounter = new MemoryUsageCounter();
+        recalculateMemoryUsage();
     }
 
+    @Override
     public RemoteViews clone() {
         final RemoteViews that = new RemoteViews(mPackage, mLayoutId);
         if (mActions != null) {
             that.mActions = (ArrayList<Action>)mActions.clone();
         }
+
+        // update the memory usage stats of the cloned RemoteViews
+        that.recalculateMemoryUsage();
         return that;
     }
 
@@ -585,6 +1020,39 @@
         return mLayoutId;
     }
 
+    /*
+     * This flag indicates whether this RemoteViews object is being created from a
+     * RemoteViewsService for use as a child of a widget collection. This flag is used
+     * to determine whether or not certain features are available, in particular,
+     * setting on click extras and setting on click pending intents. The former is enabled,
+     * and the latter disabled when this flag is true.
+     */
+    void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
+        mIsWidgetCollectionChild = isWidgetCollectionChild;
+    }
+
+    /**
+     * Updates the memory usage statistics.
+     */
+    private void recalculateMemoryUsage() {
+        mMemoryUsageCounter.clear();
+
+        // Accumulate the memory usage for each action
+        if (mActions != null) {
+            final int count = mActions.size();
+            for (int i= 0; i < count; ++i) {
+                mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter);
+            }
+        }
+    }
+
+    /**
+     * Returns an estimate of the bitmap heap memory usage for this RemoteViews.
+     */
+    int estimateBitmapMemoryUsage() {
+        return mMemoryUsageCounter.getBitmapHeapMemoryUsage();
+    }
+
     /**
      * Add an action to be executed on the remote side when apply is called.
      * 
@@ -595,6 +1063,9 @@
             mActions = new ArrayList<Action>();
         }
         mActions.add(a);
+
+        // update the memory usage stats
+        a.updateMemoryUsageEstimate(mMemoryUsageCounter);
     }
 
     /**
@@ -622,6 +1093,24 @@
     }
 
     /**
+     * Equivalent to calling {@link AdapterViewFlipper#showNext()}
+     *
+     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showNext()}
+     */
+    public void showNext(int viewId) {
+        addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
+    }
+
+    /**
+     * Equivalent to calling {@link AdapterViewFlipper#showPrevious()}
+     *
+     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showPrevious()}
+     */
+    public void showPrevious(int viewId) {
+        addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
+    }
+
+    /**
      * Equivalent to calling View.setVisibility
      * 
      * @param viewId The id of the view whose visibility should change
@@ -630,7 +1119,7 @@
     public void setViewVisibility(int viewId, int visibility) {
         setInt(viewId, "setVisibility", visibility);
     }
-    
+
     /**
      * Equivalent to calling TextView.setText
      * 
@@ -672,6 +1161,16 @@
     }
 
     /**
+     * Equivalent to calling AdapterView.setEmptyView
+     *
+     * @param viewId The id of the view on which to set the empty view
+     * @param emptyViewId The view id of the empty view
+     */
+    public void setEmptyView(int viewId, int emptyViewId) {
+        addAction(new SetEmptyView(viewId, emptyViewId));
+    }
+
+    /**
      * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
      * {@link Chronometer#setFormat Chronometer.setFormat},
      * and {@link Chronometer#start Chronometer.start()} or
@@ -718,6 +1217,11 @@
      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
      * to launch the provided {@link PendingIntent}.
      * 
+     * When setting the on-click action of items within collections (eg. {@link ListView},
+     * {@link StackView} etc.), this method will not work. Instead, use {@link
+     * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with
+     * RemoteViews#setOnClickFillInIntent(int, Intent).
+     *
      * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
      * @param pendingIntent The {@link PendingIntent} to send when user clicks
      */
@@ -726,6 +1230,55 @@
     }
 
     /**
+     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
+     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
+     * this method should be used to set a single PendingIntent template on the collection, and
+     * individual items can differentiate their on-click behavior using
+     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
+     *
+     * @param viewId The id of the collection who's children will use this PendingIntent template
+     *          when clicked
+     * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
+     *          by a child of viewId and executed when that child is clicked
+     */
+    public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
+        addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
+    }
+
+    /**
+     * Being deprecated. See {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
+     *
+     * @param viewId
+     * @param extras
+     */
+    public void setOnClickExtras(int viewId, Bundle extras) {
+        addAction(new SetOnClickExtras(viewId, extras));
+    }
+
+    /**
+     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
+     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
+     * a single PendingIntent template can be set on the collection, see {@link
+     * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
+     * action of a given item can be distinguished by setting a fillInIntent on that item. The
+     * fillInIntent is then combined with the PendingIntent template in order to determine the final
+     * intent which will be executed when the item is clicked. This works as follows: any fields
+     * which are left blank in the PendingIntent template, but are provided by the fillInIntent
+     * will be overwritten, and the resulting PendingIntent will be used.
+     *
+     *
+     * of the PendingIntent template will then be filled in with the associated fields that are
+     * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
+     *
+     * @param viewId The id of the view on which to set the fillInIntent
+     * @param fillInIntent The intent which will be combined with the parent's PendingIntent
+     *        in order to determine the on-click behavior of the view specified by viewId
+     */
+    public void setOnClickFillInIntent(int viewId, Intent fillInIntent) {
+        addAction(new SetOnClickFillInIntent(viewId, fillInIntent));
+    }
+
+    /**
      * @hide
      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
@@ -769,6 +1322,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 +1499,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.
      * 
@@ -1000,6 +1594,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mPackage);
         dest.writeInt(mLayoutId);
+        dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
         int count;
         if (mActions != null) {
             count = mActions.size();
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
new file mode 100644
index 0000000..23d6758
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -0,0 +1,884 @@
+/*
+ * 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.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.HashSet;
+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.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+
+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 TAG = "RemoteViewsAdapter";
+
+    private Context mContext;
+    private Intent mIntent;
+    private RemoteViewsAdapterServiceConnection mServiceConnection;
+    private WeakReference<RemoteAdapterConnectionCallback> mCallback;
+    private FixedSizeRemoteViewsCache mCache;
+
+    // The set of requested views that are to be notified when the associated RemoteViews are
+    // loaded.
+    private RemoteViewsFrameLayoutRefSet mRequestedViews;
+
+    private HandlerThread mWorkerThread;
+    // items may be interrupted within the normally processed queues
+    private Handler mWorkerQueue;
+    private Handler mMainQueue;
+
+    /**
+     * 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.  This must be a static inner class to ensure that no references to the outer
+     * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
+     * garbage collected, and would cause us to leak activities due to the caching mechanism for
+     * FrameLayouts in the adapter).
+     */
+    private static class RemoteViewsAdapterServiceConnection implements ServiceConnection {
+        private boolean mConnected;
+        private WeakReference<RemoteViewsAdapter> mAdapter;
+        private IRemoteViewsFactory mRemoteViewsFactory;
+
+        public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
+            mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
+        }
+
+        public void onServiceConnected(ComponentName name,
+                IBinder service) {
+            mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+            mConnected = true;
+
+            // Queue up work that we need to do for the callback to run
+            final RemoteViewsAdapter adapter = mAdapter.get();
+            if (adapter == null) return;
+            adapter.mWorkerQueue.post(new Runnable() {
+                @Override
+                public void run() {
+                    // Call back to the service to notify that the data set changed
+                    if (adapter.mServiceConnection.isConnected()) {
+                        IRemoteViewsFactory factory =
+                            adapter.mServiceConnection.getRemoteViewsFactory();
+                        try {
+                            // call back to the factory
+                            factory.onDataSetChanged();
+                        } catch (Exception e) {
+                            Log.e(TAG, "Error notifying factory of data set changed in " +
+                                        "onServiceConnected(): " + e.getMessage());
+                            e.printStackTrace();
+
+                            // Return early to prevent anything further from being notified
+                            // (effectively nothing has changed)
+                            return;
+                        }
+
+                        // Request meta data so that we have up to date data when calling back to
+                        // the remote adapter callback
+                        adapter.updateMetaData();
+
+                        // Post a runnable to call back to the view to notify it that we have
+                        // connected
+                        adapter.mMainQueue.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                final RemoteAdapterConnectionCallback callback =
+                                    adapter.mCallback.get();
+                                if (callback != null) {
+                                    callback.onRemoteAdapterConnected();
+                                }
+                            }
+                        });
+                    }
+                }
+            });
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            mConnected = false;
+            mRemoteViewsFactory = null;
+
+            final RemoteViewsAdapter adapter = mAdapter.get();
+            if (adapter == null) return;
+            
+            // Clear the main/worker queues
+            adapter.mMainQueue.removeMessages(0);
+            adapter.mWorkerQueue.removeMessages(0);
+
+            // Clear the cache (the meta data will be re-requested on service re-connection)
+            synchronized (adapter.mCache) {
+                adapter.mCache.reset();
+            }
+
+            final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
+            if (callback != null) {
+                callback.onRemoteAdapterDisconnected();
+            }
+        }
+
+        public IRemoteViewsFactory getRemoteViewsFactory() {
+            return mRemoteViewsFactory;
+        }
+
+        public boolean isConnected() {
+            return mConnected;
+        }
+    }
+
+    /**
+     * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
+     * they are loaded.
+     */
+    private class RemoteViewsFrameLayout extends FrameLayout {
+        public RemoteViewsFrameLayout(Context context) {
+            super(context);
+        }
+
+        /**
+         * Updates this RemoteViewsFrameLayout depending on the view that was loaded.
+         * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
+         *             successfully.
+         */
+        public void onRemoteViewsLoaded(RemoteViews view) {
+            try {
+                // Remove all the children of this layout first
+                removeAllViews();
+                addView(view.apply(getContext(), this));
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to apply RemoteViews.");
+            }
+        }
+    }
+
+    /**
+     * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
+     * adapter that have not yet had their RemoteViews loaded.
+     */
+    private class RemoteViewsFrameLayoutRefSet {
+        private HashMap<Integer, LinkedList<RemoteViewsFrameLayout>> mReferences;
+
+        public RemoteViewsFrameLayoutRefSet() {
+            mReferences = new HashMap<Integer, LinkedList<RemoteViewsFrameLayout>>();
+        }
+
+        /**
+         * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
+         */
+        public void add(int position, RemoteViewsFrameLayout layout) {
+            final Integer pos = position;
+            LinkedList<RemoteViewsFrameLayout> refs;
+
+            // Create the list if necessary
+            if (mReferences.containsKey(pos)) {
+                refs = mReferences.get(pos);
+            } else {
+                refs = new LinkedList<RemoteViewsFrameLayout>();
+                mReferences.put(pos, refs);
+            }
+
+            // Add the references to the list
+            refs.add(layout);
+        }
+
+        /**
+         * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
+         * the associated RemoteViews has loaded.
+         */
+        public void notifyOnRemoteViewsLoaded(int position, RemoteViews view, int typeId) {
+            if (view == null) return;
+
+            final Integer pos = position;
+            if (mReferences.containsKey(pos)) {
+                // Notify all the references for that position of the newly loaded RemoteViews
+                final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
+                for (final RemoteViewsFrameLayout ref : refs) {
+                    ref.onRemoteViewsLoaded(view);
+                }
+                refs.clear();
+
+                // Remove this set from the original mapping
+                mReferences.remove(pos);
+            }
+        }
+
+        /**
+         * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
+         */
+        public void clear() {
+            // We currently just clear the references, and leave all the previous layouts returned
+            // in their default state of the loading view.
+            mReferences.clear();
+        }
+    }
+
+    /**
+     * The meta-data associated with the cache in it's current state.
+     */
+    private class RemoteViewsMetaData {
+        int count;
+        int viewTypeCount;
+        boolean hasStableIds;
+        boolean isDataDirty;
+
+        // Used to determine how to construct loading views.  If a loading view is not specified
+        // by the user, then we try and load the first view, and use its height as the height for
+        // the default loading view.
+        RemoteViews mUserLoadingView;
+        RemoteViews mFirstView;
+        int mFirstViewHeight;
+
+        // A mapping from type id to a set of unique type ids
+        private Map<Integer, Integer> mTypeIdIndexMap;
+
+        public RemoteViewsMetaData() {
+            reset();
+        }
+
+        public void reset() {
+            count = 0;
+            // by default there is at least one dummy view type
+            viewTypeCount = 1;
+            hasStableIds = true;
+            isDataDirty = false;
+            mUserLoadingView = null;
+            mFirstView = null;
+            mFirstViewHeight = 0;
+            mTypeIdIndexMap = new HashMap<Integer, Integer>();
+        }
+
+        public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
+            mUserLoadingView = loadingView;
+            if (firstView != null) {
+                mFirstView = firstView;
+                mFirstViewHeight = -1;
+            }
+        }
+
+        public int getMappedViewType(int typeId) {
+            if (mTypeIdIndexMap.containsKey(typeId)) {
+                return mTypeIdIndexMap.get(typeId);
+            } else {
+                // We +1 because the loading view always has view type id of 0
+                int incrementalTypeId = mTypeIdIndexMap.size() + 1;
+                mTypeIdIndexMap.put(typeId, incrementalTypeId);
+                return incrementalTypeId;
+            }
+        }
+
+        private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
+                ViewGroup parent) {
+            // Create and return a new FrameLayout, and setup the references for this position
+            final Context context = parent.getContext();
+            RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
+
+            // Create a new loading view
+            synchronized (mCache) {
+                if (mUserLoadingView != null) {
+                    // A user-specified loading view
+                    View loadingView = mUserLoadingView.apply(parent.getContext(), parent);
+                    loadingView.setTag(new Integer(0));
+                    layout.addView(loadingView);
+                } else {
+                    // A default loading view
+                    // Use the size of the first row as a guide for the size of the loading view
+                    if (mFirstViewHeight < 0) {
+                        View firstView = mFirstView.apply(parent.getContext(), parent);
+                        firstView.measure(
+                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+                        mFirstViewHeight = firstView.getMeasuredHeight();
+                        mFirstView = null;
+                    }
+
+                    // Compose the loading view text
+                    TextView textView = new TextView(parent.getContext());
+                    textView.setText(com.android.internal.R.string.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);
+                    textView.setTag(new Integer(0));
+
+                    layout.addView(textView);
+                }
+            }
+
+            return layout;
+        }
+    }
+
+    /**
+     * The meta-data associated with a single item in the cache.
+     */
+    private class RemoteViewsIndexMetaData {
+        int typeId;
+        long itemId;
+
+        public RemoteViewsIndexMetaData(RemoteViews v, long itemId) {
+            set(v, itemId);
+        }
+
+        public void set(RemoteViews v, long id) {
+            itemId = id;
+            if (v != null)
+                typeId = v.getLayoutId();
+            else
+                typeId = 0;
+        }
+    }
+
+    /**
+     *
+     */
+    private class FixedSizeRemoteViewsCache {
+        private static final String TAG = "FixedSizeRemoteViewsCache";
+
+        // The meta data related to all the RemoteViews, ie. count, is stable, etc.
+        private RemoteViewsMetaData mMetaData;
+
+        // The cache/mapping of position to RemoteViewsMetaData.  This set is guaranteed to be
+        // greater than or equal to the set of RemoteViews.
+        // Note: The reason that we keep this separate from the RemoteViews cache below is that this
+        // we still need to be able to access the mapping of position to meta data, without keeping
+        // the heavy RemoteViews around.  The RemoteViews cache is trimmed to fixed constraints wrt.
+        // memory and size, but this metadata cache will retain information until the data at the
+        // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
+        private HashMap<Integer, RemoteViewsIndexMetaData> mIndexMetaData;
+
+        // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
+        // too much memory.
+        private HashMap<Integer, RemoteViews> mIndexRemoteViews;
+
+        // The set of indices that have been explicitly requested by the collection view
+        private HashSet<Integer> mRequestedIndices;
+
+        // The set of indices to load, including those explicitly requested, as well as those
+        // determined by the preloading algorithm to be prefetched
+        private HashSet<Integer> mLoadIndices;
+
+        // The lower and upper bounds of the preloaded range
+        private int mPreloadLowerBound;
+        private int mPreloadUpperBound;
+
+        // The bounds of this fixed cache, we will try and fill as many items into the cache up to
+        // the maxCount number of items, or the maxSize memory usage.
+        // The maxCountSlack is used to determine if a new position in the cache to be loaded is
+        // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
+        // preloaded.
+        private int mMaxCount;
+        private int mMaxCountSlack;
+        private static final float sMaxCountSlackPercent = 0.75f;
+        private static final int sMaxMemoryUsage = 1024 * 1024;
+
+        public FixedSizeRemoteViewsCache(int maxCacheSize) {
+            mMaxCount = maxCacheSize;
+            mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
+            mPreloadLowerBound = 0;
+            mPreloadUpperBound = -1;
+            mMetaData = new RemoteViewsMetaData();
+            mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
+            mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
+            mRequestedIndices = new HashSet<Integer>();
+            mLoadIndices = new HashSet<Integer>();
+        }
+
+        public void insert(int position, RemoteViews v, long itemId) {
+            // Trim the cache if we go beyond the count
+            if (mIndexRemoteViews.size() >= mMaxCount) {
+                mIndexRemoteViews.remove(getFarthestPositionFrom(position));
+            }
+
+            // Trim the cache if we go beyond the available memory size constraints
+            while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryUsage) {
+                // Note: This is currently the most naive mechanism for deciding what to prune when
+                // we hit the memory limit.  In the future, we may want to calculate which index to
+                // remove based on both its position as well as it's current memory usage, as well
+                // as whether it was directly requested vs. whether it was preloaded by our caching
+                // mechanism.
+                mIndexRemoteViews.remove(getFarthestPositionFrom(position));
+            }
+
+            // Update the metadata cache
+            if (mIndexMetaData.containsKey(position)) {
+                final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
+                metaData.set(v, itemId);
+            } else {
+                mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId));
+            }
+            mIndexRemoteViews.put(position, v);
+        }
+
+        public RemoteViewsMetaData getMetaData() {
+            return mMetaData;
+        }
+        public RemoteViews getRemoteViewsAt(int position) {
+            if (mIndexRemoteViews.containsKey(position)) {
+                return mIndexRemoteViews.get(position);
+            }
+            return null;
+        }
+        public RemoteViewsIndexMetaData getMetaDataAt(int position) {
+            if (mIndexMetaData.containsKey(position)) {
+                return mIndexMetaData.get(position);
+            }
+            return null;
+        }
+
+        private int getRemoteViewsBitmapMemoryUsage() {
+            // Calculate the memory usage of all the RemoteViews bitmaps being cached
+            int mem = 0;
+            for (Integer i : mIndexRemoteViews.keySet()) {
+                final RemoteViews v = mIndexRemoteViews.get(i);
+                mem += v.estimateBitmapMemoryUsage();
+            }
+            return mem;
+        }
+        private int getFarthestPositionFrom(int pos) {
+            // Find the index farthest away and remove that
+            int maxDist = 0;
+            int maxDistIndex = -1;
+            for (int i : mIndexRemoteViews.keySet()) {
+                int dist = Math.abs(i-pos);
+                if (dist > maxDist) {
+                    maxDistIndex = i;
+                    maxDist = dist;
+                }
+            }
+            return maxDistIndex;
+        }
+
+        public void queueRequestedPositionToLoad(int position) {
+            synchronized (mLoadIndices) {
+                mRequestedIndices.add(position);
+                mLoadIndices.add(position);
+            }
+        }
+        public void queuePositionsToBePreloadedFromRequestedPosition(int position) {
+            // Check if we need to preload any items
+            if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
+                int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
+                if (Math.abs(position - center) < mMaxCountSlack) {
+                    return;
+                }
+            }
+
+            int count = 0;
+            synchronized (mMetaData) {
+                count = mMetaData.count;
+            }
+            synchronized (mLoadIndices) {
+                mLoadIndices.clear();
+
+                // Add all the requested indices
+                mLoadIndices.addAll(mRequestedIndices);
+
+                // Add all the preload indices
+                int halfMaxCount = mMaxCount / 2;
+                mPreloadLowerBound = position - halfMaxCount;
+                mPreloadUpperBound = position + halfMaxCount;
+                int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
+                int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
+                for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
+                    mLoadIndices.add(i);
+                }
+
+                // But remove all the indices that have already been loaded and are cached
+                mLoadIndices.removeAll(mIndexRemoteViews.keySet());
+            }
+        }
+        public int getNextIndexToLoad() {
+            // We try and prioritize items that have been requested directly, instead
+            // of items that are loaded as a result of the caching mechanism
+            synchronized (mLoadIndices) {
+                // Prioritize requested indices to be loaded first
+                if (!mRequestedIndices.isEmpty()) {
+                    Integer i = mRequestedIndices.iterator().next();
+                    mRequestedIndices.remove(i);
+                    mLoadIndices.remove(i);
+                    return i.intValue();
+                }
+
+                // Otherwise, preload other indices as necessary
+                if (!mLoadIndices.isEmpty()) {
+                    Integer i = mLoadIndices.iterator().next();
+                    mLoadIndices.remove(i);
+                    return i.intValue();
+                }
+
+                return -1;
+            }
+        }
+
+        public boolean containsRemoteViewAt(int position) {
+            return mIndexRemoteViews.containsKey(position);
+        }
+        public boolean containsMetaDataAt(int position) {
+            return mIndexMetaData.containsKey(position);
+        }
+
+        public void reset() {
+            // Note: We do not try and reset the meta data, since that information is still used by
+            // collection views to validate it's own contents (and will be re-requested if the data
+            // is invalidated through the notifyDataSetChanged() flow).
+
+            mPreloadLowerBound = 0;
+            mPreloadUpperBound = -1;
+            mIndexRemoteViews.clear();
+            mIndexMetaData.clear();
+            synchronized (mLoadIndices) {
+                mRequestedIndices.clear();
+                mLoadIndices.clear();
+            }
+        }
+    }
+
+    public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
+        mContext = context;
+        mIntent = intent;
+        if (mIntent == null) {
+            throw new IllegalArgumentException("Non-null Intent must be specified.");
+        }
+        mRequestedViews = new RemoteViewsFrameLayoutRefSet();
+
+        // initialize the worker thread
+        mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
+        mWorkerThread.start();
+        mWorkerQueue = new Handler(mWorkerThread.getLooper());
+        mMainQueue = new Handler(Looper.myLooper());
+
+        // initialize the cache and the service connection on startup
+        mCache = new FixedSizeRemoteViewsCache(50);
+        mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
+        mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
+        requestBindService();
+    }
+
+    private void loadNextIndexInBackground() {
+        mWorkerQueue.post(new Runnable() {
+            @Override
+            public void run() {
+                // Get the next index to load
+                int position = -1;
+                synchronized (mCache) {
+                    position = mCache.getNextIndexToLoad();
+                }
+                if (position > -1) {
+                    // Load the item, and notify any existing RemoteViewsFrameLayouts
+                    updateRemoteViews(position);
+
+                    // Queue up for the next one to load
+                    loadNextIndexInBackground();
+                }
+            }
+        });
+    }
+
+    private void updateMetaData() {
+        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);
+                }
+                final RemoteViewsMetaData metaData = mCache.getMetaData();
+                synchronized (metaData) {
+                    metaData.hasStableIds = hasStableIds;
+                    metaData.viewTypeCount = viewTypeCount + 1;
+                    metaData.count = count;
+                    metaData.setLoadingViewTemplates(loadingView, firstView);
+                }
+            } catch (Exception e) {
+                // print the error
+                Log.e(TAG, "Error in requestMetaData(): " + e.getMessage());
+
+                // reset any members after the failed call
+                final RemoteViewsMetaData metaData = mCache.getMetaData();
+                synchronized (metaData) {
+                    metaData.reset();
+                }
+            }
+        }
+    }
+
+    private void updateRemoteViews(final int position) {
+        if (mServiceConnection.isConnected()) {
+            IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+
+            // Load the item information from the remote service
+            RemoteViews remoteViews = null;
+            long itemId = 0;
+            try {
+                remoteViews = factory.getViewAt(position);
+                itemId = factory.getItemId(position);
+            } catch (Exception e) {
+                // Print the error
+                Log.e(TAG, "Error in updateRemoteViewsInfo(" + position + "): " +
+                        e.getMessage());
+                e.printStackTrace();
+
+                // Return early to prevent additional work in re-centering the view cache, and
+                // swapping from the loading view
+                return;
+            }
+
+            synchronized (mCache) {
+                // Cache the RemoteViews we loaded
+                mCache.insert(position, remoteViews, itemId);
+
+                // Notify all the views that we have previously returned for this index that
+                // there is new data for it.
+                final RemoteViews rv = remoteViews;
+                final int typeId = mCache.getMetaDataAt(position).typeId;
+                mMainQueue.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
+                    }
+                });
+            }
+        }
+    }
+
+    public Intent getRemoteViewsServiceIntent() {
+        return mIntent;
+    }
+
+    public int getCount() {
+        requestBindService();
+        final RemoteViewsMetaData metaData = mCache.getMetaData();
+        synchronized (metaData) {
+            return metaData.count;
+        }
+    }
+
+    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();
+        synchronized (mCache) {
+            if (mCache.containsMetaDataAt(position)) {
+                return mCache.getMetaDataAt(position).itemId;
+            }
+            return 0;
+        }
+    }
+
+    public int getItemViewType(int position) {
+        requestBindService();
+        int typeId = 0;
+        synchronized (mCache) {
+            if (mCache.containsMetaDataAt(position)) {
+                typeId = mCache.getMetaDataAt(position).typeId;
+            } else {
+                return 0;
+            }
+        }
+
+        final RemoteViewsMetaData metaData = mCache.getMetaData();
+        synchronized (metaData) {
+            return metaData.getMappedViewType(typeId);
+        }
+    }
+
+    /**
+     * Returns the item type id for the specified convert view.  Returns -1 if the convert view
+     * is invalid.
+     */
+    private int getConvertViewTypeId(View convertView) {
+        int typeId = -1;
+        if (convertView != null && convertView.getTag() != null) {
+            typeId = (Integer) convertView.getTag();
+        }
+        return typeId;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        requestBindService();
+        if (mServiceConnection.isConnected()) {
+            // "Request" an index so that we can queue it for loading, initiate subsequent
+            // preloading, etc.
+            synchronized (mCache) {
+                // Queue up other indices to be preloaded based on this position
+                mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
+
+                RemoteViewsFrameLayout layout = (RemoteViewsFrameLayout) convertView;
+                View convertViewChild = null;
+                int convertViewTypeId = 0;
+                if (convertView != null) {
+                    convertViewChild = layout.getChildAt(0);
+                    convertViewTypeId = getConvertViewTypeId(convertViewChild);
+                }
+
+                // Second, we try and retrieve the RemoteViews from the cache, returning a loading
+                // view and queueing it to be loaded if it has not already been loaded.
+                if (mCache.containsRemoteViewAt(position)) {
+                    Context context = parent.getContext();
+                    RemoteViews rv = mCache.getRemoteViewsAt(position);
+                    int typeId = mCache.getMetaDataAt(position).typeId;
+
+                    // Reuse the convert view where possible
+                    if (convertView != null) {
+                        if (convertViewTypeId == typeId) {
+                            rv.reapply(context, convertViewChild);
+                            return convertView;
+                        }
+                    }
+
+                    // Otherwise, create a new view to be returned
+                    View newView = rv.apply(context, parent);
+                    newView.setTag(new Integer(typeId));
+                    if (convertView != null) {
+                        layout.removeAllViews();
+                    } else {
+                        layout = new RemoteViewsFrameLayout(context);
+                    }
+                    layout.addView(newView);
+                    return layout;
+                } else {
+                    // If the cache does not have the RemoteViews at this position, then create a
+                    // loading view and queue the actual position to be loaded in the background
+                    RemoteViewsFrameLayout loadingView = null;
+                    final RemoteViewsMetaData metaData = mCache.getMetaData();
+                    synchronized (metaData) {
+                        loadingView = metaData.createLoadingView(position, convertView, parent);
+                    }
+
+                    mRequestedViews.add(position, loadingView);
+                    mCache.queueRequestedPositionToLoad(position);
+                    loadNextIndexInBackground();
+
+                    return loadingView;
+                }
+            }
+        }
+        return new View(parent.getContext());
+    }
+
+    public int getViewTypeCount() {
+        requestBindService();
+        final RemoteViewsMetaData metaData = mCache.getMetaData();
+        synchronized (metaData) {
+            return metaData.viewTypeCount;
+        }
+    }
+
+    public boolean hasStableIds() {
+        requestBindService();
+        final RemoteViewsMetaData metaData = mCache.getMetaData();
+        synchronized (metaData) {
+            return metaData.hasStableIds;
+        }
+    }
+
+    public boolean isEmpty() {
+        return getCount() <= 0;
+    }
+
+    public void notifyDataSetChanged() {
+        mWorkerQueue.post(new Runnable() {
+            @Override
+            public void run() {
+                // Complete the actual notifyDataSetChanged() call initiated earlier
+                if (mServiceConnection.isConnected()) {
+                    IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+                    try {
+                        factory.onDataSetChanged();
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+
+                        // Return early to prevent from further being notified (since nothing has
+                        // changed)
+                        return;
+                    }
+                }
+
+                // Flush the cache so that we can reload new items from the service
+                synchronized (mCache) {
+                    mCache.reset();
+                }
+
+                // Re-request the new metadata (only after the notification to the factory)
+                updateMetaData();
+
+                // Propagate the notification back to the base adapter
+                mMainQueue.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        superNotifyDataSetChanged();
+                    }
+                });
+            }
+        });
+
+        // Note: we do not call super.notifyDataSetChanged() until the RemoteViewsFactory has had
+        // a chance to update itself and return new meta data associated with the new data.
+    }
+
+    private void superNotifyDataSetChanged() {
+        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();
+    }
+}
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
new file mode 100644
index 0000000..16126aa
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -0,0 +1,167 @@
+/*
+ * 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.FilterComparison, 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.
+     * 
+     * @see android.widget.Adapter
+     * @see android.appwidget.AppWidgetManager
+     */
+    public interface RemoteViewsFactory {
+        /**
+         * Called when your factory is first constructed. The same factory may be shared across
+         * multiple RemoteViewAdapters depending on the intent passed.
+         */
+        public void onCreate();
+        /**
+         * Called when notifyDataSetChanged() is triggered on the remote adapter. This allows a
+         * RemoteViewsFactory to respond to data changes by updating any internal references.
+         *
+         * @see android.appwidget.AppWidgetManager#notifyAppWidgetViewDataChanged(int[], int)
+         */
+        public void onDataSetChanged();
+        /**
+         * Called when the last RemoteViewsAdapter that is associated with this factory is
+         * unbound.
+         */
+        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 synchronized void onDataSetChanged() {
+            mFactory.onDataSetChanged();
+        }
+        public synchronized int getCount() {
+            return mFactory.getCount();
+        }
+        public synchronized RemoteViews getViewAt(int position) {
+            RemoteViews rv = mFactory.getViewAt(position);
+            rv.setIsWidgetCollectionChild(true);
+            return rv;
+        }
+        public synchronized RemoteViews getLoadingView() {
+            return mFactory.getLoadingView();
+        }
+        public synchronized int getViewTypeCount() {
+            return mFactory.getViewTypeCount();
+        }
+        public synchronized long getItemId(int position) {
+            return mFactory.getItemId(position);
+        }
+        public synchronized boolean hasStableIds() {
+            return mFactory.hasStableIds();
+        }
+
+        private RemoteViewsFactory mFactory;
+    }
+
+    public RemoteViewsService() {
+        mRemoteViewFactories =
+                new HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>>();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        synchronized (mLock) {
+            // increment the reference count to the particular factory associated with this intent
+            Intent.FilterComparison fc = new Intent.FilterComparison(intent);
+            Pair<RemoteViewsFactory, Integer> factoryRef = null;
+            RemoteViewsFactory factory = null;
+            if (!mRemoteViewFactories.containsKey(fc)) {
+                factory = onGetViewFactory(intent);
+                factoryRef = new Pair<RemoteViewsFactory, Integer>(factory, 1);
+                mRemoteViewFactories.put(fc, factoryRef);
+                factory.onCreate();
+            } else {
+                Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(fc);
+                factory = oldFactoryRef.first;
+                int newRefCount = oldFactoryRef.second.intValue() + 1;
+                factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
+                mRemoteViewFactories.put(fc, factoryRef);
+            }
+            return new RemoteViewsFactoryAdapter(factory);
+        }
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        synchronized (mLock) {
+            Intent.FilterComparison fc = new Intent.FilterComparison(intent);
+            if (mRemoteViewFactories.containsKey(fc)) {
+                // this alleviates the user's responsibility of having to clear all factories
+                Pair<RemoteViewsFactory, Integer> oldFactoryRef =
+                        mRemoteViewFactories.get(fc);
+                int newRefCount = oldFactoryRef.second.intValue() - 1;
+                if (newRefCount <= 0) {
+                    oldFactoryRef.first.onDestroy();
+                    mRemoteViewFactories.remove(fc);
+                } else {
+                    Pair<RemoteViewsFactory, Integer> factoryRef =
+                            new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
+                    mRemoteViewFactories.put(fc, 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/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
index c9c217a..aee411e 100644
--- a/core/java/android/widget/ResourceCursorAdapter.java
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -36,9 +36,8 @@
     
     /**
      * Constructor.
-     * 
-     * @param context The context where the ListView associated with this
-     *            SimpleListItemFactory is running
+     *
+     * @param context The context where the ListView associated with this adapter is running
      * @param layout resource identifier of a layout file that defines the views
      *            for this list item.  Unless you override them later, this will
      *            define both the item views and the drop down views.
@@ -51,9 +50,8 @@
     
     /**
      * Constructor.
-     * 
-     * @param context The context where the ListView associated with this
-     *            SimpleListItemFactory is running
+     *
+     * @param context The context where the ListView associated with this adapter is running
      * @param layout resource identifier of a layout file that defines the views
      *            for this list item.  Unless you override them later, this will
      *            define both the item views and the drop down views.
@@ -69,6 +67,22 @@
     }
 
     /**
+     * Constructor.
+     *
+     * @param context The context where the ListView associated with this adapter is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     * @param c The cursor from which to get the data.
+     * @param flags flags used to determine the behavior of the adapter
+     */
+    public ResourceCursorAdapter(Context context, int layout, Cursor c, int flags) {
+        super(context, c, flags);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    /**
      * Inflates view(s) from the specified XML file.
      * 
      * @see android.widget.CursorAdapter#newView(android.content.Context,
diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java
index 3b113ae..93a1179 100644
--- a/core/java/android/widget/ScrollBarDrawable.java
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -111,8 +111,7 @@
         }
 
         Rect r = getBounds();
-        if (canvas.quickReject(r.left, r.top, r.right, r.bottom, 
-                Canvas.EdgeType.AA)) {
+        if (canvas.quickReject(r.left, r.top, r.right, r.bottom, Canvas.EdgeType.AA)) {
             return;
         }
         if (drawTrack) {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 8472d9c..eb527eb 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -19,11 +19,8 @@
 import com.android.internal.R;
 
 import android.content.Context;
-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.util.AttributeSet;
 import android.view.FocusFinder;
 import android.view.KeyEvent;
@@ -52,6 +49,8 @@
  * within a larger container.
  * 
  * <p>ScrollView only supports vertical scrolling.
+ *
+ * @attr ref android.R.styleable#ScrollView_fillViewport
  */
 public class ScrollView extends FrameLayout {
     static final int ANIMATED_SCROLL_GAP = 250;
@@ -62,9 +61,7 @@
     private long mLastScroll;
 
     private final Rect mTempRect = new Rect();
-    private OverScroller mScroller;
-    private EdgeGlow mEdgeGlowTop;
-    private EdgeGlow mEdgeGlowBottom;
+    private Scroller mScroller;
 
     /**
      * Flag to indicate that we are moving focus ourselves. This is so the
@@ -118,9 +115,6 @@
     private int mMinimumVelocity;
     private int mMaximumVelocity;
     
-    private int mOverscrollDistance;
-    private int mOverflingDistance;
-
     /**
      * ID of the active pointer. This is used to retain consistency during
      * drags/flings if multiple pointers are used.
@@ -193,7 +187,7 @@
 
 
     private void initScrollView() {
-        mScroller = new OverScroller(getContext());
+        mScroller = new Scroller(getContext());
         setFocusable(true);
         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
         setWillNotDraw(false);
@@ -201,8 +195,6 @@
         mTouchSlop = configuration.getScaledTouchSlop();
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
-        mOverscrollDistance = configuration.getScaledOverscrollDistance();
-        mOverflingDistance = configuration.getScaledOverflingDistance();
     }
 
     @Override
@@ -257,6 +249,8 @@
      * Indicates whether this ScrollView's content is stretched to fill the viewport.
      *
      * @return True if the content fills the viewport, false otherwise.
+     * 
+     * @attr ref android.R.styleable#ScrollView_fillViewport
      */
     public boolean isFillViewport() {
         return mFillViewport;
@@ -268,6 +262,8 @@
      *
      * @param fillViewport True to stretch the content's height to the viewport's
      *        boundaries, false otherwise.
+     * 
+     * @attr ref android.R.styleable#ScrollView_fillViewport
      */
     public void setFillViewport(boolean fillViewport) {
         if (fillViewport != mFillViewport) {
@@ -463,9 +459,6 @@
                 /* Release the drag */
                 mIsBeingDragged = false;
                 mActivePointerId = INVALID_POINTER;
-                if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
-                    invalidate();
-                }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
                 onSecondaryPointerUp(ev);
@@ -498,7 +491,9 @@
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 final float y = ev.getY();
-                mIsBeingDragged = true;
+                if (!(mIsBeingDragged = inChild((int) ev.getX(), (int) y))) {
+                    return false;
+                }
                 
                 /*
                  * If being flinged and user touches, stop the fling. isFinished
@@ -521,26 +516,7 @@
                     final int deltaY = (int) (mLastMotionY - y);
                     mLastMotionY = y;
 
-                    final int oldX = mScrollX;
-                    final int oldY = mScrollY;
-                    final int range = getScrollRange();
-                    if (overscrollBy(0, deltaY, 0, mScrollY, 0, range,
-                            0, mOverscrollDistance, true)) {
-                        // Break our velocity if we hit a scroll barrier.
-                        mVelocityTracker.clear();
-                    }
-                    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
-
-                    final int overscrollMode = getOverscrollMode();
-                    if (overscrollMode == OVERSCROLL_ALWAYS ||
-                            (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) {
-                        final int pulledToY = oldY + deltaY;
-                        if (pulledToY < 0) {
-                            mEdgeGlowTop.onPull((float) deltaY / getHeight());
-                        } else if (pulledToY > range) {
-                            mEdgeGlowBottom.onPull((float) deltaY / getHeight());
-                        }
-                    }
+                    scrollBy(0, deltaY);
                 }
                 break;
             case MotionEvent.ACTION_UP: 
@@ -549,15 +525,8 @@
                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                     int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
 
-                    if (getChildCount() > 0) {
-                        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
-                            fling(-initialVelocity);
-                        } else {
-                            final int bottom = getScrollRange();
-                            if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, bottom)) {
-                                invalidate();
-                            }
-                        }
+                    if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
+                        fling(-initialVelocity);
                     }
 
                     mActivePointerId = INVALID_POINTER;
@@ -567,27 +536,16 @@
                         mVelocityTracker.recycle();
                         mVelocityTracker = null;
                     }
-                    if (mEdgeGlowTop != null) {
-                        mEdgeGlowTop.onRelease();
-                        mEdgeGlowBottom.onRelease();
-                    }
                 }
                 break;
             case MotionEvent.ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
-                    if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
-                        invalidate();
-                    }
                     mActivePointerId = INVALID_POINTER;
                     mIsBeingDragged = false;
                     if (mVelocityTracker != null) {
                         mVelocityTracker.recycle();
                         mVelocityTracker = null;
                     }
-                    if (mEdgeGlowTop != null) {
-                        mEdgeGlowTop.onRelease();
-                        mEdgeGlowBottom.onRelease();
-                    }
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -614,32 +572,6 @@
         }
     }
     
-    @Override
-    protected void onOverscrolled(int scrollX, int scrollY,
-            boolean clampedX, boolean clampedY) {
-        // Treat animating scrolls differently; see #computeScroll() for why.
-        if (!mScroller.isFinished()) {
-            mScrollX = scrollX;
-            mScrollY = scrollY;
-            if (clampedY) {
-                mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
-            }
-        } else {
-            super.scrollTo(scrollX, scrollY);
-        }
-        awakenScrollBars();
-    }
-
-    private int getScrollRange() {
-        int scrollRange = 0;
-        if (getChildCount() > 0) {
-            View child = getChildAt(0);
-            scrollRange = Math.max(0,
-                    child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
-        }
-        return scrollRange;
-    }
-
     /**
      * <p>
      * Finds the next focusable component that fits in this View's bounds
@@ -1016,16 +948,7 @@
             return contentHeight;
         }
         
-        int scrollRange = getChildAt(0).getBottom();
-        final int scrollY = mScrollY;
-        final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
-        if (scrollY < 0) {
-            scrollRange -= scrollY;
-        } else if (scrollY > overscrollBottom) {
-            scrollRange += scrollY - overscrollBottom;
-        }
-
-        return scrollRange;
+        return getChildAt(0).getBottom();
     }
 
     @Override
@@ -1086,20 +1009,14 @@
             int x = mScroller.getCurrX();
             int y = mScroller.getCurrY();
 
-            if (oldX != x || oldY != y) {
-                overscrollBy(x - oldX, y - oldY, oldX, oldY, 0, getScrollRange(),
-                        0, mOverflingDistance, false);
-                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
-
-                final int range = getScrollRange();
-                final int overscrollMode = getOverscrollMode();
-                if (overscrollMode == OVERSCROLL_ALWAYS ||
-                        (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) {
-                    if (y < 0 && oldY >= 0) {
-                        mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
-                    } else if (y > range && oldY <= range) {
-                        mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
-                    }
+            if (getChildCount() > 0) {
+                View child = getChildAt(0);
+                x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+                y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+                if (x != oldX || y != oldY) {
+                    mScrollX = x;
+                    mScrollY = y;
+                    onScrollChanged(x, y, oldX, oldY);
                 }
             }
             awakenScrollBars();
@@ -1337,7 +1254,7 @@
             int bottom = getChildAt(0).getHeight();
     
             mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, 
-                    Math.max(0, bottom - height), 0, height/2);
+                    Math.max(0, bottom - height));
     
             final boolean movingDown = velocityY > 0;
     
@@ -1375,55 +1292,6 @@
         }
     }
 
-    @Override
-    public void setOverscrollMode(int mode) {
-        if (mode != OVERSCROLL_NEVER) {
-            if (mEdgeGlowTop == null) {
-                final Resources res = getContext().getResources();
-                final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
-                final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
-                mEdgeGlowTop = new EdgeGlow(edge, glow);
-                mEdgeGlowBottom = new EdgeGlow(edge, glow);
-            }
-        } else {
-            mEdgeGlowTop = null;
-            mEdgeGlowBottom = null;
-        }
-        super.setOverscrollMode(mode);
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-        if (mEdgeGlowTop != null) {
-            final int scrollY = mScrollY;
-            if (!mEdgeGlowTop.isFinished()) {
-                final int restoreCount = canvas.save();
-                final int width = getWidth();
-
-                canvas.translate(-width / 2, Math.min(0, scrollY));
-                mEdgeGlowTop.setSize(width * 2, getHeight());
-                if (mEdgeGlowTop.draw(canvas)) {
-                    invalidate();
-                }
-                canvas.restoreToCount(restoreCount);
-            }
-            if (!mEdgeGlowBottom.isFinished()) {
-                final int restoreCount = canvas.save();
-                final int width = getWidth();
-                final int height = getHeight();
-
-                canvas.translate(-width / 2, Math.max(getScrollRange(), scrollY) + height);
-                canvas.rotate(180, width, 0);
-                mEdgeGlowBottom.setSize(width * 2, height);
-                if (mEdgeGlowBottom.draw(canvas)) {
-                    invalidate();
-                }
-                canvas.restoreToCount(restoreCount);
-            }
-        }
-    }
-
     private int clamp(int n, int my, int child) {
         if (my >= child || n < 0) {
             /* my >= child is this case:
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index 23f72b6..4cb0839 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -50,6 +50,8 @@
     private float mDurationReciprocal;
     private float mDeltaX;
     private float mDeltaY;
+    private float mViscousFluidScale;
+    private float mViscousFluidNormalize;
     private boolean mFinished;
     private Interpolator mInterpolator;
 
@@ -63,17 +65,6 @@
 
     private final float mDeceleration;
 
-    private static float sViscousFluidScale;
-    private static float sViscousFluidNormalize;
-
-    static {
-        // This controls the viscous fluid effect (how much of it)
-        sViscousFluidScale = 8.0f;
-        // must be set to 1.0 (used in viscousFluid())
-        sViscousFluidNormalize = 1.0f;
-        sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
-    }
-
     /**
      * Create a Scroller with the default duration and interpolator.
      */
@@ -286,6 +277,11 @@
         mDeltaX = dx;
         mDeltaY = dy;
         mDurationReciprocal = 1.0f / (float) mDuration;
+        // This controls the viscous fluid effect (how much of it)
+        mViscousFluidScale = 8.0f;
+        // must be set to 1.0 (used in viscousFluid())
+        mViscousFluidNormalize = 1.0f;
+        mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
     }
 
     /**
@@ -343,9 +339,11 @@
         mFinalY = Math.max(mFinalY, mMinY);
     }
     
-    static float viscousFluid(float x)
+    
+    
+    private float viscousFluid(float x)
     {
-        x *= sViscousFluidScale;
+        x *= mViscousFluidScale;
         if (x < 1.0f) {
             x -= (1.0f - (float)Math.exp(-x));
         } else {
@@ -353,7 +351,7 @@
             x = 1.0f - (float)Math.exp(1.0f - x);
             x = start + x * (1.0f - start);
         }
-        x *= sViscousFluidNormalize;
+        x *= mViscousFluidNormalize;
         return x;
     }
     
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
new file mode 100644
index 0000000..dd67197
--- /dev/null
+++ b/core/java/android/widget/SearchView.java
@@ -0,0 +1,734 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 static android.widget.SuggestionsAdapter.getColumnString;
+
+import com.android.internal.R;
+
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.TextView.OnEditorActionListener;
+
+import java.util.WeakHashMap;
+
+/**
+ * Provides the user interface elements for the user to enter a search query and submit a
+ * request to a search provider. Shows a list of query suggestions or results, if
+ * available and allows the user to pick a suggestion or result to launch into.
+ */
+public class SearchView extends LinearLayout {
+
+    private static final boolean DBG = false;
+    private static final String LOG_TAG = "SearchView";
+
+    private OnQueryChangeListener mOnQueryChangeListener;
+    private OnCloseListener mOnCloseListener;
+
+    private boolean mIconifiedByDefault;
+    private boolean mIconified;
+    private CursorAdapter mSuggestionsAdapter;
+    private View mSearchButton;
+    private View mSubmitButton;
+    private View mCloseButton;
+    private View mSearchEditFrame;
+    private AutoCompleteTextView mQueryTextView;
+    private boolean mSubmitButtonEnabled;
+    private CharSequence mQueryHint;
+    private boolean mQueryRefinement;
+
+    private SearchableInfo mSearchable;
+
+    // A weak map of drawables we've gotten from other packages, so we don't load them
+    // more than once.
+    private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache =
+            new WeakHashMap<String, Drawable.ConstantState>();
+
+    /**
+     * Callbacks for changes to the query text.
+     */
+    public interface OnQueryChangeListener {
+
+        /**
+         * Called when the user submits the query. This could be due to a key press on the
+         * keyboard or due to pressing a submit button.
+         * The listener can override the standard behavior by returning true
+         * to indicate that it has handled the submit request. Otherwise return false to
+         * let the SearchView handle the submission by launching any associated intent.
+         *
+         * @param query the query text that is to be submitted
+         *
+         * @return true if the query has been handled by the listener, false to let the
+         * SearchView perform the default action.
+         */
+        boolean onSubmitQuery(String query);
+
+        /**
+         * Called when the query text is changed by the user.
+         *
+         * @param newText the new content of the query text field.
+         *
+         * @return false if the SearchView should perform the default action of showing any
+         * suggestions if available, true if the action was handled by the listener.
+         */
+        boolean onQueryTextChanged(String newText);
+    }
+
+    public interface OnCloseListener {
+
+        /**
+         * The user is attempting to close the SearchView.
+         *
+         * @return true if the listener wants to override the default behavior of clearing the
+         * text field and dismissing it, false otherwise.
+         */
+        boolean onClose();
+    }
+
+    public SearchView(Context context) {
+        this(context, null);
+    }
+
+    public SearchView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        LayoutInflater inflater = (LayoutInflater) context
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.search_view, this, true);
+
+        mSearchButton = findViewById(R.id.search_button);
+        mQueryTextView = (AutoCompleteTextView) findViewById(R.id.search_src_text);
+        mSearchEditFrame = findViewById(R.id.search_edit_frame);
+        mSubmitButton = findViewById(R.id.search_go_btn);
+        mCloseButton = findViewById(R.id.search_close_btn);
+
+        mSearchButton.setOnClickListener(mOnClickListener);
+        mCloseButton.setOnClickListener(mOnClickListener);
+        mSubmitButton.setOnClickListener(mOnClickListener);
+        mQueryTextView.addTextChangedListener(mTextWatcher);
+        mQueryTextView.setOnEditorActionListener(mOnEditorActionListener);
+        mQueryTextView.setOnItemClickListener(mOnItemClickListener);
+        mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchView, 0, 0);
+        setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, true));
+        a.recycle();
+
+        updateViewsVisibility(mIconifiedByDefault);
+    }
+
+    /**
+     * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used
+     * to display labels, hints, suggestions, create intents for launching search results screens
+     * and controlling other affordances such as a voice button.
+     *
+     * @param searchable a SearchableInfo can be retrieved from the SearchManager, for a specific
+     * activity or a global search provider.
+     */
+    public void setSearchableInfo(SearchableInfo searchable) {
+        mSearchable = searchable;
+        if (mSearchable != null) {
+            updateSearchAutoComplete();
+        }
+        updateViewsVisibility(mIconifiedByDefault);
+    }
+
+    /**
+     * Sets a listener for user actions within the SearchView.
+     *
+     * @param listener the listener object that receives callbacks when the user performs
+     * actions in the SearchView such as clicking on buttons or typing a query.
+     */
+    public void setOnQueryChangeListener(OnQueryChangeListener listener) {
+        mOnQueryChangeListener = listener;
+    }
+
+    /**
+     * Sets a listener to inform when the user closes the SearchView.
+     *
+     * @param listener the listener to call when the user closes the SearchView.
+     */
+    public void setOnCloseListener(OnCloseListener listener) {
+        mOnCloseListener = listener;
+    }
+
+    /**
+     * Sets a query string in the text field and optionally submits the query as well.
+     *
+     * @param query the query string. This replaces any query text already present in the
+     * text field.
+     * @param submit whether to submit the query right now or only update the contents of
+     * text field.
+     */
+    public void setQuery(CharSequence query, boolean submit) {
+        mQueryTextView.setText(query);
+        // If the query is not empty and submit is requested, submit the query
+        if (submit && !TextUtils.isEmpty(query)) {
+            onSubmitQuery();
+        }
+    }
+
+    /**
+     * Sets the hint text to display in the query text field. This overrides any hint specified
+     * in the SearchableInfo.
+     *
+     * @param hint the hint text to display
+     */
+    public void setQueryHint(CharSequence hint) {
+        mQueryHint = hint;
+        updateQueryHint();
+    }
+
+    /**
+     * Sets the default or resting state of the search field. If true, a single search icon is
+     * shown by default and expands to show the text field and other buttons when pressed. Also,
+     * if the default state is iconified, then it collapses to that state when the close button
+     * is pressed. Changes to this property will take effect immediately.
+     *
+     * <p>The default value is false.</p>
+     *
+     * @param iconified whether the search field should be iconified by default
+     */
+    public void setIconifiedByDefault(boolean iconified) {
+        mIconifiedByDefault = iconified;
+        updateViewsVisibility(iconified);
+    }
+
+    /**
+     * Returns the default iconified state of the search field.
+     * @return
+     */
+    public boolean isIconfiedByDefault() {
+        return mIconifiedByDefault;
+    }
+
+    /**
+     * Iconifies or expands the SearchView. Any query text is cleared when iconified. This is
+     * a temporary state and does not override the default iconified state set by
+     * {@link #setIconifiedByDefault(boolean)}. If the default state is iconified, then
+     * a false here will only be valid until the user closes the field. And if the default
+     * state is expanded, then a true here will only clear the text field and not close it.
+     *
+     * @param iconify a true value will collapse the SearchView to an icon, while a false will
+     * expand it.
+     */
+    public void setIconified(boolean iconify) {
+        if (iconify) {
+            onCloseClicked();
+        } else {
+            onSearchClicked();
+        }
+    }
+
+    /**
+     * Returns the current iconified state of the SearchView.
+     *
+     * @return true if the SearchView is currently iconified, false if the search field is
+     * fully visible.
+     */
+    public boolean isIconified() {
+        return mIconified;
+    }
+
+    /**
+     * Enables showing a submit button when the query is non-empty. In cases where the SearchView
+     * is being used to filter the contents of the current activity and doesn't launch a separate
+     * results activity, then the submit button should be disabled.
+     *
+     * @param enabled true to show a submit button for submitting queries, false if a submit
+     * button is not required.
+     */
+    public void setSubmitButtonEnabled(boolean enabled) {
+        mSubmitButton.setVisibility(enabled ? VISIBLE : GONE);
+        mSubmitButtonEnabled = enabled;
+    }
+
+    /**
+     * Returns whether the submit button is enabled when necessary or never displayed.
+     *
+     * @return whether the submit button is enabled automatically when necessary
+     */
+    public boolean isSubmitButtonEnabled() {
+        return mSubmitButtonEnabled;
+    }
+
+    /**
+     * Specifies if a query refinement button should be displayed alongside each suggestion
+     * or if it should depend on the flags set in the individual items retrieved from the
+     * suggestions provider. Clicking on the query refinement button will replace the text
+     * in the query text field with the text from the suggestion. This flag only takes effect
+     * if a SearchableInfo has been specified with {@link #setSearchableInfo(SearchableInfo)}
+     * and not when using a custom adapter.
+     *
+     * @param enable true if all items should have a query refinement button, false if only
+     * those items that have a query refinement flag set should have the button.
+     *
+     * @see SearchManager#SUGGEST_COLUMN_FLAGS
+     * @see SearchManager#FLAG_QUERY_REFINEMENT
+     */
+    public void setQueryRefinementEnabled(boolean enable) {
+        mQueryRefinement = enable;
+        if (mSuggestionsAdapter instanceof SuggestionsAdapter) {
+            ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
+                    enable ? SuggestionsAdapter.REFINE_ALL : SuggestionsAdapter.REFINE_BY_ENTRY);
+        }
+    }
+
+    /**
+     * Returns whether query refinement is enabled for all items or only specific ones.
+     * @return true if enabled for all items, false otherwise.
+     */
+    public boolean isQueryRefinementEnabled() {
+        return mQueryRefinement;
+    }
+
+    /**
+     * You can set a custom adapter if you wish. Otherwise the default adapter is used to
+     * display the suggestions from the suggestions provider associated with the SearchableInfo.
+     *
+     * @see #setSearchableInfo(SearchableInfo)
+     */
+    public void setSuggestionsAdapter(CursorAdapter adapter) {
+        mSuggestionsAdapter = adapter;
+
+        mQueryTextView.setAdapter(mSuggestionsAdapter);
+    }
+
+    /**
+     * Returns the adapter used for suggestions, if any.
+     * @return the suggestions adapter
+     */
+    public CursorAdapter getSuggestionsAdapter() {
+        return mSuggestionsAdapter;
+    }
+
+    private void updateViewsVisibility(final boolean collapsed) {
+        mIconified = collapsed;
+        // Visibility of views that are visible when collapsed
+        final int visCollapsed = collapsed ? VISIBLE : GONE;
+        // Visibility of views that are visible when expanded
+        final int visExpanded = collapsed ? GONE : VISIBLE;
+
+        mSearchButton.setVisibility(visCollapsed);
+        mSubmitButton.setVisibility(mSubmitButtonEnabled ? visExpanded : GONE);
+        mSearchEditFrame.setVisibility(visExpanded);
+
+        setImeVisibility(!collapsed);
+    }
+
+    private void setImeVisibility(boolean visible) {
+        // don't mess with the soft input if we're not iconified by default
+        if (mIconifiedByDefault) {
+            InputMethodManager imm = (InputMethodManager)
+            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+
+            // We made sure the IME was displayed, so also make sure it is closed
+            // when we go away.
+            if (imm != null) {
+                if (visible) {
+                    imm.showSoftInputUnchecked(0, null);
+                } else {
+                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by the SuggestionsAdapter
+     * @hide
+     */
+    /* package */void onQueryRefine(CharSequence queryText) {
+        setQuery(queryText);
+    }
+
+    private final OnClickListener mOnClickListener = new OnClickListener() {
+
+        public void onClick(View v) {
+            if (v == mSearchButton) {
+                onSearchClicked();
+            } else if (v == mCloseButton) {
+                onCloseClicked();
+            } else if (v == mSubmitButton) {
+                onSubmitQuery();
+            }
+        }
+    };
+
+    /**
+     * Handles the key down event for dealing with action keys.
+     *
+     * @param keyCode This is the keycode of the typed key, and is the same value as
+     *        found in the KeyEvent parameter.
+     * @param event The complete event record for the typed key
+     *
+     * @return true if the event was handled here, or false if not.
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mSearchable == null) {
+            return false;
+        }
+
+        // if it's an action specified by the searchable activity, launch the
+        // entered query with the action key
+        SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+        if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
+            launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText()
+                    .toString());
+            return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void updateQueryHint() {
+        if (mQueryHint != null) {
+            mQueryTextView.setHint(mQueryHint);
+        } else if (mSearchable != null) {
+            CharSequence hint = null;
+            int hintId = mSearchable.getHintId();
+            if (hintId != 0) {
+                hint = getContext().getString(hintId);
+            }
+            if (hint != null) {
+                mQueryTextView.setHint(hint);
+            }
+        }
+    }
+
+    /**
+     * Updates the auto-complete text view.
+     */
+    private void updateSearchAutoComplete() {
+        // close any existing suggestions adapter
+        //closeSuggestionsAdapter();
+
+        mQueryTextView.setDropDownAnimationStyle(0); // no animation
+
+        // attach the suggestions adapter, if suggestions are available
+        // The existence of a suggestions authority is the proxy for "suggestions available here"
+        if (mSearchable.getSuggestAuthority() != null) {
+            mSuggestionsAdapter = new SuggestionsAdapter(getContext(),
+                    this, mSearchable, mOutsideDrawablesCache);
+            mQueryTextView.setAdapter(mSuggestionsAdapter);
+            ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
+                    mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
+                    : SuggestionsAdapter.REFINE_BY_ENTRY);
+        }
+    }
+
+    private final OnEditorActionListener mOnEditorActionListener = new OnEditorActionListener() {
+
+        /**
+         * Called when the input method default action key is pressed.
+         */
+        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+            onSubmitQuery();
+            return true;
+        }
+    };
+
+    private void onTextChanged(CharSequence newText) {
+        CharSequence text = mQueryTextView.getText();
+        boolean hasText = !TextUtils.isEmpty(text);
+        if (isSubmitButtonEnabled()) {
+            mSubmitButton.setVisibility(hasText ? VISIBLE : GONE);
+        }
+        if (mOnQueryChangeListener != null)
+            mOnQueryChangeListener.onQueryTextChanged(newText.toString());
+    }
+
+    private void onSubmitQuery() {
+        CharSequence query = mQueryTextView.getText();
+        if (!TextUtils.isEmpty(query)) {
+            if (mOnQueryChangeListener == null
+                    || !mOnQueryChangeListener.onSubmitQuery(query.toString())) {
+                if (mSearchable != null) {
+                    launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
+                }
+            }
+        }
+    }
+
+    private void onCloseClicked() {
+        if (mOnCloseListener == null || !mOnCloseListener.onClose()) {
+            mQueryTextView.setText("");
+            updateViewsVisibility(mIconifiedByDefault);
+        }
+    }
+
+    private void onSearchClicked() {
+        mQueryTextView.requestFocus();
+        updateViewsVisibility(false);
+    }
+
+    private final OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
+
+        /**
+         * Implements OnItemClickListener
+         */
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            if (DBG)
+                Log.d(LOG_TAG, "onItemClick() position " + position);
+            launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
+        }
+    };
+
+    private final OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
+
+        /**
+         * Implements OnItemSelectedListener
+         */
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            if (DBG)
+                Log.d(LOG_TAG, "onItemSelected() position " + position);
+            // A suggestion has been selected, rewrite the query if possible,
+            // otherwise the restore the original query.
+            rewriteQueryFromSuggestion(position);
+        }
+
+        /**
+         * Implements OnItemSelectedListener
+         */
+        public void onNothingSelected(AdapterView<?> parent) {
+            if (DBG)
+                Log.d(LOG_TAG, "onNothingSelected()");
+        }
+    };
+
+    /**
+     * Query rewriting.
+     */
+    private void rewriteQueryFromSuggestion(int position) {
+        CharSequence oldQuery = mQueryTextView.getText();
+        Cursor c = mSuggestionsAdapter.getCursor();
+        if (c == null) {
+            return;
+        }
+        if (c.moveToPosition(position)) {
+            // Get the new query from the suggestion.
+            CharSequence newQuery = mSuggestionsAdapter.convertToString(c);
+            if (newQuery != null) {
+                // The suggestion rewrites the query.
+                // Update the text field, without getting new suggestions.
+                setQuery(newQuery);
+            } else {
+                // The suggestion does not rewrite the query, restore the user's query.
+                setQuery(oldQuery);
+            }
+        } else {
+            // We got a bad position, restore the user's query.
+            setQuery(oldQuery);
+        }
+    }
+
+    /**
+     * Launches an intent based on a suggestion.
+     *
+     * @param position The index of the suggestion to create the intent from.
+     * @param actionKey The key code of the action key that was pressed,
+     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+     * @param actionMsg The message for the action key that was pressed,
+     *        or <code>null</code> if none.
+     * @return true if a successful launch, false if could not (e.g. bad position).
+     */
+    private boolean launchSuggestion(int position, int actionKey, String actionMsg) {
+        Cursor c = mSuggestionsAdapter.getCursor();
+        if ((c != null) && c.moveToPosition(position)) {
+
+            Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
+
+            // launch the intent
+            launchIntent(intent);
+
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Launches an intent, including any special intent handling.
+     */
+    private void launchIntent(Intent intent) {
+        if (intent == null) {
+            return;
+        }
+        try {
+            // If the intent was created from a suggestion, it will always have an explicit
+            // component here.
+            getContext().startActivity(intent);
+        } catch (RuntimeException ex) {
+            Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
+        }
+    }
+
+    /**
+     * Sets the text in the query box, without updating the suggestions.
+     */
+    private void setQuery(CharSequence query) {
+        mQueryTextView.setText(query, true);
+        // Move the cursor to the end
+        mQueryTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
+    }
+
+    private void launchQuerySearch(int actionKey, String actionMsg, String query) {
+        String action = Intent.ACTION_SEARCH;
+        Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
+        getContext().startActivity(intent);
+    }
+
+    /**
+     * Constructs an intent from the given information and the search dialog state.
+     *
+     * @param action Intent action.
+     * @param data Intent data, or <code>null</code>.
+     * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
+     * @param query Intent query, or <code>null</code>.
+     * @param actionKey The key code of the action key that was pressed,
+     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+     * @param actionMsg The message for the action key that was pressed,
+     *        or <code>null</code> if none.
+     * @param mode The search mode, one of the acceptable values for
+     *             {@link SearchManager#SEARCH_MODE}, or {@code null}.
+     * @return The intent.
+     */
+    private Intent createIntent(String action, Uri data, String extraData, String query,
+            int actionKey, String actionMsg) {
+        // Now build the Intent
+        Intent intent = new Intent(action);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // We need CLEAR_TOP to avoid reusing an old task that has other activities
+        // on top of the one we want. We don't want to do this in in-app search though,
+        // as it can be destructive to the activity stack.
+        if (data != null) {
+            intent.setData(data);
+        }
+        intent.putExtra(SearchManager.USER_QUERY, query);
+        if (query != null) {
+            intent.putExtra(SearchManager.QUERY, query);
+        }
+        if (extraData != null) {
+            intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
+        }
+        if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
+            intent.putExtra(SearchManager.ACTION_KEY, actionKey);
+            intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
+        }
+        intent.setComponent(mSearchable.getSearchActivity());
+        return intent;
+    }
+
+    /**
+     * When a particular suggestion has been selected, perform the various lookups required
+     * to use the suggestion.  This includes checking the cursor for suggestion-specific data,
+     * and/or falling back to the XML for defaults;  It also creates REST style Uri data when
+     * the suggestion includes a data id.
+     *
+     * @param c The suggestions cursor, moved to the row of the user's selection
+     * @param actionKey The key code of the action key that was pressed,
+     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+     * @param actionMsg The message for the action key that was pressed,
+     *        or <code>null</code> if none.
+     * @return An intent for the suggestion at the cursor's position.
+     */
+    private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) {
+        try {
+            // use specific action if supplied, or default action if supplied, or fixed default
+            String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
+
+            if (action == null) {
+                action = mSearchable.getSuggestIntentAction();
+            }
+            if (action == null) {
+                action = Intent.ACTION_SEARCH;
+            }
+
+            // use specific data if supplied, or default data if supplied
+            String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
+            if (data == null) {
+                data = mSearchable.getSuggestIntentData();
+            }
+            // then, if an ID was provided, append it.
+            if (data != null) {
+                String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
+                if (id != null) {
+                    data = data + "/" + Uri.encode(id);
+                }
+            }
+            Uri dataUri = (data == null) ? null : Uri.parse(data);
+
+            String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
+            String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
+
+            return createIntent(action, dataUri, extraData, query, actionKey, actionMsg);
+        } catch (RuntimeException e ) {
+            int rowNum;
+            try {                       // be really paranoid now
+                rowNum = c.getPosition();
+            } catch (RuntimeException e2 ) {
+                rowNum = -1;
+            }
+            Log.w(LOG_TAG, "Search Suggestions cursor at row " + rowNum +
+                            " returned exception" + e.toString());
+            return null;
+        }
+    }
+
+    /**
+     * Callback to watch the text field for empty/non-empty
+     */
+    private TextWatcher mTextWatcher = new TextWatcher() {
+
+        public void beforeTextChanged(CharSequence s, int start, int before, int after) { }
+
+        public void onTextChanged(CharSequence s, int start,
+                int before, int after) {
+            SearchView.this.onTextChanged(s);
+        }
+
+        public void afterTextChanged(Editable s) {
+        }
+    };
+
+    /*
+     * Avoid getting focus when searching for something to focus on.
+     * The user will have to touch the text view to get focus.
+     */
+    protected boolean onRequestFocusInDescendants(int direction,
+            Rect previouslyFocusedRect) {
+        return false;
+    }
+ }
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 8ddb06c..c0cbb19 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;
 
@@ -40,27 +41,151 @@
  */
 @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;
+    
+    /**
+     * Use the theme-supplied value to select the dropdown mode.
+     */
+    private static final int MODE_THEME = -1;
+    
+    private SpinnerPopup mPopup;
+    private DropDownAdapter mTempAdapter;
 
+    /**
+     * Construct a new spinner with the given context's theme.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     */
     public Spinner(Context context) {
         this(context, null);
     }
 
+    /**
+     * Construct a new spinner with the given context's theme and the supplied
+     * mode of displaying choices. <code>mode</code> may be one of
+     * {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param mode Constant describing how the user will select choices from the spinner.
+     * 
+     * @see #MODE_DIALOG
+     * @see #MODE_DROPDOWN
+     */
+    public Spinner(Context context, int mode) {
+        this(context, null, com.android.internal.R.attr.spinnerStyle, mode);
+    }
+
+    /**
+     * Construct a new spinner with the given context's theme and the supplied attribute set.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     */
     public Spinner(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.spinnerStyle);
     }
 
+    /**
+     * Construct a new spinner with the given context's theme, the supplied attribute set,
+     * and default style.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @param defStyle The default style to apply to this view. If 0, no style
+     *        will be applied (beyond what is included in the theme). This may
+     *        either be an attribute resource, whose value will be retrieved
+     *        from the current theme, or an explicit style resource.
+     */
     public Spinner(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle, MODE_THEME);
+    }
+
+    /**
+     * Construct a new spinner with the given context's theme, the supplied attribute set,
+     * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
+     * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @param defStyle The default style to apply to this view. If 0, no style
+     *        will be applied (beyond what is included in the theme). This may
+     *        either be an attribute resource, whose value will be retrieved
+     *        from the current theme, or an explicit style resource.
+     * @param mode Constant describing how the user will select choices from the spinner.
+     * 
+     * @see #MODE_DIALOG
+     * @see #MODE_DROPDOWN
+     */
+    public Spinner(Context context, AttributeSet attrs, int defStyle, int mode) {
         super(context, attrs, defStyle);
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.Spinner, defStyle, 0);
+
+        if (mode == MODE_THEME) {
+            mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG);
+        }
         
-        mPrompt = a.getString(com.android.internal.R.styleable.Spinner_prompt);
+        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
@@ -87,7 +212,6 @@
         
         if (mPopup != null && mPopup.isShowing()) {
             mPopup.dismiss();
-            mPopup = null;
         }
     }
 
@@ -197,8 +321,6 @@
         return child;
     }
 
-
-
     /**
      * Helper for makeAndAddView to set the position of a view
      * and fill out its layout paramters.
@@ -249,15 +371,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;
@@ -266,7 +383,6 @@
     public void onClick(DialogInterface dialog, int which) {
         setSelection(which);
         dialog.dismiss();
-        mPopup = null;
     }
 
     /**
@@ -274,7 +390,7 @@
      * @param prompt the prompt to set
      */
     public void setPrompt(CharSequence prompt) {
-        mPrompt = prompt;
+        mPopup.setPromptText(prompt);
     }
 
     /**
@@ -282,14 +398,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();
     }
     
     /**
@@ -387,4 +503,124 @@
             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);
+            }
+        }
+
+        @Override
+        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/StackView.java b/core/java/android/widget/StackView.java
new file mode 100644
index 0000000..f0954e2
--- /dev/null
+++ b/core/java/android/widget/StackView.java
@@ -0,0 +1,1014 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.animation.PropertyValuesHolder;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.TableMaskFilter;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.LinearInterpolator;
+import android.widget.RemoteViews.RemoteView;
+
+@RemoteView
+/**
+ * A view that displays its children in a stack and allows users to discretely swipe
+ * through the children.
+ */
+public class StackView extends AdapterViewAnimator {
+    private final String TAG = "StackView";
+
+    /**
+     * Default animation parameters
+     */
+    private final int DEFAULT_ANIMATION_DURATION = 400;
+    private final int MINIMUM_ANIMATION_DURATION = 50;
+
+    /**
+     * Parameters effecting the perspective visuals
+     */
+    private static float PERSPECTIVE_SHIFT_FACTOR = 0.12f;
+    private static float PERSPECTIVE_SCALE_FACTOR = 0.35f;
+
+    /**
+     * Represent the two possible stack modes, one where items slide up, and the other
+     * where items slide down. The perspective is also inverted between these two modes.
+     */
+    private static final int ITEMS_SLIDE_UP = 0;
+    private static final int ITEMS_SLIDE_DOWN = 1;
+
+    /**
+     * These specify the different gesture states
+     */
+    private static final int GESTURE_NONE = 0;
+    private static final int GESTURE_SLIDE_UP = 1;
+    private static final int GESTURE_SLIDE_DOWN = 2;
+
+    /**
+     * Specifies how far you need to swipe (up or down) before it
+     * will be consider a completed gesture when you lift your finger
+     */
+    private static final float SWIPE_THRESHOLD_RATIO = 0.35f;
+    private static final float SLIDE_UP_RATIO = 0.7f;
+
+    /**
+     * Sentinel value for no current active pointer.
+     * Used by {@link #mActivePointerId}.
+     */
+    private static final int INVALID_POINTER = -1;
+
+    /**
+     * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
+     */
+    private static final int NUM_ACTIVE_VIEWS = 5;
+
+    private static final int FRAME_PADDING = 4;
+
+    /**
+     * These variables are all related to the current state of touch interaction
+     * with the stack
+     */
+    private float mInitialY;
+    private float mInitialX;
+    private int mActivePointerId;
+    private int mYVelocity = 0;
+    private int mSwipeGestureType = GESTURE_NONE;
+    private int mSlideAmount;
+    private int mSwipeThreshold;
+    private int mTouchSlop;
+    private int mMaximumVelocity;
+    private VelocityTracker mVelocityTracker;
+
+    private static HolographicHelper sHolographicHelper;
+    private ImageView mHighlight;
+    private StackSlider mStackSlider;
+    private boolean mFirstLayoutHappened = false;
+    private ViewGroup mAncestorContainingAllChildren = null;
+    private int mAncestorHeight = 0;
+    private int mStackMode;
+    private int mFramePadding;
+
+    public StackView(Context context) {
+        super(context);
+        initStackView();
+    }
+
+    public StackView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initStackView();
+    }
+
+    private void initStackView() {
+        configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2);
+        setStaticTransformationsEnabled(true);
+        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+        mTouchSlop = configuration.getScaledTouchSlop();
+        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+        mActivePointerId = INVALID_POINTER;
+
+        mHighlight = new ImageView(getContext());
+        mHighlight.setLayoutParams(new LayoutParams(mHighlight));
+        addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
+        mStackSlider = new StackSlider();
+
+        if (sHolographicHelper == null) {
+            sHolographicHelper = new HolographicHelper(mContext);
+        }
+        setClipChildren(false);
+        setClipToPadding(false);
+
+        // This sets the form of the StackView, which is currently to have the perspective-shifted
+        // views above the active view, and have items slide down when sliding out. The opposite is
+        // available by using ITEMS_SLIDE_UP.
+        mStackMode = ITEMS_SLIDE_DOWN;
+
+        // This is a flag to indicate the the stack is loading for the first time
+        mWhichChild = -1;
+
+        // Adjust the frame padding based on the density, since the highlight changes based
+        // on the density
+        final float density = mContext.getResources().getDisplayMetrics().density;
+        mFramePadding = (int) Math.ceil(density * FRAME_PADDING);
+    }
+
+    /**
+     * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
+     */
+    void animateViewForTransition(int fromIndex, int toIndex, View view) {
+        if (fromIndex == -1 && toIndex == 0) {
+            // Fade item in
+            if (view.getAlpha() == 1) {
+                view.setAlpha(0);
+            }
+            view.setVisibility(VISIBLE);
+
+            ObjectAnimator<Float> fadeIn = new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION,
+                    view, "alpha", view.getAlpha(), 1.0f);
+            fadeIn.start();
+        } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) {
+            // Slide item in
+            view.setVisibility(VISIBLE);
+
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
+
+            StackSlider animationSlider = new StackSlider(mStackSlider);
+            PropertyValuesHolder<Float> slideInY =
+                    new PropertyValuesHolder<Float>("YProgress", 0.0f);
+            PropertyValuesHolder<Float> slideInX =
+                    new PropertyValuesHolder<Float>("XProgress", 0.0f);
+            ObjectAnimator pa = new ObjectAnimator(duration, animationSlider,
+                    slideInX, slideInY);
+            pa.setInterpolator(new LinearInterpolator());
+            pa.start();
+        } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
+            // Slide item out
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+
+            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
+
+            StackSlider animationSlider = new StackSlider(mStackSlider);
+            PropertyValuesHolder<Float> slideOutY =
+                    new PropertyValuesHolder<Float>("YProgress", 1.0f);
+            PropertyValuesHolder<Float> slideOutX =
+                    new PropertyValuesHolder<Float>("XProgress", 0.0f);
+            ObjectAnimator pa = new ObjectAnimator(duration, animationSlider,
+                   slideOutX, slideOutY);
+            pa.setInterpolator(new LinearInterpolator());
+            pa.start();
+        } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
+            // Make sure this view that is "waiting in the wings" is invisible
+            view.setAlpha(0.0f);
+            view.setVisibility(INVISIBLE);
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            lp.setVerticalOffset(-mSlideAmount);
+        } else if (toIndex == -1) {
+            // Fade item out
+            ObjectAnimator<Float> fadeOut = new ObjectAnimator<Float>
+                    (DEFAULT_ANIMATION_DURATION, view, "alpha", view.getAlpha(), 0.0f);
+            fadeOut.start();
+        }
+
+        // Implement the faked perspective
+        if (toIndex != -1) {
+            transformViewAtIndex(toIndex, view);
+        }
+    }
+
+    private void transformViewAtIndex(int index, View view) {
+        float maxPerpectiveShift = mMeasuredHeight * PERSPECTIVE_SHIFT_FACTOR;
+
+        if (index == mNumActiveViews -1) index--;
+
+        float r = (index * 1.0f) / (mNumActiveViews - 2);
+
+        float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
+        PropertyValuesHolder<Float> scaleX = new PropertyValuesHolder<Float>("scaleX", scale);
+        PropertyValuesHolder<Float> scaleY = new PropertyValuesHolder<Float>("scaleY", scale);
+
+        r = (float) Math.pow(r, 2);
+
+        int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
+        float perspectiveTranslation = -stackDirection * r * maxPerpectiveShift;
+        float scaleShiftCorrection = stackDirection * (1 - scale) *
+                (mMeasuredHeight * (1 - PERSPECTIVE_SHIFT_FACTOR) / 2.0f);
+        float transY = perspectiveTranslation + scaleShiftCorrection;
+
+        PropertyValuesHolder<Float> translationY =
+                new PropertyValuesHolder<Float>("translationY", transY);
+        ObjectAnimator pa = new ObjectAnimator(100, view, scaleX, scaleY, translationY);
+        pa.start();
+    }
+
+    private void updateChildTransforms() {
+        for (int i = 0; i < mNumActiveViews - 1; i++) {
+            View v = getViewAtRelativeIndex(i);
+            if (v != null) {
+                transformViewAtIndex(i, v);
+            }
+        }
+    }
+
+    @Override
+    FrameLayout getFrameForChild() {
+        FrameLayout fl = new FrameLayout(mContext);
+        fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding);
+        return fl;
+    }
+
+    /**
+     * Apply any necessary tranforms for the child that is being added.
+     */
+    void applyTransformForChildAtIndex(View child, int relativeIndex) {
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+    }
+
+    // TODO: right now, this code walks up the hierarchy as far as needed and disables clipping
+    // so that the stack's children can draw outside of the stack's bounds. This is fine within
+    // the context of widgets in the launcher, but is destructive in general, as the clipping
+    // values are not being reset. For this to be a full framework level widget, we will need
+    // framework level support for drawing outside of a parent's bounds.
+    private void disableParentalClipping() {
+        if (mAncestorContainingAllChildren != null) {
+            ViewGroup vg = this;
+            while (vg.getParent() != null && vg.getParent() instanceof ViewGroup) {
+                if (vg == mAncestorContainingAllChildren) break;
+                vg = (ViewGroup) vg.getParent();
+                vg.setClipChildren(false);
+                vg.setClipToPadding(false);
+            }
+        }
+    }
+
+    private void onLayout() {
+        if (!mFirstLayoutHappened) {
+            mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
+            updateChildTransforms();
+            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount);
+            mFirstLayoutHappened = true;
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        switch(action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                if (mActivePointerId == INVALID_POINTER) {
+                    mInitialX = ev.getX();
+                    mInitialY = ev.getY();
+                    mActivePointerId = ev.getPointerId(0);
+                }
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == INVALID_POINTER) {
+                    // no data for our primary pointer, this shouldn't happen, log it
+                    Log.d(TAG, "Error: No data for our primary pointer.");
+                    return false;
+                }
+                float newY = ev.getY(pointerIndex);
+                float deltaY = newY - mInitialY;
+
+                beginGestureIfNeeded(deltaY);
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP: {
+                onSecondaryPointerUp(ev);
+                break;
+            }
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL: {
+                mActivePointerId = INVALID_POINTER;
+                mSwipeGestureType = GESTURE_NONE;
+            }
+        }
+
+        return mSwipeGestureType != GESTURE_NONE;
+    }
+
+    private void beginGestureIfNeeded(float deltaY) {
+        if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
+            int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
+            cancelLongPress();
+            requestDisallowInterceptTouchEvent(true);
+
+            int activeIndex;
+            if (mStackMode == ITEMS_SLIDE_UP) {
+                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
+                        mNumActiveViews - 1 : mNumActiveViews - 2;
+            } else {
+                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
+                        mNumActiveViews - 2 : mNumActiveViews - 1;
+            }
+
+            if (mAdapter == null) return;
+
+            if (mLoopViews) {
+                mStackSlider.setMode(StackSlider.NORMAL_MODE);
+            } else if (mCurrentWindowStartUnbounded + activeIndex == 0) {
+                mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE);
+            } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount()) {
+                activeIndex--;
+                mStackSlider.setMode(StackSlider.END_OF_STACK_MODE);
+            } else {
+                mStackSlider.setMode(StackSlider.NORMAL_MODE);
+            }
+
+            View v = getViewAtRelativeIndex(activeIndex);
+            if (v == null) return;
+
+            mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
+            mHighlight.setRotation(v.getRotation());
+            mHighlight.setTranslationY(v.getTranslationY());
+            mHighlight.bringToFront();
+            v.bringToFront();
+            mStackSlider.setView(v);
+
+            if (swipeGestureType == GESTURE_SLIDE_DOWN)
+                v.setVisibility(VISIBLE);
+
+            // We only register this gesture if we've made it this far without a problem
+            mSwipeGestureType = swipeGestureType;
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        int pointerIndex = ev.findPointerIndex(mActivePointerId);
+        if (pointerIndex == INVALID_POINTER) {
+            // no data for our primary pointer, this shouldn't happen, log it
+            Log.d(TAG, "Error: No data for our primary pointer.");
+            return false;
+        }
+
+        float newY = ev.getY(pointerIndex);
+        float newX = ev.getX(pointerIndex);
+        float deltaY = newY - mInitialY;
+        float deltaX = newX - mInitialX;
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_MOVE: {
+                beginGestureIfNeeded(deltaY);
+
+                float rx = deltaX / (mSlideAmount * 1.0f);
+                if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
+                    float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
+                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
+                    mStackSlider.setYProgress(1 - r);
+                    mStackSlider.setXProgress(rx);
+                    return true;
+                } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
+                    float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
+                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
+                    mStackSlider.setYProgress(r);
+                    mStackSlider.setXProgress(rx);
+                    return true;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                handlePointerUp(ev);
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP: {
+                onSecondaryPointerUp(ev);
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL: {
+                mActivePointerId = INVALID_POINTER;
+                mSwipeGestureType = GESTURE_NONE;
+                break;
+            }
+        }
+        return true;
+    }
+
+    private final Rect touchRect = new Rect();
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int activePointerIndex = ev.getActionIndex();
+        final int pointerId = ev.getPointerId(activePointerIndex);
+        if (pointerId == mActivePointerId) {
+
+            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1
+                    : mNumActiveViews - 2;
+
+            View v = getViewAtRelativeIndex(activeViewIndex);
+            if (v == null) return;
+
+            // Our primary pointer has gone up -- let's see if we can find
+            // another pointer on the view. If so, then we should replace
+            // our primary pointer with this new pointer and adjust things
+            // so that the view doesn't jump
+            for (int index = 0; index < ev.getPointerCount(); index++) {
+                if (index != activePointerIndex) {
+
+                    float x = ev.getX(index);
+                    float y = ev.getY(index);
+
+                    touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
+                    if (touchRect.contains(Math.round(x), Math.round(y))) {
+                        float oldX = ev.getX(activePointerIndex);
+                        float oldY = ev.getY(activePointerIndex);
+
+                        // adjust our frame of reference to avoid a jump
+                        mInitialY += (y - oldY);
+                        mInitialX += (x - oldX);
+
+                        mActivePointerId = ev.getPointerId(index);
+                        if (mVelocityTracker != null) {
+                            mVelocityTracker.clear();
+                        }
+                        // ok, we're good, we found a new pointer which is touching the active view
+                        return;
+                    }
+                }
+            }
+            // if we made it this far, it means we didn't find a satisfactory new pointer :(,
+            // so end the gesture
+            handlePointerUp(ev);
+        }
+    }
+
+    private void handlePointerUp(MotionEvent ev) {
+        int pointerIndex = ev.findPointerIndex(mActivePointerId);
+        float newY = ev.getY(pointerIndex);
+        int deltaY = (int) (newY - mInitialY);
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+            mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
+        }
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+
+        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
+                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
+            // Swipe threshold exceeded, swipe down
+            if (mStackMode == ITEMS_SLIDE_UP) {
+                showNext();
+            } else {
+                showPrevious();
+            }
+            mHighlight.bringToFront();
+        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
+                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
+            // Swipe threshold exceeded, swipe up
+            if (mStackMode == ITEMS_SLIDE_UP) {
+                showPrevious();
+            } else {
+                showNext();
+            }
+
+            mHighlight.bringToFront();
+        } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
+            // Didn't swipe up far enough, snap back down
+            int duration;
+            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
+            if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
+                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
+            } else {
+                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
+            }
+
+            StackSlider animationSlider = new StackSlider(mStackSlider);
+            PropertyValuesHolder<Float> snapBackY =
+                    new PropertyValuesHolder<Float>("YProgress", finalYProgress);
+            PropertyValuesHolder<Float> snapBackX =
+                    new PropertyValuesHolder<Float>("XProgress", 0.0f);
+            ObjectAnimator pa = new ObjectAnimator(duration, animationSlider,
+                    snapBackX, snapBackY);
+            pa.setInterpolator(new LinearInterpolator());
+            pa.start();
+        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
+            // Didn't swipe down far enough, snap back up
+            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
+            int duration;
+            if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
+                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
+            } else {
+                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
+            }
+
+            StackSlider animationSlider = new StackSlider(mStackSlider);
+            PropertyValuesHolder<Float> snapBackY =
+                    new PropertyValuesHolder<Float>("YProgress", finalYProgress);
+            PropertyValuesHolder<Float> snapBackX =
+                    new PropertyValuesHolder<Float>("XProgress", 0.0f);
+            ObjectAnimator pa = new ObjectAnimator(duration, animationSlider,
+                    snapBackX, snapBackY);
+            pa.start();
+        }
+
+        mActivePointerId = INVALID_POINTER;
+        mSwipeGestureType = GESTURE_NONE;
+    }
+
+    private class StackSlider {
+        View mView;
+        float mYProgress;
+        float mXProgress;
+
+        static final int NORMAL_MODE = 0;
+        static final int BEGINNING_OF_STACK_MODE = 1;
+        static final int END_OF_STACK_MODE = 2;
+
+        int mMode = NORMAL_MODE;
+
+        public StackSlider() {
+        }
+
+        public StackSlider(StackSlider copy) {
+            mView = copy.mView;
+            mYProgress = copy.mYProgress;
+            mXProgress = copy.mXProgress;
+            mMode = copy.mMode;
+        }
+
+        private float cubic(float r) {
+            return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
+        }
+
+        private float highlightAlphaInterpolator(float r) {
+            float pivot = 0.4f;
+            if (r < pivot) {
+                return 0.85f * cubic(r / pivot);
+            } else {
+                return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
+            }
+        }
+
+        private float viewAlphaInterpolator(float r) {
+            float pivot = 0.3f;
+            if (r > pivot) {
+                return (r - pivot) / (1 - pivot);
+            } else {
+                return 0;
+            }
+        }
+
+        private float rotationInterpolator(float r) {
+            float pivot = 0.2f;
+            if (r < pivot) {
+                return 0;
+            } else {
+                return (r - pivot) / (1 - pivot);
+            }
+        }
+
+        void setView(View v) {
+            mView = v;
+        }
+
+        public void setYProgress(float r) {
+            // enforce r between 0 and 1
+            r = Math.min(1.0f, r);
+            r = Math.max(0, r);
+
+            mYProgress = r;
+            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
+            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
+
+            int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
+
+            switch (mMode) {
+                case NORMAL_MODE:
+                    viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
+                    highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
+                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
+
+                    float alpha = viewAlphaInterpolator(1 - r);
+
+                    // We make sure that views which can't be seen (have 0 alpha) are also invisible
+                    // so that they don't interfere with click events.
+                    if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
+                        mView.setVisibility(VISIBLE);
+                    } else if (alpha == 0 && mView.getAlpha() != 0
+                            && mView.getVisibility() == VISIBLE) {
+                        mView.setVisibility(INVISIBLE);
+                    }
+
+                    mView.setAlpha(alpha);
+                    mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
+                    mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
+                    break;
+                case BEGINNING_OF_STACK_MODE:
+                    r = r * 0.2f;
+                    viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
+                    highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
+                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
+                    break;
+                case END_OF_STACK_MODE:
+                    r = (1-r) * 0.2f;
+                    viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
+                    highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
+                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
+                    break;
+            }
+        }
+
+        public void setXProgress(float r) {
+            // enforce r between 0 and 1
+            r = Math.min(2.0f, r);
+            r = Math.max(-2.0f, r);
+
+            mXProgress = r;
+
+            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
+            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
+
+            r *= 0.2f;
+            viewLp.setHorizontalOffset(Math.round(r * mSlideAmount));
+            highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount));
+        }
+
+        void setMode(int mode) {
+            mMode = mode;
+        }
+
+        float getDurationForNeutralPosition() {
+            return getDuration(false, 0);
+        }
+
+        float getDurationForOffscreenPosition() {
+            return getDuration(true, 0);
+        }
+
+        float getDurationForNeutralPosition(float velocity) {
+            return getDuration(false, velocity);
+        }
+
+        float getDurationForOffscreenPosition(float velocity) {
+            return getDuration(true, velocity);
+        }
+
+        private float getDuration(boolean invert, float velocity) {
+            if (mView != null) {
+                final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
+
+                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
+                        Math.pow(viewLp.verticalOffset, 2));
+                float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) +
+                        Math.pow(0.4f * mSlideAmount, 2));
+
+                if (velocity == 0) {
+                    return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
+                } else {
+                    float duration = invert ? d / Math.abs(velocity) :
+                            (maxd - d) / Math.abs(velocity);
+                    if (duration < MINIMUM_ANIMATION_DURATION ||
+                            duration > DEFAULT_ANIMATION_DURATION) {
+                        return getDuration(invert, 0);
+                    } else {
+                        return duration;
+                    }
+                }
+            }
+            return 0;
+        }
+
+        public float getYProgress() {
+            return mYProgress;
+        }
+
+        public float getXProgress() {
+            return mXProgress;
+        }
+    }
+
+    @Override
+    public void onRemoteAdapterConnected() {
+        super.onRemoteAdapterConnected();
+        // On first run, we want to set the stack to the end.
+        if (mAdapter != null && mWhichChild == -1) {
+            mWhichChild = mAdapter.getCount() - 1;
+        }
+        if (mWhichChild >= 0) {
+            setDisplayedChild(mWhichChild);
+        }
+    }
+
+    LayoutParams createOrReuseLayoutParams(View v) {
+        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
+        if (currentLp instanceof LayoutParams) {
+            LayoutParams lp = (LayoutParams) currentLp;
+            lp.setHorizontalOffset(0);
+            lp.setVerticalOffset(0);
+            lp.width = 0;
+            lp.width = 0;
+            return lp;
+        }
+        return new LayoutParams(v);
+    }
+
+    @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);
+            refreshChildren();
+        }
+
+        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();
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
+                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
+
+        }
+
+        mDataChanged = false;
+        onLayout();
+    }
+
+    private void measureChildren() {
+        final int count = getChildCount();
+        final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+        final int childHeight = Math.round(mMeasuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR))
+                - mPaddingTop - mPaddingBottom;
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
+
+        // We need to deal with the case where our parent hasn't told us how
+        // big we should be. In this case we should
+        float factor = 1/(1 - PERSPECTIVE_SHIFT_FACTOR);
+        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
+            heightSpecSize = haveChildRefSize ?
+                    Math.round(mReferenceChildHeight * (1 + factor)) +
+                    mPaddingTop + mPaddingBottom : 0;
+        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+            heightSpecSize = haveChildRefSize ? Math.min(
+                    Math.round(mReferenceChildHeight * (1 + factor)) + mPaddingTop +
+                    mPaddingBottom, heightSpecSize) : 0;
+        }
+
+        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
+            widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
+                    mPaddingRight : 0;
+        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+            widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
+                    mPaddingRight, widthSpecSize) : 0;
+        }
+
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+        measureChildren();
+    }
+
+    class LayoutParams extends ViewGroup.LayoutParams {
+        int horizontalOffset;
+        int verticalOffset;
+        View mView;
+
+        LayoutParams(View view) {
+            super(0, 0);
+            width = 0;
+            height = 0;
+            horizontalOffset = 0;
+            verticalOffset = 0;
+            mView = view;
+        }
+
+        LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            horizontalOffset = 0;
+            verticalOffset = 0;
+            width = 0;
+            height = 0;
+        }
+
+        private Rect parentRect = new Rect();
+        void invalidateGlobalRegion(View v, Rect r) {
+            View p = v;
+            if (!(v.getParent() != null && v.getParent() instanceof View)) return;
+
+            boolean firstPass = true;
+            parentRect.set(0, 0, 0, 0);
+            int depth = 0;
+            while (p.getParent() != null && p.getParent() instanceof View
+                    && !parentRect.contains(r)) {
+                if (!firstPass) {
+                    r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
+                    depth++;
+                }
+                firstPass = false;
+                p = (View) p.getParent();
+                parentRect.set(p.getScrollX(), p.getScrollY(),
+                               p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY());
+
+                // TODO: we need to stop early here if we've hit the edge of the screen
+                // so as to prevent us from walking too high in the hierarchy. A lot of this
+                // code might become a lot more straightforward.
+            }
+
+            if (depth > mAncestorHeight) {
+                mAncestorContainingAllChildren = (ViewGroup) p;
+                mAncestorHeight = depth;
+                disableParentalClipping();
+            }
+
+            p.invalidate(r.left, r.top, r.right, r.bottom);
+        }
+
+        private Rect invalidateRect = new Rect();
+        private RectF invalidateRectf = new RectF();
+        // This is public so that ObjectAnimator can access it
+        public void setVerticalOffset(int newVerticalOffset) {
+            int offsetDelta = newVerticalOffset - verticalOffset;
+            verticalOffset = newVerticalOffset;
+
+            if (mView != null) {
+                mView.requestLayout();
+                int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
+                int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());
+
+                invalidateRectf.set(mView.getLeft(),  top, mView.getRight(), bottom);
+
+                float xoffset = -invalidateRectf.left;
+                float yoffset = -invalidateRectf.top;
+                invalidateRectf.offset(xoffset, yoffset);
+                mView.getMatrix().mapRect(invalidateRectf);
+                invalidateRectf.offset(-xoffset, -yoffset);
+                invalidateRect.set((int) Math.floor(invalidateRectf.left),
+                        (int) Math.floor(invalidateRectf.top),
+                        (int) Math.ceil(invalidateRectf.right),
+                        (int) Math.ceil(invalidateRectf.bottom));
+
+                invalidateGlobalRegion(mView, invalidateRect);
+            }
+        }
+
+        public void setHorizontalOffset(int newHorizontalOffset) {
+            int offsetDelta = newHorizontalOffset - horizontalOffset;
+            horizontalOffset = newHorizontalOffset;
+
+            if (mView != null) {
+                mView.requestLayout();
+                int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft());
+                int right = Math.max(mView.getRight() + offsetDelta, mView.getRight());
+                invalidateRectf.set(left,  mView.getTop(), right, mView.getBottom());
+
+                float xoffset = -invalidateRectf.left;
+                float yoffset = -invalidateRectf.top;
+                invalidateRectf.offset(xoffset, yoffset);
+                mView.getMatrix().mapRect(invalidateRectf);
+                invalidateRectf.offset(-xoffset, -yoffset);
+
+                invalidateRect.set((int) Math.floor(invalidateRectf.left),
+                        (int) Math.floor(invalidateRectf.top),
+                        (int) Math.ceil(invalidateRectf.right),
+                        (int) Math.ceil(invalidateRectf.bottom));
+
+                invalidateGlobalRegion(mView, invalidateRect);
+            }
+        }
+    }
+
+    private static class HolographicHelper {
+        private final Paint mHolographicPaint = new Paint();
+        private final Paint mErasePaint = new Paint();
+        private final Paint mBlurPaint = new Paint();
+
+        HolographicHelper(Context context) {
+            initializePaints(context);
+        }
+
+        void initializePaints(Context context) {
+            final float density = context.getResources().getDisplayMetrics().density;
+
+            mHolographicPaint.setColor(0xff6699ff);
+            mHolographicPaint.setFilterBitmap(true);
+            mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
+            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+            mErasePaint.setFilterBitmap(true);
+            mBlurPaint.setMaskFilter(new BlurMaskFilter(2*density, BlurMaskFilter.Blur.NORMAL));
+        }
+
+        Bitmap createOutline(View v) {
+            if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
+                return null;
+            }
+
+            Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
+                    Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+
+            float rotationX = v.getRotationX();
+            float rotation = v.getRotation();
+            float translationY = v.getTranslationY();
+            v.setRotationX(0);
+            v.setRotation(0);
+            v.setTranslationY(0);
+            v.draw(canvas);
+            v.setRotationX(rotationX);
+            v.setRotation(rotation);
+            v.setTranslationY(translationY);
+
+            drawOutline(canvas, bitmap);
+            return bitmap;
+        }
+
+        final Matrix id = new Matrix();
+        void drawOutline(Canvas dest, Bitmap src) {
+            int[] xy = new int[2];
+            Bitmap mask = src.extractAlpha(mBlurPaint, xy);
+            Canvas maskCanvas = new Canvas(mask);
+            maskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
+            dest.drawColor(0, PorterDuff.Mode.CLEAR);
+            dest.setMatrix(id);
+            dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
+            mask.recycle();
+        }
+    }
+}
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
new file mode 100644
index 0000000..1ebe622
--- /dev/null
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -0,0 +1,702 @@
+/*
+ * 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.widget;
+
+import com.android.internal.R;
+
+import android.app.SearchDialog;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContentResolver.OpenResourceIdResult;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.WeakHashMap;
+
+/**
+ * Provides the contents for the suggestion drop-down list.in {@link SearchDialog}.
+ *
+ * @hide
+ */
+class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListener {
+
+    private static final boolean DBG = false;
+    private static final String LOG_TAG = "SuggestionsAdapter";
+    private static final int QUERY_LIMIT = 50;
+
+    static final int REFINE_NONE = 0;
+    static final int REFINE_BY_ENTRY = 1;
+    static final int REFINE_ALL = 2;
+
+    private SearchManager mSearchManager;
+    private SearchView mSearchView;
+    private SearchableInfo mSearchable;
+    private Context mProviderContext;
+    private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
+    private boolean mClosed = false;
+    private int mQueryRefinement = REFINE_BY_ENTRY;
+
+    // URL color
+    private ColorStateList mUrlColor;
+
+    // Cached column indexes, updated when the cursor changes.
+    private int mText1Col;
+    private int mText2Col;
+    private int mText2UrlCol;
+    private int mIconName1Col;
+    private int mIconName2Col;
+    private int mFlagsCol;
+
+    static final int NONE = -1;
+
+    private final Runnable mStartSpinnerRunnable;
+    private final Runnable mStopSpinnerRunnable;
+
+    /**
+     * The amount of time we delay in the filter when the user presses the delete key.
+     * @see Filter#setDelayer(android.widget.Filter.Delayer).
+     */
+    private static final long DELETE_KEY_POST_DELAY = 500L;
+
+    public SuggestionsAdapter(Context context, SearchView searchView,
+            SearchableInfo searchable,
+            WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) {
+        super(context,
+                com.android.internal.R.layout.search_dropdown_item_icons_2line,
+                null,   // no initial cursor
+                true);  // auto-requery
+        mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+        mSearchView = searchView;
+        mSearchable = searchable;
+        // set up provider resources (gives us icons, etc.)
+        Context activityContext = mSearchable.getActivityContext(mContext);
+        mProviderContext = mSearchable.getProviderContext(mContext, activityContext);
+
+        mOutsideDrawablesCache = outsideDrawablesCache;
+        
+        mStartSpinnerRunnable = new Runnable() {
+                public void run() {
+                // mSearchView.setWorking(true); // TODO:
+                }
+            };
+
+        mStopSpinnerRunnable = new Runnable() {
+            public void run() {
+                // mSearchView.setWorking(false); // TODO:
+            }
+        };
+
+        // delay 500ms when deleting
+        getFilter().setDelayer(new Filter.Delayer() {
+
+            private int mPreviousLength = 0;
+
+            public long getPostingDelay(CharSequence constraint) {
+                if (constraint == null) return 0;
+
+                long delay = constraint.length() < mPreviousLength ? DELETE_KEY_POST_DELAY : 0;
+                mPreviousLength = constraint.length();
+                return delay;
+            }
+        });
+    }
+
+    /**
+     * Enables query refinement for all suggestions. This means that an additional icon
+     * will be shown for each entry. When clicked, the suggested text on that line will be
+     * copied to the query text field.
+     * <p>
+     *
+     * @param refine which queries to refine. Possible values are {@link #REFINE_NONE},
+     * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}.
+     */
+    public void setQueryRefinement(int refineWhat) {
+        mQueryRefinement = refineWhat;
+    }
+
+    /**
+     * Returns the current query refinement preference.
+     * @return value of query refinement preference
+     */
+    public int getQueryRefinement() {
+        return mQueryRefinement;
+    }
+
+    /**
+     * Overridden to always return <code>false</code>, since we cannot be sure that
+     * suggestion sources return stable IDs.
+     */
+    @Override
+    public boolean hasStableIds() {
+        return false;
+    }
+
+    /**
+     * Use the search suggestions provider to obtain a live cursor.  This will be called
+     * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
+     * The results will be processed in the UI thread and changeCursor() will be called.
+     */
+    @Override
+    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+        if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")");
+        String query = (constraint == null) ? "" : constraint.toString();
+        /**
+         * for in app search we show the progress spinner until the cursor is returned with
+         * the results.
+         */
+        Cursor cursor = null;
+        //mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO:
+        try {
+            cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT);
+            // trigger fill window so the spinner stays up until the results are copied over and
+            // closer to being ready
+            if (cursor != null) {
+                cursor.getCount();
+                return cursor;
+            }
+        } catch (RuntimeException e) {
+            Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
+        }
+        // If cursor is null or an exception was thrown, stop the spinner and return null.
+        // changeCursor doesn't get called if cursor is null
+        // mSearchView.getWindow().getDecorView().post(mStopSpinnerRunnable); // TODO:
+        return null;
+    }
+
+    public void close() {
+        if (DBG) Log.d(LOG_TAG, "close()");
+        changeCursor(null);
+        mClosed = true;
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
+        super.notifyDataSetChanged();
+
+        // mSearchView.onDataSetChanged(); // TODO:
+
+        updateSpinnerState(getCursor());
+    }
+
+    @Override
+    public void notifyDataSetInvalidated() {
+        if (DBG) Log.d(LOG_TAG, "notifyDataSetInvalidated");
+        super.notifyDataSetInvalidated();
+
+        updateSpinnerState(getCursor());
+    }
+
+    private void updateSpinnerState(Cursor cursor) {
+        Bundle extras = cursor != null ? cursor.getExtras() : null;
+        if (DBG) {
+            Log.d(LOG_TAG, "updateSpinnerState - extra = "
+                + (extras != null
+                        ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
+                        : null));
+        }
+        // Check if the Cursor indicates that the query is not complete and show the spinner
+        if (extras != null
+                && extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)) {
+            // mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO:
+            return;
+        }
+        // If cursor is null or is done, stop the spinner
+        // mSearchView.getWindow().getDecorView().post(mStopSpinnerRunnable); // TODO:
+    }
+
+    /**
+     * Cache columns.
+     */
+    @Override
+    public void changeCursor(Cursor c) {
+        if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
+
+        if (mClosed) {
+            Log.w(LOG_TAG, "Tried to change cursor after adapter was closed.");
+            if (c != null) c.close();
+            return;
+        }
+
+        try {
+            super.changeCursor(c);
+
+            if (c != null) {
+                mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
+                mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
+                mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
+                mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
+                mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
+                mFlagsCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FLAGS);
+            }
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "error changing cursor and caching columns", e);
+        }
+    }
+
+    /**
+     * Tags the view with cached child view look-ups.
+     */
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        View v = super.newView(context, cursor, parent);
+        v.setTag(new ChildViewCache(v));
+        return v;
+    }
+
+    /**
+     * Cache of the child views of drop-drown list items, to avoid looking up the children
+     * each time the contents of a list item are changed.
+     */
+    private final static class ChildViewCache {
+        public final TextView mText1;
+        public final TextView mText2;
+        public final ImageView mIcon1;
+        public final ImageView mIcon2;
+        public final ImageView mIconRefine;
+
+        public ChildViewCache(View v) {
+            mText1 = (TextView) v.findViewById(com.android.internal.R.id.text1);
+            mText2 = (TextView) v.findViewById(com.android.internal.R.id.text2);
+            mIcon1 = (ImageView) v.findViewById(com.android.internal.R.id.icon1);
+            mIcon2 = (ImageView) v.findViewById(com.android.internal.R.id.icon2);
+            mIconRefine = (ImageView) v.findViewById(com.android.internal.R.id.edit_query);
+        }
+    }
+
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        ChildViewCache views = (ChildViewCache) view.getTag();
+
+        int flags = 0;
+        if (mFlagsCol != -1) {
+            flags = cursor.getInt(mFlagsCol);
+        }
+        if (views.mText1 != null) {
+            String text1 = getStringOrNull(cursor, mText1Col);
+            setViewText(views.mText1, text1);
+        }
+        if (views.mText2 != null) {
+            // First check TEXT_2_URL
+            CharSequence text2 = getStringOrNull(cursor, mText2UrlCol);
+            if (text2 != null) {
+                text2 = formatUrl(text2);
+            } else {
+                text2 = getStringOrNull(cursor, mText2Col);
+            }
+
+            // If no second line of text is indicated, allow the first line of text
+            // to be up to two lines if it wants to be.
+            if (TextUtils.isEmpty(text2)) {
+                if (views.mText1 != null) {
+                    views.mText1.setSingleLine(false);
+                    views.mText1.setMaxLines(2);
+                }
+            } else {
+                if (views.mText1 != null) {
+                    views.mText1.setSingleLine(true);
+                    views.mText1.setMaxLines(1);
+                }
+            }
+            setViewText(views.mText2, text2);
+        }
+
+        if (views.mIcon1 != null) {
+            setViewDrawable(views.mIcon1, getIcon1(cursor));
+        }
+        if (views.mIcon2 != null) {
+            setViewDrawable(views.mIcon2, getIcon2(cursor));
+        }
+        if (mQueryRefinement == REFINE_ALL
+                || (mQueryRefinement == REFINE_BY_ENTRY
+                        && (flags & SearchManager.FLAG_QUERY_REFINEMENT) != 0)) {
+            views.mIconRefine.setVisibility(View.VISIBLE);
+            views.mIconRefine.setTag(views.mText1.getText());
+            views.mIconRefine.setOnClickListener(this);
+        } else {
+            views.mIconRefine.setVisibility(View.GONE);
+        }
+    }
+
+    public void onClick(View v) {
+        Object tag = v.getTag();
+        if (tag instanceof CharSequence) {
+            mSearchView.onQueryRefine((CharSequence) tag);
+        }
+    }
+
+    private CharSequence formatUrl(CharSequence url) {
+        if (mUrlColor == null) {
+            // Lazily get the URL color from the current theme.
+            TypedValue colorValue = new TypedValue();
+            mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
+            mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId);
+        }
+
+        SpannableString text = new SpannableString(url);
+        text.setSpan(new TextAppearanceSpan(null, 0, 0, mUrlColor, null),
+                0, url.length(),
+                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        return text;
+    }
+
+    private void setViewText(TextView v, CharSequence text) {
+        // Set the text even if it's null, since we need to clear any previous text.
+        v.setText(text);
+
+        if (TextUtils.isEmpty(text)) {
+            v.setVisibility(View.GONE);
+        } else {
+            v.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private Drawable getIcon1(Cursor cursor) {
+        if (mIconName1Col < 0) {
+            return null;
+        }
+        String value = cursor.getString(mIconName1Col);
+        Drawable drawable = getDrawableFromResourceValue(value);
+        if (drawable != null) {
+            return drawable;
+        }
+        return getDefaultIcon1(cursor);
+    }
+
+    private Drawable getIcon2(Cursor cursor) {
+        if (mIconName2Col < 0) {
+            return null;
+        }
+        String value = cursor.getString(mIconName2Col);
+        return getDrawableFromResourceValue(value);
+    }
+
+    /**
+     * Sets the drawable in an image view, makes sure the view is only visible if there
+     * is a drawable.
+     */
+    private void setViewDrawable(ImageView v, Drawable drawable) {
+        // Set the icon even if the drawable is null, since we need to clear any
+        // previous icon.
+        v.setImageDrawable(drawable);
+
+        if (drawable == null) {
+            v.setVisibility(View.GONE);
+        } else {
+            v.setVisibility(View.VISIBLE);
+
+            // This is a hack to get any animated drawables (like a 'working' spinner)
+            // to animate. You have to setVisible true on an AnimationDrawable to get
+            // it to start animating, but it must first have been false or else the
+            // call to setVisible will be ineffective. We need to clear up the story
+            // about animated drawables in the future, see http://b/1878430.
+            drawable.setVisible(false, false);
+            drawable.setVisible(true, false);
+        }
+    }
+
+    /**
+     * Gets the text to show in the query field when a suggestion is selected.
+     *
+     * @param cursor The Cursor to read the suggestion data from. The Cursor should already
+     *        be moved to the suggestion that is to be read from.
+     * @return The text to show, or <code>null</code> if the query should not be
+     *         changed when selecting this suggestion.
+     */
+    @Override
+    public CharSequence convertToString(Cursor cursor) {
+        if (cursor == null) {
+            return null;
+        }
+
+        String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY);
+        if (query != null) {
+            return query;
+        }
+
+        if (mSearchable.shouldRewriteQueryFromData()) {
+            String data = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
+            if (data != null) {
+                return data;
+            }
+        }
+
+        if (mSearchable.shouldRewriteQueryFromText()) {
+            String text1 = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_TEXT_1);
+            if (text1 != null) {
+                return text1;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * This method is overridden purely to provide a bit of protection against
+     * flaky content providers.
+     *
+     * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
+     */
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        try {
+            return super.getView(position, convertView, parent);
+        } catch (RuntimeException e) {
+            Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
+            // Put exception string in item title
+            View v = newView(mContext, mCursor, parent);
+            if (v != null) {
+                ChildViewCache views = (ChildViewCache) v.getTag();
+                TextView tv = views.mText1;
+                tv.setText(e.toString());
+            }
+            return v;
+        }
+    }
+
+    /**
+     * Gets a drawable given a value provided by a suggestion provider.
+     *
+     * This value could be just the string value of a resource id
+     * (e.g., "2130837524"), in which case we will try to retrieve a drawable from
+     * the provider's resources. If the value is not an integer, it is
+     * treated as a Uri and opened with
+     * {@link ContentResolver#openOutputStream(android.net.Uri, String)}.
+     *
+     * All resources and URIs are read using the suggestion provider's context.
+     *
+     * If the string is not formatted as expected, or no drawable can be found for
+     * the provided value, this method returns null.
+     *
+     * @param drawableId a string like "2130837524",
+     *        "android.resource://com.android.alarmclock/2130837524",
+     *        or "content://contacts/photos/253".
+     * @return a Drawable, or null if none found
+     */
+    private Drawable getDrawableFromResourceValue(String drawableId) {
+        if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) {
+            return null;
+        }
+        try {
+            // First, see if it's just an integer
+            int resourceId = Integer.parseInt(drawableId);
+            // It's an int, look for it in the cache
+            String drawableUri = ContentResolver.SCHEME_ANDROID_RESOURCE
+                    + "://" + mProviderContext.getPackageName() + "/" + resourceId;
+            // Must use URI as cache key, since ints are app-specific
+            Drawable drawable = checkIconCache(drawableUri);
+            if (drawable != null) {
+                return drawable;
+            }
+            // Not cached, find it by resource ID
+            drawable = mProviderContext.getResources().getDrawable(resourceId);
+            // Stick it in the cache, using the URI as key
+            storeInIconCache(drawableUri, drawable);
+            return drawable;
+        } catch (NumberFormatException nfe) {
+            // It's not an integer, use it as a URI
+            Drawable drawable = checkIconCache(drawableId);
+            if (drawable != null) {
+                return drawable;
+            }
+            Uri uri = Uri.parse(drawableId);
+            drawable = getDrawable(uri);
+            storeInIconCache(drawableId, drawable);
+            return drawable;
+        } catch (Resources.NotFoundException nfe) {
+            // It was an integer, but it couldn't be found, bail out
+            Log.w(LOG_TAG, "Icon resource not found: " + drawableId);
+            return null;
+        }
+    }
+
+    /**
+     * Gets a drawable by URI, without using the cache.
+     *
+     * @return A drawable, or {@code null} if the drawable could not be loaded.
+     */
+    private Drawable getDrawable(Uri uri) {
+        try {
+            String scheme = uri.getScheme();
+            if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+                // Load drawables through Resources, to get the source density information
+                OpenResourceIdResult r =
+                    mProviderContext.getContentResolver().getResourceId(uri);
+                try {
+                    return r.r.getDrawable(r.id);
+                } catch (Resources.NotFoundException ex) {
+                    throw new FileNotFoundException("Resource does not exist: " + uri);
+                }
+            } else {
+                // Let the ContentResolver handle content and file URIs.
+                InputStream stream = mProviderContext.getContentResolver().openInputStream(uri);
+                if (stream == null) {
+                    throw new FileNotFoundException("Failed to open " + uri);
+                }
+                try {
+                    return Drawable.createFromStream(stream, null);
+                } finally {
+                    try {
+                        stream.close();
+                    } catch (IOException ex) {
+                        Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex);
+                    }
+                }
+            }
+        } catch (FileNotFoundException fnfe) {
+            Log.w(LOG_TAG, "Icon not found: " + uri + ", " + fnfe.getMessage());
+            return null;
+        }
+    }
+
+    private Drawable checkIconCache(String resourceUri) {
+        Drawable.ConstantState cached = mOutsideDrawablesCache.get(resourceUri);
+        if (cached == null) {
+            return null;
+        }
+        if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + resourceUri);
+        return cached.newDrawable();
+    }
+
+    private void storeInIconCache(String resourceUri, Drawable drawable) {
+        if (drawable != null) {
+            mOutsideDrawablesCache.put(resourceUri, drawable.getConstantState());
+        }
+    }
+
+    /**
+     * Gets the left-hand side icon that will be used for the current suggestion
+     * if the suggestion contains an icon column but no icon or a broken icon.
+     *
+     * @param cursor A cursor positioned at the current suggestion.
+     * @return A non-null drawable.
+     */
+    private Drawable getDefaultIcon1(Cursor cursor) {
+        // Check the component that gave us the suggestion
+        Drawable drawable = getActivityIconWithCache(mSearchable.getSearchActivity());
+        if (drawable != null) {
+            return drawable;
+        }
+
+        // Fall back to a default icon
+        return mContext.getPackageManager().getDefaultActivityIcon();
+    }
+
+    /**
+     * Gets the activity or application icon for an activity.
+     * Uses the local icon cache for fast repeated lookups.
+     *
+     * @param component Name of an activity.
+     * @return A drawable, or {@code null} if neither the activity nor the application
+     *         has an icon set.
+     */
+    private Drawable getActivityIconWithCache(ComponentName component) {
+        // First check the icon cache
+        String componentIconKey = component.flattenToShortString();
+        // Using containsKey() since we also store null values.
+        if (mOutsideDrawablesCache.containsKey(componentIconKey)) {
+            Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey);
+            return cached == null ? null : cached.newDrawable(mProviderContext.getResources());
+        }
+        // Then try the activity or application icon
+        Drawable drawable = getActivityIcon(component);
+        // Stick it in the cache so we don't do this lookup again.
+        Drawable.ConstantState toCache = drawable == null ? null : drawable.getConstantState();
+        mOutsideDrawablesCache.put(componentIconKey, toCache);
+        return drawable;
+    }
+
+    /**
+     * Gets the activity or application icon for an activity.
+     *
+     * @param component Name of an activity.
+     * @return A drawable, or {@code null} if neither the acitivy or the application
+     *         have an icon set.
+     */
+    private Drawable getActivityIcon(ComponentName component) {
+        PackageManager pm = mContext.getPackageManager();
+        final ActivityInfo activityInfo;
+        try {
+            activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA);
+        } catch (NameNotFoundException ex) {
+            Log.w(LOG_TAG, ex.toString());
+            return null;
+        }
+        int iconId = activityInfo.getIconResource();
+        if (iconId == 0) return null;
+        String pkg = component.getPackageName();
+        Drawable drawable = pm.getDrawable(pkg, iconId, activityInfo.applicationInfo);
+        if (drawable == null) {
+            Log.w(LOG_TAG, "Invalid icon resource " + iconId + " for "
+                    + component.flattenToShortString());
+            return null;
+        }
+        return drawable;
+    }
+
+    /**
+     * Gets the value of a string column by name.
+     *
+     * @param cursor Cursor to read the value from.
+     * @param columnName The name of the column to read.
+     * @return The value of the given column, or <code>null</null>
+     *         if the cursor does not contain the given column.
+     */
+    public static String getColumnString(Cursor cursor, String columnName) {
+        int col = cursor.getColumnIndex(columnName);
+        return getStringOrNull(cursor, col);
+    }
+
+    private static String getStringOrNull(Cursor cursor, int col) {
+        if (col == NONE) {
+            return null;
+        }
+        try {
+            return cursor.getString(col);
+        } catch (Exception e) {
+            Log.e(LOG_TAG,
+                    "unexpected error retrieving valid column from cursor, "
+                            + "did the remote process die?", e);
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5350478..70c6378 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -21,8 +21,9 @@
 
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.content.ClipData;
+import android.content.ClipboardManager;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -36,6 +37,7 @@
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.inputmethodservice.ExtractEditText;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -44,7 +46,6 @@
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.text.BoringLayout;
-import android.text.ClipboardManager;
 import android.text.DynamicLayout;
 import android.text.Editable;
 import android.text.GetChars;
@@ -84,10 +85,13 @@
 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;
@@ -190,7 +194,9 @@
     static final String LOG_TAG = "TextView";
     static final boolean DEBUG_EXTRACT = false;
     
-    private static int PRIORITY = 100;
+    private static final int PRIORITY = 100;
+
+    private int mCurrentAlpha = 255;    
 
     private final int[] mTempCoords = new int[2];
 
@@ -2813,6 +2819,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);
         }
@@ -2820,6 +2834,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);
+        }
     }
 
     /**
@@ -3732,7 +3763,7 @@
         //   allow to test for hasSelection in onFocusChanged, which would trigger a
         //   startTextSelectionMode here. TODO
         if (selectionController != null && hasSelection()) {
-            startTextSelectionMode();
+            startSelectionActionMode();
         }
 
         mPreDrawState = PREDRAW_DONE;
@@ -3872,6 +3903,22 @@
     }
 
     @Override
+    protected boolean onSetAlpha(int alpha) {
+        if (mMovement == null && getBackground() == null) {
+            mCurrentAlpha = alpha;
+            final Drawables dr = mDrawables;
+            if (dr != null) {
+                if (dr.mDrawableLeft != null) dr.mDrawableLeft.mutate().setAlpha(alpha);
+                if (dr.mDrawableTop != null) dr.mDrawableTop.mutate().setAlpha(alpha);
+                if (dr.mDrawableRight != null) dr.mDrawableRight.mutate().setAlpha(alpha);
+                if (dr.mDrawableBottom != null) dr.mDrawableBottom.mutate().setAlpha(alpha);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
         restartMarqueeIfNeeded();
 
@@ -3968,6 +4015,7 @@
         }
 
         mTextPaint.setColor(color);
+        mTextPaint.setAlpha(mCurrentAlpha);
         mTextPaint.drawableState = getDrawableState();
 
         canvas.save();
@@ -4353,8 +4401,8 @@
 
                 // Has to be done on key down (and not on key up) to correctly be intercepted.
             case KeyEvent.KEYCODE_BACK:
-                if (mIsInTextSelectionMode) {
-                    stopTextSelectionMode();
+                if (mSelectionActionMode != null) {
+                    stopSelectionActionMode();
                     return -1;
                 }
                 break;
@@ -4851,7 +4899,7 @@
                 makeBlink();
             }
         }
-        
+
         checkForResize();
     }
     
@@ -4942,6 +4990,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;
 
@@ -5106,6 +5156,8 @@
     }
 
     private boolean compressText(float width) {
+        if (isHardwareAccelerated()) return false;
+        
         // Only compress the text if it hasn't been compressed by the previous pass
         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
                 mTextPaint.getTextScaleX() == 1.0f) {
@@ -5766,11 +5818,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) {
@@ -6601,11 +6657,14 @@
 
             hideInsertionPointCursorController();
             if (this instanceof ExtractEditText) {
-                // terminateTextSelectionMode would remove selection, which we want to keep when
+                // terminateTextSelectionMode removes selection, which we want to keep when
                 // ExtractEditText goes out of focus.
-                mIsInTextSelectionMode = false;
+                final int selStart = getSelectionStart();
+                final int selEnd = getSelectionEnd();
+                terminateSelectionActionMode();
+                Selection.setSelection((Spannable) mText, selStart, selEnd);
             } else {
-                terminateTextSelectionMode();
+                terminateSelectionActionMode();
             }
 
             mLastTouchOffset = -1;
@@ -6709,7 +6768,7 @@
                 return;
             } else {
                 // Tapping outside stops selection mode, if any
-                stopTextSelectionMode();
+                stopSelectionActionMode();
 
                 if (mInsertionPointCursorController != null) {
                     mInsertionPointCursorController.show();
@@ -6722,13 +6781,13 @@
 
     class CommitSelectionReceiver extends ResultReceiver {
         private final int mPrevStart, mPrevEnd;
-        
+
         public CommitSelectionReceiver(int prevStart, int prevEnd) {
             super(getHandler());
             mPrevStart = prevStart;
             mPrevEnd = prevEnd;
         }
-        
+
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             // If this tap was actually used to show the IMM, leave cursor or selection unchanged
@@ -6740,12 +6799,12 @@
                 Selection.setSelection((Spannable)mText, start, end);
 
                 if (hasSelection()) {
-                    startTextSelectionMode();
+                    startSelectionActionMode();
                 }
             }
         }
     }
-    
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         final int action = event.getActionMasked();
@@ -6847,7 +6906,7 @@
             }
         } else {
             // Stop selection mode if the controller becomes unavailable.
-            stopTextSelectionMode();
+            stopSelectionActionMode();
             mSelectionModifierCursorController = null;
         }
     }
@@ -6987,8 +7046,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();
     }
@@ -7135,7 +7196,7 @@
                 getSelectionStart() >= 0 &&
                 getSelectionEnd() >= 0 &&
                 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
-                hasText());
+                hasPrimaryClip());
     }
 
     /**
@@ -7227,7 +7288,7 @@
         // Two ints packed in a long
         return (((long) start) << 32) | end;
     }
-    
+
     private void selectCurrentWord() {
         // In case selection mode is started after an orientation change or after a select all,
         // use the current selection instead of creating one
@@ -7242,7 +7303,50 @@
             ((SelectionModifierCursorController) mSelectionModifierCursorController);
         int minOffset = selectionModifierCursorController.getMinTouchOffset();
         int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
-        
+
+        if (minOffset == maxOffset) {
+            int offset = Math.max(0, Math.min(minOffset, mTransformed.length()));
+
+            // Tolerance, number of charaters around tapped position
+            final int range = 1;
+            final int max = mTransformed.length() - 1;
+
+            // 'Smart' word selection: detect position between words
+            for (int i = -range; i <= range; i++) {
+                int index = offset + i;
+                if (index >= 0 && index <= max) {
+                    if (Character.isSpaceChar(mTransformed.charAt(index))) {
+                        // Select current space
+                        selectionStart = index;
+                        selectionEnd = selectionStart + 1;
+
+                        // Extend selection to maximum space range
+                        while (selectionStart > 0 &&
+                                Character.isSpaceChar(mTransformed.charAt(selectionStart - 1))) {
+                            selectionStart--;
+                        }
+                        while (selectionEnd < max &&
+                                Character.isSpaceChar(mTransformed.charAt(selectionEnd))) {
+                            selectionEnd++;
+                        }
+
+                        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+                        return;
+                    }
+                }
+            }
+
+            // 'Smart' word selection: detect position at beginning or end of text.
+            if (offset <= range) {
+                Selection.setSelection((Spannable) mText, 0, 0);
+                return;
+            }
+            if (offset >= (max - range)) {
+                Selection.setSelection((Spannable) mText, max + 1, max + 1);
+                return;
+            }
+        }
+
         long wordLimits = getWordLimitsAt(minOffset);
         if (wordLimits >= 0) {
             selectionStart = (int) (wordLimits >>> 32);
@@ -7259,22 +7363,7 @@
 
         Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
     }
-    
-    private String getWordForDictionary() {
-        if (mLastTouchOffset < 0) {
-            return null;
-        }
 
-        long wordLimits = getWordLimitsAt(mLastTouchOffset);
-        if (wordLimits >= 0) {
-            int start = (int) (wordLimits >>> 32);
-            int end = (int) (wordLimits & 0x00000000FFFFFFFFL);
-            return mTransformed.subSequence(start, end).toString();
-        } else {
-            return null;
-        }
-    }
-    
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         if (!isShown()) {
@@ -7316,93 +7405,34 @@
         super.onCreateContextMenu(menu);
         boolean added = false;
 
-        if (mIsInTextSelectionMode) {
-            MenuHandler handler = new MenuHandler();
-            
-            if (canCut()) {
-                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
-                     setOnMenuItemClickListener(handler).
-                     setAlphabeticShortcut('x');
+        MenuHandler handler = new MenuHandler();
+
+        if (mText instanceof Spanned) {
+            int selStart = getSelectionStart();
+            int selEnd = getSelectionEnd();
+
+            int min = Math.min(selStart, selEnd);
+            int max = Math.max(selStart, selEnd);
+
+            URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
+                                                        URLSpan.class);
+            if (urls.length == 1) {
+                menu.add(0, ID_COPY_URL, 0,
+                         com.android.internal.R.string.copyUrl).
+                            setOnMenuItemClickListener(handler);
+
                 added = true;
             }
-
-            if (canCopy()) {
-                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
-                     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;
-            }
-        } else {
-            /*
-            if (!isFocused()) {
-                if (isFocusable() && mInput != null) {
-                    if (canCopy()) {
-                        MenuHandler handler = new MenuHandler();
-                        menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
-                             setOnMenuItemClickListener(handler).
-                             setAlphabeticShortcut('c');
-                        menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
-                    }
-                }
-
-                //return;
-            }
-             */
-            MenuHandler handler = new MenuHandler();
-
-            if (canSelectText()) {
-                menu.add(0, ID_START_SELECTING_TEXT, 0, com.android.internal.R.string.selectText).
-                     setOnMenuItemClickListener(handler);
-                menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
-                     setOnMenuItemClickListener(handler).
-                     setAlphabeticShortcut('a');
-                added = true;
-            }
-
-            if (mText instanceof Spanned) {
-                int selStart = getSelectionStart();
-                int selEnd = getSelectionEnd();
-
-                int min = Math.min(selStart, selEnd);
-                int max = Math.max(selStart, selEnd);
-
-                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
-                        URLSpan.class);
-                if (urls.length == 1) {
-                    menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl).
-                         setOnMenuItemClickListener(handler);
-                    added = true;
-                }
-            }
-            
-            if (canPaste()) {
-                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
-                     setOnMenuItemClickListener(handler).
-                     setAlphabeticShortcut('v');
-                added = true;
-            }
-
-            if (isInputMethodTarget()) {
-                menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
-                     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;
-
-            }
+        }
+        
+        // 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;
         }
 
         if (added) {
@@ -7419,15 +7449,14 @@
         return imm != null && imm.isActive(this);
     }
     
-    // Context menu entries
+    // 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_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) {
@@ -7437,11 +7466,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#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 min = 0;
@@ -7455,93 +7480,31 @@
             max = Math.max(0, Math.max(selStart, selEnd));
         }
 
-        ClipboardManager clip = (ClipboardManager)getContext()
+        ClipboardManager clipboard = (ClipboardManager)getContext()
                 .getSystemService(Context.CLIPBOARD_SERVICE);
 
         switch (id) {
-            case ID_SELECT_ALL:
-                Selection.setSelection((Spannable) mText, 0, mText.length());
-                startTextSelectionMode();
-                return true;
-
-            case ID_START_SELECTING_TEXT:
-                startTextSelectionMode();
-                return true;
-
-            case ID_CUT:                
-                clip.setText(mTransformed.subSequence(min, max));
-                ((Editable) mText).delete(min, max);
-                stopTextSelectionMode();
-                return true;
-
-            case ID_COPY:
-                clip.setText(mTransformed.subSequence(min, max));
-                stopTextSelectionMode();
-                return true;
-
-            case ID_PASTE:
-                CharSequence paste = clip.getText();
-
-                if (paste != null && paste.length() > 0) {
-                    // Paste adds/removes spaces before or after insertion as needed.
-
-                    if (Character.isSpaceChar(paste.charAt(0))) {
-                        if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
-                            // Two spaces at beginning of paste: remove one
-                            ((Editable) mText).replace(min - 1, min, "");
-                            min = min - 1;
-                            max = max - 1;
-                        }
-                    } else {
-                        if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
-                            // No space at beginning of paste: add one
-                            ((Editable) mText).replace(min, min, " ");
-                            min = min + 1;
-                            max = max + 1;
-                        }
-                    }
-
-                    if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
-                        if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
-                            // Two spaces at end of paste: remove one
-                            ((Editable) mText).replace(max, max + 1, "");
-                        }
-                    } else {
-                        if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
-                            // No space at end of paste: add one
-                            ((Editable) mText).replace(max, max, " ");
-                        }
-                    }
-
-                    Selection.setSelection((Spannable) mText, max);
-                    ((Editable) mText).replace(min, max, paste);
-                    stopTextSelectionMode();
-                }
-                return true;
-
             case ID_COPY_URL:
+
                 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
-                if (urls.length == 1) {
-                    clip.setText(urls[0].getURL());
+                if (urls.length >= 1) {
+                    ClipData clip = null;
+                    for (int i=0; i<urls.length; i++) {
+                        Uri uri = Uri.parse(urls[0].getURL());
+                        if (clip == null) {
+                            clip = ClipData.newRawUri(null, null, uri);
+                        } else {
+                            clip.addItem(new ClipData.Item(uri));
+                        }
+                    }
+                    if (clip != null) {
+                        clipboard.setPrimaryClip(clip);
+                    }
                 }
                 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;
             }
 
@@ -7554,35 +7517,81 @@
             mEatTouchRelease = true;
             return true;
         }
+        
+        if (startSelectionActionMode()) {
+            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            mEatTouchRelease = true;
+            return true;
+        }
 
         return false;
     }
 
-    private void startTextSelectionMode() {
-        if (!mIsInTextSelectionMode) {
-            if (mSelectionModifierCursorController == null) {
-                Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
-                return;
-            }
+    private boolean touchPositionIsInSelection() {
+        int selectionStart = getSelectionStart();
+        int selectionEnd = getSelectionEnd();
 
-            if (!requestFocus()) {
-                return;
-            }
-
-            selectCurrentWord();
-            mSelectionModifierCursorController.show();
-            mIsInTextSelectionMode = true;
+        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));
     }
-    
+
     /**
-     * Same as {@link #stopTextSelectionMode()}, except that there is no cursor controller
+     * 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 #stopSelectionActionMode()}, 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 terminateTextSelectionMode() {
-        stopTextSelectionMode();
+    private void terminateSelectionActionMode() {
+        stopSelectionActionMode();
         if (mSelectionModifierCursorController != null) {
             SelectionModifierCursorController selectionModifierCursorController =
                 (SelectionModifierCursorController) mSelectionModifierCursorController;
@@ -7590,14 +7599,146 @@
         }
     }
 
-    private void stopTextSelectionMode() {
-        if (mIsInTextSelectionMode) {
-            Selection.setSelection((Spannable) mText, getSelectionEnd());
+    private void stopSelectionActionMode() {
+        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 (canSelectText()) {
+                menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+                    setIcon(com.android.internal.R.drawable.ic_menu_select_all).
+                    setAlphabeticShortcut('a');
+                atLeastOne = true;
+            }
+
+            if (canCut()) {
+                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
+                    setIcon(com.android.internal.R.drawable.ic_menu_cut).
+                    setAlphabeticShortcut('x');
+                atLeastOne = true;
+            }
+
+            if (canCopy()) {
+                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
+                    setIcon(com.android.internal.R.drawable.ic_menu_copy).
+                    setAlphabeticShortcut('c');
+                atLeastOne = true;
+            }
+
+            if (canPaste()) {
+                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+                        setIcon(com.android.internal.R.drawable.ic_menu_paste).
+                        setAlphabeticShortcut('v');
+                atLeastOne = true;
+            }
+
+            if (atLeastOne) {
+                mSelectionModifierCursorController.show();
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        @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 clipboard = (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:
+                    ClipData clip = clipboard.getPrimaryClip();
+                    if (clip != null) {
+                        boolean didfirst = false;
+                        for (int i=0; i<clip.getItemCount(); i++) {
+                            CharSequence paste = clip.getItem(i).coerceToText(getContext());
+                            if (paste != null) {
+                                if (!didfirst) {
+                                    Selection.setSelection((Spannable) mText, max);
+                                    ((Editable) mText).replace(min, max, paste);
+                                } else {
+                                    ((Editable) mText).insert(getSelectionEnd(), "\n");
+                                    ((Editable) mText).insert(getSelectionEnd(), paste);
+                                }
+                            }
+                        }
+                        stopSelectionActionMode();
+                    }
+
+                    return true;
+
+                case ID_CUT:
+                    clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
+                            mTransformed.subSequence(min, max)));
+                    ((Editable) mText).delete(min, max);
+                    stopSelectionActionMode();
+                    return true;
+
+                case ID_COPY:
+                    clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
+                            mTransformed.subSequence(min, max)));
+                    stopSelectionActionMode();
+                    return true;
+            }
+
+            return false;
+        }
+
+        @Override
+        public void onDestroyActionMode(ActionMode mode) {
+            Selection.setSelection((Spannable) mText, getSelectionStart());
             if (mSelectionModifierCursorController != null) {
                 mSelectionModifierCursorController.hide();
             }
-
-            mIsInTextSelectionMode = false;
+            mSelectionActionMode = null;
         }
     }
 
@@ -8048,7 +8189,7 @@
 
     private void hideControllers() {
         hideInsertionPointCursorController();
-        stopTextSelectionMode();
+        stopSelectionActionMode();
     }
 
     private int getOffsetForHorizontal(int line, int x) {
@@ -8111,7 +8252,8 @@
         return getOffsetForHorizontal(line, x);
     }
 
-    @ViewDebug.ExportedProperty
+
+    @ViewDebug.ExportedProperty(category = "text")
     private CharSequence            mText;
     private CharSequence            mTransformed;
     private BufferType              mBufferType = BufferType.NORMAL;
@@ -8142,7 +8284,7 @@
     // Cursor Controllers. Null when disabled.
     private CursorController        mInsertionPointCursorController;
     private CursorController        mSelectionModifierCursorController;
-    private boolean                 mIsInTextSelectionMode = false;
+    private ActionMode              mSelectionActionMode;
     private int                     mLastTouchOffset = -1;
     // Created once and shared by different CursorController helper methods.
     // Only one cursor controller is active at any time which prevent race conditions.
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..cf029d2
--- /dev/null
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Dialog;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Context;
+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 Context mContext;
+    private Activity mActivity;
+    private Dialog mDialog;
+
+    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) {
+        mActivity = activity;
+        init(activity.getWindow().getDecorView());
+    }
+
+    public ActionBarImpl(Dialog dialog) {
+        mDialog = dialog;
+        init(dialog.getWindow().getDecorView());
+    }
+
+    private void init(View decor) {
+        mContext = decor.getContext();
+        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");
+        }
+
+        mActionView.setContextView(mUpperContextView);
+        mContextDisplayMode = mLowerContextView == null ?
+                CONTEXT_DISPLAY_NORMAL : CONTEXT_DISPLAY_SPLIT;
+    }
+
+    @Override
+    public void setTitle(int resId) {
+        setTitle(mContext.getString(resId));
+    }
+
+    @Override
+    public void setSubtitle(int resId) {
+        setSubtitle(mContext.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 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);
+        }
+        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 ActionBar.TabListener callback = tabi.getCallback();
+
+        if (callback == null) {
+            throw new IllegalStateException("Action Bar Tab must have a Callback");
+        }
+
+        tabi.setPosition(position);
+        mTabs.add(position, tabi);
+
+        if (isFirstTab) {
+            final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction();
+            mSelectedTab = tabi;
+            callback.onTabSelected(tab, trans);
+            if (!trans.isEmpty()) {
+                trans.commit();
+            }
+        }
+    }
+
+    @Override
+    public void addTab(Tab tab) {
+        mActionView.addTab(tab);
+        configureTab(tab, mTabs.size());
+    }
+
+    @Override
+    public void addTab(Tab tab, int position) {
+        mActionView.addTab(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() {
+        if (mActivity == null) {
+            throw new IllegalStateException(
+                    "Tab navigation mode cannot be used outside of an Activity");
+        }
+        mActionView.setNavigationMode(NAVIGATION_MODE_TABS);
+    }
+
+    @Override
+    public void selectTab(Tab tab) {
+        if (mSelectedTab == tab) {
+            return;
+        }
+
+        mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
+        final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction();
+        if (mSelectedTab != null) {
+            mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
+        }
+        mSelectedTab = (TabImpl) tab;
+        if (mSelectedTab != null) {
+            mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans);
+        }
+
+        if (!trans.isEmpty()) {
+            trans.commit();
+        }
+    }
+
+    @Override
+    public Tab getSelectedTab() {
+        return mSelectedTab;
+    }
+
+    @Override
+    public int getHeight() {
+        return mActionView.getHeight();
+    }
+
+    @Override
+    public void show() {
+        // TODO animate!
+        mAnimatorView.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void hide() {
+        // TODO animate!
+        mAnimatorView.setVisibility(View.GONE);
+    }
+
+    public boolean isShowing() {
+        return mAnimatorView.getVisibility() == View.VISIBLE;
+    }
+
+    /**
+     * @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())
+                    .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            mMenu.setCallback(this);
+        }
+
+        @Override
+        public MenuInflater getMenuInflater() {
+            return new MenuInflater(mContext);
+        }
+
+        @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(mContext, subMenu).show();
+            return true;
+        }
+
+        public void onCloseSubMenu(SubMenuBuilder menu) {
+        }
+
+        public void onMenuModeChange(MenuBuilder menu) {
+            invalidate();
+            mUpperContextView.showOverflowMenu();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public class TabImpl extends ActionBar.Tab {
+        private ActionBar.TabListener mCallback;
+        private Object mTag;
+        private Drawable mIcon;
+        private CharSequence mText;
+        private int mPosition;
+        private View mCustomView;
+
+        @Override
+        public Object getTag() {
+            return mTag;
+        }
+
+        @Override
+        public void setTag(Object tag) {
+            mTag = tag;
+        }
+
+        public ActionBar.TabListener getCallback() {
+            return mCallback;
+        }
+
+        @Override
+        public void setTabListener(ActionBar.TabListener callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public View getCustomView() {
+            return mCustomView;
+        }
+
+        @Override
+        public void setCustomView(View view) {
+            mCustomView = view;
+        }
+
+        @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 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/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
new file mode 100644
index 0000000..c8e3935
--- /dev/null
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.R;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.ListFragment;
+import android.app.backup.BackupManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.Locale;
+
+public class LocalePicker extends ListFragment {
+    private static final String TAG = "LocalePicker";
+    private static final boolean DEBUG = false;
+
+    public static interface LocaleSelectionListener {
+        // You can add any argument if you really need it...
+        public void onLocaleSelected(Locale locale);
+    }
+
+    Loc[] mLocales;
+    String[] mSpecialLocaleCodes;
+    String[] mSpecialLocaleNames;
+
+    private Locale mNewLocale;
+
+    LocaleSelectionListener mListener;  // default to null
+
+    private static class Loc implements Comparable<Loc> {
+        static Collator sCollator = Collator.getInstance();
+
+        String label;
+        Locale locale;
+
+        public Loc(String label, Locale locale) {
+            this.label = label;
+            this.locale = locale;
+        }
+
+        @Override
+        public String toString() {
+            return this.label;
+        }
+
+        @Override
+        public int compareTo(Loc another) {
+            return sCollator.compare(this.label, another.label);
+        }
+    }
+
+    private void setUpLocaleList() {
+        final Activity activity = getActivity();
+        final Resources resources = activity.getResources();
+        mSpecialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
+        mSpecialLocaleNames = resources.getStringArray(R.array.special_locale_names);
+
+        final String[] locales = activity.getAssets().getLocales();
+        Arrays.sort(locales);
+        final int origSize = locales.length;
+        Loc[] preprocess = new Loc[origSize];
+        int finalSize = 0;
+        for (int i = 0 ; i < origSize; i++ ) {
+            String s = locales[i];
+            int len = s.length();
+            if (len == 5) {
+                String language = s.substring(0, 2);
+                String country = s.substring(3, 5);
+                Locale l = new Locale(language, country);
+
+                if (finalSize == 0) {
+                    if (DEBUG) {
+                        Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l)));
+                    }
+                    preprocess[finalSize++] =
+                            new Loc(toTitleCase(l.getDisplayLanguage(l)), l);
+                } else {
+                    // check previous entry:
+                    //  same lang and a country -> upgrade to full name and
+                    //    insert ours with full name
+                    //  diff lang -> insert ours with lang-only name
+                    if (preprocess[finalSize-1].locale.getLanguage().equals(
+                            language)) {
+                        if (DEBUG) {
+                            Log.v(TAG, "backing up and fixing "+
+                                    preprocess[finalSize-1].label+" to "+
+                                    getDisplayName(preprocess[finalSize-1].locale));
+                        }
+                        preprocess[finalSize-1].label = toTitleCase(
+                                getDisplayName(preprocess[finalSize-1].locale));
+                        if (DEBUG) {
+                            Log.v(TAG, "  and adding "+ toTitleCase(getDisplayName(l)));
+                        }
+                        preprocess[finalSize++] =
+                                new Loc(toTitleCase(getDisplayName(l)), l);
+                    } else {
+                        String displayName;
+                        if (s.equals("zz_ZZ")) {
+                            displayName = "Pseudo...";
+                        } else {
+                            displayName = toTitleCase(l.getDisplayLanguage(l));
+                        }
+                        if (DEBUG) {
+                            Log.v(TAG, "adding "+displayName);
+                        }
+                        preprocess[finalSize++] = new Loc(displayName, l);
+                    }
+                }
+            }
+        }
+        mLocales = new Loc[finalSize];
+        for (int i = 0; i < finalSize ; i++) {
+            mLocales[i] = preprocess[i];
+        }
+        Arrays.sort(mLocales);
+        final int layoutId = R.layout.locale_picker_item;
+        final int fieldId = R.id.locale;
+        final ArrayAdapter<Loc> adapter =
+                new ArrayAdapter<Loc>(activity, layoutId, fieldId, mLocales);
+        setListAdapter(adapter);
+    }
+
+    @Override
+    public void onActivityCreated(final Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        setUpLocaleList();
+    }
+
+    public void setLocaleSelectionListener(LocaleSelectionListener listener) {
+        mListener = listener;
+    }
+
+    private static String toTitleCase(String s) {
+        if (s.length() == 0) {
+            return s;
+        }
+
+        return Character.toUpperCase(s.charAt(0)) + s.substring(1);
+    }
+
+    private String getDisplayName(Locale l) {
+        String code = l.toString();
+
+        for (int i = 0; i < mSpecialLocaleCodes.length; i++) {
+            if (mSpecialLocaleCodes[i].equals(code)) {
+                return mSpecialLocaleNames[i];
+            }
+        }
+
+        return l.getDisplayName(l);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getListView().requestFocus();
+    }
+
+    /**
+     * Each listener needs to call {@link #updateLocale(Locale)} to actually change the locale.
+     *
+     * We don't call {@link #updateLocale(Locale)} automatically, as it halt the system for
+     * a moment and some callers won't want it.
+     */
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        if (mListener != null) {
+            mListener.onLocaleSelected(mLocales[position].locale);
+        }
+    }
+
+    /**
+     * Requests the system to update the system locale. Note that the system looks halted
+     * for a while during the Locale migration, so the caller need to take care of it.
+     */
+    public static void updateLocale(Locale locale) {
+        try {
+            IActivityManager am = ActivityManagerNative.getDefault();
+            Configuration config = am.getConfiguration();
+
+            config.locale = locale;
+
+            // indicate this isn't some passing default - the user wants this remembered
+            config.userSetLocale = true;
+
+            am.updateConfiguration(config);
+            // Trigger the dirty bit for the Settings Provider.
+            BackupManager.dataChanged("com.android.providers.settings");
+        } catch (RemoteException e) {
+            // Intentionally left blank
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 2ed4773..216d985 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, int viewId);
 }
 
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 496aa1a..4d56745 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -40,7 +40,9 @@
     // for AppWidgetManager
     //
     void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
+    void partiallyUpdateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
     void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views);
+    void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, 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/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 66149ac..8ea02aa 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -18,6 +18,7 @@
 
 import com.android.internal.util.JournaledFile;
 
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.net.TrafficStats;
 import android.os.BatteryManager;
@@ -50,6 +51,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -1150,7 +1152,11 @@
 
     private int getCurrentBluetoothPingCount() {
         if (mBtHeadset != null) {
-            return mBtHeadset.getBatteryUsageHint();
+            Set<BluetoothDevice> deviceSet = mBtHeadset.getConnectedDevices();
+            BluetoothDevice[] devices = deviceSet.toArray(new BluetoothDevice[deviceSet.size()]);
+            if (devices.length > 0) {
+                return mBtHeadset.getBatteryUsageHint(devices[0]);
+            }
         }
         return -1;
     }
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 59600dc..5767832 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -24,6 +24,7 @@
 import android.os.Process;
 import android.os.SystemProperties;
 import android.util.Config;
+import android.util.Finalizers;
 import android.util.Log;
 import android.util.Slog;
 
@@ -141,6 +142,12 @@
             Debug.enableEmulatorTraceOutput();
         }
 
+        /**
+         * Initialize the thread used to reclaim resources without
+         * going through finalizers.
+         */
+        Finalizers.init();
+
         initialized = true;
     }
 
@@ -331,9 +338,6 @@
         }
     }
 
-    /** Counter used to prevent reentrancy in {@link #reportException}. */
-    private static final AtomicInteger sInReportException = new AtomicInteger();
-
     /**
      * Set the object identifying this application/process, for reporting VM
      * errors.
diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
index ea278a6..b87ac90 100644
--- a/core/java/com/android/internal/os/SamplingProfilerIntegration.java
+++ b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
@@ -26,7 +26,9 @@
 import java.io.PrintStream;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
 
+import android.content.pm.PackageInfo;
 import android.util.Log;
 import android.os.*;
 
@@ -37,15 +39,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.");
         }
     }
@@ -69,48 +83,47 @@
         ThreadGroup group = Thread.currentThread().getThreadGroup();
         SamplingProfiler.ThreadSet threadSet = SamplingProfiler.newThreadGroupTheadSet(group);
         INSTANCE = new SamplingProfiler(4, threadSet);
-        INSTANCE.start(10);
+        INSTANCE.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);
                     }
                 }
             });
@@ -124,15 +137,15 @@
         if (!enabled) {
             return;
         }
-
-        String dir = "/data/zygote/snapshots";
-        new File(dir).mkdirs();
-        writeSnapshot(dir, "zygote");
+        writeSnapshot("zygote", null);
         INSTANCE.shutdown();
         INSTANCE = 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) {
         if (!enabled) {
             return;
         }
@@ -144,40 +157,55 @@
          * we capture two snapshots in rapid succession.
          */
         long start = System.currentTimeMillis();
-        String path = dir + "/" + name.replace(':', '.') + "-"
-                + System.currentTimeMillis() + ".snapshot";
-
-        // Try to open the file a few times. The SD card may not be mounted.
-        PrintStream out;
-        int count = 0;
-        while (true) {
-            try {
-                out = new PrintStream(new BufferedOutputStream(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 */ }
-            }
-        }
-
+        String name = processName.replaceAll(":", ".");
+        String path = dir + "/" + name + "-" +System.currentTimeMillis() + ".snapshot";
+        PrintStream out = null;
         try {
+            out = new PrintStream(new BufferedOutputStream(new FileOutputStream(path)));
+        } catch (IOException e) {
+            Log.e(TAG, "Could not open " + path + ":" + e);
+            return;
+        }
+        try {
+            generateSnapshotHeader(name, packageInfo, out);
             INSTANCE.writeHprofData(out);
         } finally {
             out.close();
         }
         if (out.checkError()) {
             Log.e(TAG, "Error writing snapshot.");
-        } else {
-            long elapsed = System.currentTimeMillis() - start;
-            Log.i(TAG, "Wrote snapshot for " + name
-                  + " in " + elapsed + "ms.");
+            return;
         }
+        // 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,
+            PrintStream out) {
+        // profiler version
+        out.println("Version: 2");
+        out.println("Process: " + processName);
+        if (packageInfo != null) {
+            out.println("Package: " + packageInfo.packageName);
+            out.println("Package-Version: " + packageInfo.versionCode);
+        }
+        out.println("Build: " + Build.FINGERPRINT);
+        // single blank line means the end of snapshot header.
+        out.println();
     }
 }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a409ec8..f0f3387 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -22,6 +22,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.LocalServerSocket;
 import android.os.Debug;
+import android.os.FileUtils;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Config;
@@ -506,6 +507,9 @@
 
         closeServerSocket();
 
+        // set umask to 0077 so new files and directories will default to owner-only permissions.
+        FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);
+
         /*
          * Pass the remaining arguments to SystemServer.
          * "--nice-name=system_server com.android.server.SystemServer"
@@ -523,7 +527,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/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 4501bd7..e884af8 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -30,5 +30,6 @@
     void disable(int state);
     void animateExpand();
     void animateCollapse();
+    void setLightsOn(boolean on);
 }
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 852630d..0763f5e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -30,14 +30,18 @@
     void setIcon(String slot, String iconPackage, int iconId, int iconLevel);
     void setIconVisibility(String slot, boolean visible);
     void removeIcon(String slot);
+    void setActiveWindowIsFullscreen(boolean fullscreen);
 
     // ---- Methods below are for use by the status bar policy services ----
     // You need the STATUS_BAR_SERVICE permission
     void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList,
-            out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications);
+            out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications,
+            out boolean[] lightsOn);
     void onPanelRevealed();
     void onNotificationClick(String pkg, String tag, int id);
     void onNotificationError(String pkg, String tag, int id,
             int uid, int initialPid, String message);
     void onClearAllNotifications();
+    void onNotificationClear(String pkg, String tag, int id);
+    void setLightsOn(boolean on);
 }
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java
index aa340fb..2b96bf6 100644
--- a/core/java/com/android/internal/statusbar/StatusBarNotification.java
+++ b/core/java/com/android/internal/statusbar/StatusBarNotification.java
@@ -121,6 +121,10 @@
         return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
     }
 
+    public boolean isClearable() {
+        return ((notification.flags & Notification.FLAG_ONGOING_EVENT) == 0)
+                && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
+    }
 }
 
 
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java
index f789301..3030316 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/TypedProperties.java b/core/java/com/android/internal/util/TypedProperties.java
index c2ce210..5613999 100644
--- a/core/java/com/android/internal/util/TypedProperties.java
+++ b/core/java/com/android/internal/util/TypedProperties.java
@@ -412,7 +412,7 @@
      */
 
     /**
-     * An unchecked exception that is thrown if a {@code get&lt;TYPE&gt;()} method
+     * An unchecked exception that is thrown if a {@code get<TYPE>()} method
      * is used to retrieve a parameter whose type does not match the method name.
      */
     public static class TypeException extends IllegalArgumentException {
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/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 4da74e6..d5213db 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,11 +16,14 @@
 
 package com.android.internal.view;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.view.DragEvent;
 import android.view.IWindow;
 import android.view.IWindowSession;
 import android.view.KeyEvent;
@@ -66,7 +69,10 @@
             }
         }
     }
-    
+
+    public void dispatchDragEvent(DragEvent event) {
+    }
+
     public void dispatchWallpaperCommand(String action, int x, int y,
             int z, Bundle extras, boolean sync) {
         if (sync) {
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 8ff18ed..c7fcab8 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -23,6 +23,7 @@
 import android.view.MotionEvent;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethodSubtype;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodCallback;
 import com.android.internal.view.IInputMethodSession;
@@ -52,4 +53,6 @@
     void showSoftInput(int flags, in ResultReceiver resultReceiver);
     
     void hideSoftInput(int flags, in ResultReceiver resultReceiver);
+
+    void changeInputMethodSubtype(in InputMethodSubtype subtype);
 }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index adec0a7..d012b0f 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -48,6 +48,7 @@
             int softInputMode, boolean first, int windowFlags);
             
     void showInputMethodPickerFromClient(in IInputMethodClient client);
+    void showInputMethodSubtypePickerFromClient(in IInputMethodClient client);
     void setInputMethod(in IBinder token, String id);
     void hideMySoftInput(in IBinder token, int flags);
     void showMySoftInput(in IBinder token, int flags);
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..d381901
--- /dev/null
+++ b/core/java/com/android/internal/view/StandaloneActionMode.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.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).setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        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) {
+        invalidate();
+        mContextView.showOverflowMenu();
+    }
+}
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..d2851a9
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.ContextMenu.ContextMenuInfo;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+
+/**
+ * @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.
+    }
+
+    public MenuItem setActionView(View actionView) {
+        throw new UnsupportedOperationException();
+    }
+
+    public View getActionView() {
+        return null;
+    }
+}
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..af0438c
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -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.
+ */
+
+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());
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        mImageButton.setEnabled(enabled);
+        mTextButton.setEnabled(enabled);
+    }
+
+    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..20939ab
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Gravity;
+import android.view.View;
+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;
+    private MenuPopupHelper mOverflowPopup;
+
+    private Runnable mShowOverflow = new Runnable() {
+        public void run() {
+            showOverflowMenu();
+        }
+    };
+    
+    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();
+
+        // Measure for initial configuration
+        mMaxItems = measureMaxActionButtons();
+
+        // TODO There has to be a better way to indicate that we don't have a hard menu key.
+        final int screen = getResources().getConfiguration().screenLayout;
+        mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
+                Configuration.SCREENLAYOUT_SIZE_XLARGE;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        final int screen = newConfig.screenLayout;
+        mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
+                Configuration.SCREENLAYOUT_SIZE_XLARGE;
+        mMaxItems = measureMaxActionButtons();
+        if (mMenu != null) {
+            mMenu.setMaxActionItems(mMaxItems);
+            updateChildren(false);
+        }
+
+        if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
+            mOverflowPopup.dismiss();
+            post(mShowOverflow);
+        }
+    }
+
+    private int measureMaxActionButtons() {
+        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;
+        
+        return spaceAvailable / (itemSpace > 0 ? itemSpace : 1);
+    }
+
+    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.gravity == Gravity.CENTER_VERTICAL &&
+                    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;
+        params.gravity = Gravity.CENTER_VERTICAL;
+        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);
+            final View actionView = itemData.getActionView();
+            if (actionView != null) {
+                addView(actionView);
+            } else {
+                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) {
+            final MenuPopupHelper popup =
+                    new MenuPopupHelper(getContext(), mMenu, mOverflowButton, true);
+            // Post this for later; we might still need a layout for the anchor to be right.
+            post(new Runnable() {
+                public void run() {
+                    popup.show();
+                }
+            });
+            mOverflowPopup = popup;
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isOverflowMenuShowing() {
+        MenuPopupHelper popup = mOverflowPopup;
+        if (popup != null) {
+            return popup.isShowing();
+        }
+        return false;
+    }
+
+    public boolean hideOverflowMenu() {
+        MenuPopupHelper popup = mOverflowPopup;
+        if (popup != null) {
+            popup.dismiss();
+            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.actionOverflowButtonStyle);
+
+            final Resources res = context.getResources();
+            setClickable(true);
+            setFocusable(true);
+            setVisibility(VISIBLE);
+            setEnabled(true);
+        }
+
+        @Override
+        public boolean performClick() {
+            if (super.performClick()) {
+                return true;
+            }
+
+            // Change to overflow mode
+            mMenu.getCallback().onMenuModeChange(mMenu);
+            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..d517d4c 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,27 @@
      * 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_*
+    // Special values:
+    // 0: Use the system default theme
+    // -1: Use the app's own theme
     static final int THEME_RES_FOR_TYPE[] = new int[] {
         com.android.internal.R.style.Theme_IconMenu,
         com.android.internal.R.style.Theme_ExpandedMenu,
-        0,
+        com.android.internal.R.style.Theme_Light,
+        -1,
+        -1,
     };
     
     // Order must be the same order as the TYPE_*
@@ -80,6 +94,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 +103,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.popup_menu_item_layout,
     };
 
     private static final int[]  sCategoryToOrder = new int[] {
@@ -130,6 +148,35 @@
      * 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;
+
+    /**
+     * Default value for how added items should show in the action list.
+     */
+    private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
 
     /**
      * Current use case is Context Menus: As Views populate the context menu, each one has
@@ -176,8 +223,9 @@
         LayoutInflater getInflater() {
             // Create an inflater that uses the given theme for the Views it inflates
             if (mInflater == null) {
-                Context wrappedContext = new ContextThemeWrapper(mContext,
-                        THEME_RES_FOR_TYPE[mMenuType]); 
+                int themeResForType = THEME_RES_FOR_TYPE[mMenuType];
+                Context wrappedContext = themeResForType < 0 ? mContext :
+                        new ContextThemeWrapper(mContext, themeResForType);
                 mInflater = (LayoutInflater) wrappedContext
                         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
             }
@@ -281,10 +329,19 @@
         mVisibleItems = new ArrayList<MenuItemImpl>();
         mIsVisibleItemsStale = true;
         
+        mActionItems = new ArrayList<MenuItemImpl>();
+        mNonActionItems = new ArrayList<MenuItemImpl>();
+        mIsActionItemsStale = true;
+        
         mShortcutsVisible =
                 (mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS);
     }
     
+    public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) {
+        mDefaultShowAsAction = defaultShowAsAction;
+        return this;
+    }
+    
     public void setCallback(Callback callback) {
         mCallback = callback;
     }
@@ -368,7 +425,8 @@
     private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
         final int ordering = getOrdering(categoryOrder);
         
-        final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder, ordering, title);
+        final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
+                ordering, title, mDefaultShowAsAction);
 
         if (mCurrentMenuInfo != null) {
             // Pass along the current menu info
@@ -633,6 +691,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 +963,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 +984,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 +1007,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 +1225,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 +1312,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..42f9771 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -74,6 +74,11 @@
     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;
+
+    private View mActionView;
 
     /** Used for the icon resource ID if this item does not have an icon */
     static final int NO_ICON = 0;
@@ -105,7 +110,7 @@
      * @param title The text to display for the item.
      */
     MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
-            CharSequence title) {
+            CharSequence title, int showAsAction) {
 
         if (sPrependShortcutLabel == null) {
             // This is instantiated from the UI thread, so no chance of sync issues 
@@ -126,6 +131,7 @@
         mCategoryOrder = categoryOrder;
         mOrdering = ordering;
         mTitle = title;
+        mShowAsAction = showAsAction;
     }
     
     /**
@@ -580,6 +586,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 +638,43 @@
      * @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);
+    }
+
+    public MenuItem setActionView(View view) {
+        mActionView = view;
+        return this;
+    }
+
+    public View getActionView() {
+        return mActionView;
     }
 }
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..c947824
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.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.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 android.widget.PopupWindow;
+
+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;
+
+    private PopupWindow.OnDismissListener mDismissListener = new PopupWindow.OnDismissListener() {
+        public void onDismiss() {
+            mPopup = null;
+        }
+    };
+
+    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() {
+        mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
+        mPopup.setOnItemClickListener(this);
+        mPopup.setOnDismissListener(mDismissListener);
+
+        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() {
+        if (isShowing()) {
+            mPopup.dismiss();
+        }
+    }
+
+    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..b5e57c2
--- /dev/null
+++ b/core/java/com/android/internal/widget/ActionBarContextView.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 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.view.View.MeasureSpec;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class ActionBarContextView extends ViewGroup {
+    private int mItemPadding;
+    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 int mTitleStyleRes;
+    private int mSubtitleStyleRes;
+    private ActionMenuView mMenuView;
+    
+    public ActionBarContextView(Context context) {
+        this(context, null);
+    }
+    
+    public ActionBarContextView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.actionModeStyle);
+    }
+    
+    public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0);
+        mItemPadding = a.getDimensionPixelOffset(
+                com.android.internal.R.styleable.ActionMode_itemPadding, 0);
+        setBackgroundDrawable(a.getDrawable(
+                com.android.internal.R.styleable.ActionMode_background));
+        mTitleStyleRes = a.getResourceId(
+                com.android.internal.R.styleable.ActionMode_titleTextStyle, 0);
+        mSubtitleStyleRes = a.getResourceId(
+                com.android.internal.R.styleable.ActionMode_subtitleTextStyle, 0);
+
+        mContentHeight = a.getLayoutDimension(
+                com.android.internal.R.styleable.ActionMode_height, 0);
+        a.recycle();
+    }
+    
+    @Override
+    public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
+        // No starting an action mode for an existing action mode UI child! (Where would it go?)
+        return null;
+    }
+
+    public void setHeight(int height) {
+        mContentHeight = height;
+    }
+
+    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 (mTitleStyleRes != 0) {
+                    mTitleView.setTextAppearance(mContext, mTitleStyleRes);
+                }
+            }
+            if (mSubtitle != null) {
+                mSubtitleView.setText(mSubtitle);
+                if (mSubtitleStyleRes != 0) {
+                    mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
+                }
+                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(), null,
+                    com.android.internal.R.attr.actionModeCloseButtonStyle);
+        }
+        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;
+    }
+
+    public boolean showOverflowMenu() {
+        if (mMenuView != null) {
+            return mMenuView.showOverflowMenu();
+        }
+        return false;
+    }
+
+    public boolean hideOverflowMenu() {
+        if (mMenuView != null) {
+            return mMenuView.hideOverflowMenu();
+        }
+        return false;
+    }
+
+    public boolean isOverflowMenuShowing() {
+        if (mMenuView != null) {
+            return mMenuView.isOverflowMenuShowing();
+        }
+        return false;
+    }
+
+    @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 maxHeight = mContentHeight > 0 ?
+                mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
+
+        final int verticalPadding = getPaddingTop() + getPaddingBottom();
+        int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
+        final int height = maxHeight - verticalPadding;
+        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));
+        }
+
+        if (mContentHeight <= 0) {
+            int measuredHeight = 0;
+            final int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                View v = getChildAt(i);
+                int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
+                if (paddedViewHeight > measuredHeight) {
+                    measuredHeight = paddedViewHeight;
+                }
+            }
+            setMeasuredDimension(contentWidth, measuredHeight);
+        } else {
+            setMeasuredDimension(contentWidth, maxHeight);
+        }
+    }
+
+    @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..308a709
--- /dev/null
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -0,0 +1,771 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.ActionBar.NavigationCallback;
+import android.app.Activity;
+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.ActionMode;
+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.HorizontalScrollView;
+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_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 HorizontalScrollView mTabScrollView;
+    private LinearLayout mTabLayout;
+    private View mCustomNavView;
+    
+    private int mTitleStyleRes;
+    private int mSubtitleStyleRes;
+
+    private boolean mShowMenu;
+    private boolean mUserTitle;
+
+    private MenuBuilder mOptionsMenu;
+    private ActionMenuView mMenuView;
+    
+    private ActionBarContextView mContextView;
+
+    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;
+
+    private OnClickListener mTabClickListener = null;
+
+    public ActionBarView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+
+        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);
+        }
+        
+        mTitleStyleRes = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
+        mSubtitleStyleRes = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
+
+        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;
+        }
+
+        mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
+
+        a.recycle();
+
+        // TODO: Set this in the theme
+        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);
+                    }
+                }
+            };
+        }
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
+        // No starting an action mode for an action bar child! (Where would it go?)
+        return null;
+    }
+
+    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 void postShowOverflowMenu() {
+        post(new Runnable() {
+            public void run() {
+                showOverflowMenu();
+            }
+        });
+    }
+
+    public boolean hideOverflowMenu() {
+        if (mMenuView != null) {
+            return mMenuView.hideOverflowMenu();
+        }
+        return false;
+    }
+
+    public boolean isOverflowMenuShowing() {
+        if (mMenuView != null) {
+            return mMenuView.isOverflowMenuShowing();
+        }
+        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(mTabScrollView);
+                    mTabLayout = null;
+                    mTabScrollView = 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:
+                mTabScrollView = new HorizontalScrollView(getContext());
+                mTabLayout = new LinearLayout(getContext(), null,
+                        com.android.internal.R.attr.actionBarTabBarStyle);
+                mTabScrollView.addView(mTabLayout);
+                addView(mTabScrollView);
+                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);
+
+        if (mTabClickListener == null) {
+            mTabClickListener = new TabClickListener();
+        }
+        tabView.setOnClickListener(mTabClickListener);
+        return tabView;
+    }
+
+    public void addTab(ActionBar.Tab tab) {
+        final boolean isFirst = mTabLayout.getChildCount() == 0;
+        View tabView = createTabView(tab);
+        mTabLayout.addView(tabView);
+        if (isFirst) {
+            tabView.setSelected(true);
+        }
+    }
+
+    public void addTab(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 (mTitleStyleRes != 0) {
+            mTitleView.setTextAppearance(mContext, mTitleStyleRes);
+        }
+        if (mTitle != null) {
+            mTitleView.setText(mTitle);
+        }
+
+        if (mSubtitleStyleRes != 0) {
+            mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
+        }
+        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);
+        }
+    }
+
+    public void setContextView(ActionBarContextView view) {
+        mContextView = view;
+    }
+
+    @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 maxHeight = mContentHeight > 0 ?
+                mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
+        
+        final int verticalPadding = getPaddingTop() + getPaddingBottom();
+        int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
+        final int height = maxHeight - verticalPadding;
+        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.AT_MOST));
+            }
+            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;
+
+                // If the action bar is wrapping to its content height, don't allow a custom
+                // view to MATCH_PARENT.
+                int customNavHeightMode;
+                if (mContentHeight <= 0) {
+                    customNavHeightMode = MeasureSpec.AT_MOST;
+                } else {
+                    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 (mTabScrollView != null) {
+                mTabScrollView.measure(
+                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+            }
+            break;
+        }
+
+        if (mContentHeight <= 0) {
+            int measuredHeight = 0;
+            final int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                View v = getChildAt(i);
+                int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
+                if (paddedViewHeight > measuredHeight) {
+                    measuredHeight = paddedViewHeight;
+                }
+            }
+            setMeasuredDimension(contentWidth, measuredHeight);
+        } else {
+            setMeasuredDimension(contentWidth, maxHeight);
+        }
+
+        if (mContextView != null) {
+            mContextView.setHeight(getMeasuredHeight());
+        }
+    }
+
+    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 (mTabScrollView != null) {
+                x += positionChild(mTabScrollView, 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, null, com.android.internal.R.attr.actionBarTabStyle);
+            mTab = tab;
+
+            final View custom = tab.getCustomView();
+            if (custom != null) {
+                addView(custom);
+            } else {
+                // 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, null,
+                            com.android.internal.R.attr.actionBarTabTextStyle);
+                    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/DrawableHolder.java b/core/java/com/android/internal/widget/DrawableHolder.java
new file mode 100644
index 0000000..d53860c
--- /dev/null
+++ b/core/java/com/android/internal/widget/DrawableHolder.java
@@ -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.
+ */
+
+package com.android.internal.widget;
+
+import java.util.ArrayList;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.Animator.AnimatorListener;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.Log;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This class is a container for a Drawable with multiple animated properties.
+ *
+ */
+public class DrawableHolder implements AnimatorListener {
+    public static final DecelerateInterpolator EASE_OUT_INTERPOLATOR = new DecelerateInterpolator();
+    private static final String TAG = "DrawableHolder";
+    private static final boolean DBG = false;
+    private float mX = 0.0f;
+    private float mY = 0.0f;
+    private float mScaleX = 1.0f;
+    private float mScaleY = 1.0f;
+    private BitmapDrawable mDrawable;
+    private float mAlpha = 1f;
+    private ArrayList<ObjectAnimator<Float>> mAnimators = new ArrayList<ObjectAnimator<Float>>();
+    private ArrayList<ObjectAnimator<Float>> mNeedToStart = new ArrayList<ObjectAnimator<Float>>();
+
+    public DrawableHolder(BitmapDrawable drawable) {
+        this(drawable, 0.0f, 0.0f);
+    }
+
+    public DrawableHolder(BitmapDrawable drawable, float x, float y) {
+        mDrawable = drawable;
+        mX = x;
+        mY = y;
+        mDrawable.getPaint().setAntiAlias(true); // Force AA
+        mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
+    }
+
+    /**
+     *
+     * Adds an animation that interpolates given property from its current value
+     * to the given value.
+     *
+     * @param duration the duration, in ms.
+     * @param delay the delay to start the animation, in ms.
+     * @param property the property to animate
+     * @param toValue the target value
+     * @param replace if true, replace the current animation with this one.
+     */
+    public ObjectAnimator<Float> addAnimTo(long duration, long delay,
+            String property, float toValue, boolean replace) {
+
+        if (replace) removeAnimationFor(property);
+
+        ObjectAnimator<Float> anim = new ObjectAnimator<Float>(duration, this, property, toValue);
+        anim.setStartDelay(delay);
+        anim.setInterpolator(EASE_OUT_INTERPOLATOR);
+        this.addAnimation(anim, replace);
+        if (DBG) Log.v(TAG, "animationCount = " + mAnimators.size());
+        return anim;
+    }
+
+    /**
+     * Stops all animations for the given property and removes it from the list.
+     *
+     * @param property
+     */
+    public void removeAnimationFor(String property) {
+        ArrayList<ObjectAnimator<Float>> removalList = new ArrayList<ObjectAnimator<Float>>();
+        for (ObjectAnimator<Float> currentAnim : mAnimators) {
+            if (property.equals(currentAnim.getPropertyName())) {
+                currentAnim.cancel();
+                removalList.add(currentAnim);
+            }
+        }
+        if (DBG) Log.v(TAG, "Remove list size: " + removalList.size());
+        mAnimators.removeAll(removalList);
+    }
+
+    /**
+     * Stops all animations and removes them from the list.
+     */
+    public void clearAnimations() {
+        for (ObjectAnimator<Float> currentAnim : mAnimators) {
+            currentAnim.cancel();
+        }
+        mAnimators.clear();
+    }
+
+    /**
+     * Adds the given animation to the list of animations for this object.
+     *
+     * @param anim
+     * @param overwrite
+     * @return
+     */
+    private DrawableHolder addAnimation(ObjectAnimator<Float> anim, boolean overwrite) {
+        if (anim != null)
+            mAnimators.add(anim);
+        mNeedToStart.add(anim);
+        return this;
+    }
+
+    /**
+     * Draw this object to the canvas using the properties defined in this class.
+     *
+     * @param canvas canvas to draw into
+     */
+    public void draw(Canvas canvas) {
+        final float threshold = 1.0f / 256.0f; // contribution less than 1 LSB of RGB byte
+        if (mAlpha <= threshold) // don't bother if it won't show up
+            return;
+        canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.translate(mX, mY);
+        canvas.scale(mScaleX, mScaleY);
+        canvas.translate(-0.5f*getWidth(), -0.5f*getHeight());
+        mDrawable.setAlpha((int) Math.round(mAlpha * 255f));
+        mDrawable.draw(canvas);
+        canvas.restore();
+    }
+
+    /**
+     * Starts all animations added since the last call to this function.  Used to synchronize
+     * animations.
+     *
+     * @param listener an optional listener to add to the animations. Typically used to know when
+     * to invalidate the surface these are being drawn to.
+     */
+    public void startAnimations(ValueAnimator.AnimatorUpdateListener listener) {
+        for (int i = 0; i < mNeedToStart.size(); i++) {
+            ObjectAnimator<Float> anim = mNeedToStart.get(i);
+            anim.addUpdateListener(listener);
+            anim.addListener(this);
+            anim.start();
+        }
+        mNeedToStart.clear();
+    }
+
+
+    public DrawableHolder setX(float value) {
+        mX = value;
+        return this;
+    }
+
+    public DrawableHolder setY(float value) {
+        mY = value;
+        return this;
+    }
+
+    public DrawableHolder setScaleX(float value) {
+        mScaleX = value;
+        return this;
+    }
+
+    public DrawableHolder setScaleY(float value) {
+        mScaleY = value;
+        return this;
+    }
+
+    public DrawableHolder setAlpha(float alpha) {
+        mAlpha = alpha;
+        return this;
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public float getScaleX() {
+        return mScaleX;
+    }
+
+    public float getScaleY() {
+        return mScaleY;
+    }
+
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    public BitmapDrawable getDrawable() {
+        return mDrawable;
+    }
+
+    public int getWidth() {
+        return mDrawable.getIntrinsicWidth();
+    }
+
+    public int getHeight() {
+        return mDrawable.getIntrinsicHeight();
+    }
+
+    public void onAnimationCancel(Animator animation) {
+
+    }
+
+    public void onAnimationEnd(Animator animation) {
+        mAnimators.remove(animation);
+    }
+
+    public void onAnimationRepeat(Animator animation) {
+
+    }
+
+    public void onAnimationStart(Animator animation) {
+
+    }
+}
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..ae9900c
--- /dev/null
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -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.
+ */
+
+package com.android.internal.widget;
+
+import android.widget.RemoteViews;
+
+/** {@hide} */
+interface IRemoteViewsFactory {
+    void onDataSetChanged();
+    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 c788605..90423be 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -96,6 +96,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;
@@ -163,6 +165,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.
      *
@@ -227,6 +256,33 @@
     }
 
     /**
+     * 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);
+    }
+
+    /**
      * Check to see if the user has stored a lock pattern.
      * @return Whether a saved pattern exists.
      */
@@ -279,6 +335,11 @@
                     activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
                 }
                 break;
+            case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+                if (isLockPasswordEnabled()) {
+                    activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+                }
+                break;
         }
         return activePasswordQuality;
     }
@@ -287,8 +348,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);
@@ -301,7 +360,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");
@@ -316,14 +375,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
@@ -381,17 +441,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
@@ -531,7 +633,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);
     }
 
     /**
@@ -655,12 +758,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/PasswordEntryKeyboard.java b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
index e1a6737..facda36 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
@@ -67,8 +67,22 @@
         this(context, xmlLayoutResId, 0);
     }
 
+    public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int width, int height) {
+        this(context, xmlLayoutResId, 0, width, height);
+    }
+
     public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int mode) {
         super(context, xmlLayoutResId, mode);
+        init(context);
+    }
+
+    public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int mode,
+            int width, int height) {
+        super(context, xmlLayoutResId, mode, width, height);
+        init(context);
+    }
+
+    private void init(Context context) {
         final Resources res = context.getResources();
         mRes = res;
         mShiftIcon = res.getDrawable(R.drawable.sym_keyboard_shift);
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
index 53720e4..384f7bc 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -54,10 +54,20 @@
     private Vibrator mVibrator;
 
     public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) {
+        this(context, keyboardView, targetView, true);
+    }
+
+    public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView,
+            boolean useFullScreenWidth) {
         mContext = context;
         mTargetView = targetView;
         mKeyboardView = keyboardView;
-        createKeyboards();
+        if (useFullScreenWidth || mKeyboardView.getLayoutParams().width == -1) {
+            createKeyboards();
+        } else {
+            createKeyboardsWithSpecificSize(mKeyboardView.getLayoutParams().width,
+                    mKeyboardView.getLayoutParams().height);
+        }
         mKeyboardView.setOnKeyboardActionListener(this);
         mVibrator = new Vibrator();
     }
@@ -66,6 +76,29 @@
         return mKeyboardMode == KEYBOARD_MODE_ALPHA;
     }
 
+    private void createKeyboardsWithSpecificSize(int viewWidth, int viewHeight) {
+        mNumericKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_numeric,
+                viewWidth, viewHeight);
+        mQwertyKeyboard = new PasswordEntryKeyboard(mContext,
+                R.xml.password_kbd_qwerty, R.id.mode_normal, viewWidth, viewHeight);
+        mQwertyKeyboard.enableShiftLock();
+
+        mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext,
+                R.xml.password_kbd_qwerty_shifted,
+                R.id.mode_normal, viewWidth, viewHeight);
+        mQwertyKeyboardShifted.enableShiftLock();
+        mQwertyKeyboardShifted.setShifted(true); // always shifted.
+
+        mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_symbols,
+                viewWidth, viewHeight);
+        mSymbolsKeyboard.enableShiftLock();
+
+        mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext,
+                R.xml.password_kbd_symbols_shift, viewWidth, viewHeight);
+        mSymbolsKeyboardShifted.enableShiftLock();
+        mSymbolsKeyboardShifted.setShifted(true); // always shifted
+    }
+
     private void createKeyboards() {
         mNumericKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_numeric);
         mQwertyKeyboard = new PasswordEntryKeyboard(mContext,
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index 9152729..3865510 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.
@@ -417,7 +416,7 @@
         }
 
         /**
-         * Start animating the slider. Note we need two animations since an Animator
+         * Start animating the slider. Note we need two animations since an ValueAnimator
          * keeps internal state of the invalidation region which is just the view being animated.
          *
          * @param anim1
@@ -475,10 +474,13 @@
         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
 
-        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
-            Log.e("SlidingTab", "SlidingTab cannot have UNSPECIFIED MeasureSpec"
-                    +"(wspec=" + widthSpecMode + ", hspec=" + heightSpecMode + ")",
-                    new RuntimeException(LOG_TAG + "stack:"));
+        if (DBG) {
+            if (widthSpecMode == MeasureSpec.UNSPECIFIED 
+                    || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+                Log.e("SlidingTab", "SlidingTab cannot have UNSPECIFIED MeasureSpec"
+                        +"(wspec=" + widthSpecMode + ", hspec=" + heightSpecMode + ")",
+                        new RuntimeException(LOG_TAG + "stack:"));
+            }
         }
 
         mLeftSlider.measure();
@@ -608,14 +610,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 +618,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 +838,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/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java
new file mode 100644
index 0000000..f4ee7ee
--- /dev/null
+++ b/core/java/com/android/internal/widget/WaveView.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.util.ArrayList;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.internal.R;
+
+/**
+ * A special widget containing a center and outer ring. Moving the center ring to the outer ring
+ * causes an event that can be caught by implementing OnTriggerListener.
+ */
+public class WaveView extends View implements ValueAnimator.AnimatorUpdateListener {
+    private static final String TAG = "WaveView";
+    private static final boolean DBG = false;
+    private static final int WAVE_COUNT = 5; // default wave count
+    private static final long VIBRATE_SHORT = 20;  // msec
+    private static final long VIBRATE_LONG = 20;  // msec
+
+    // Lock state machine states
+    private static final int STATE_RESET_LOCK = 0;
+    private static final int STATE_READY = 1;
+    private static final int STATE_START_ATTEMPT = 2;
+    private static final int STATE_ATTEMPTING = 3;
+    private static final int STATE_UNLOCK_ATTEMPT = 4;
+    private static final int STATE_UNLOCK_SUCCESS = 5;
+
+    // Animation properties.
+    private static final long DURATION = 500; // duration of transitional animations
+    private static final long FINAL_DELAY = 1300; // delay for final animations
+    private static final long SHORT_DELAY = 100; // for starting one animation after another.
+    private static final long WAVE_DURATION = 2000; // amount of time for way to expand/decay
+    private static final long RESET_TIMEOUT = 3000; // elapsed time of inactivity before we reset
+    private static final long DELAY_INCREMENT = 15; // increment per wave while tracking motion
+    private static final long DELAY_INCREMENT2 = 12; // increment per wave while not tracking
+
+    private Vibrator mVibrator;
+    private OnTriggerListener mOnTriggerListener;
+    private ArrayList<DrawableHolder> mDrawables = new ArrayList<DrawableHolder>(3);
+    private ArrayList<DrawableHolder> mLightWaves = new ArrayList<DrawableHolder>(WAVE_COUNT);
+    private boolean mFingerDown = false;
+    private float mRingRadius = 182.0f; // Radius of bitmap ring. Used to snap halo to it
+    private int mSnapRadius = 136; // minimum threshold for drag unlock
+    private int mWaveDelay = 240; // time to delay
+    private int mWaveCount = WAVE_COUNT;  // number of waves
+    private long mWaveTimerDelay = mWaveDelay;
+    private int mCurrentWave = 0;
+    private float mLockCenterX; // center of widget as dictated by widget size
+    private float mLockCenterY;
+    private float mMouseX; // current mouse position as of last touch event
+    private float mMouseY;
+    private DrawableHolder mUnlockRing;
+    private DrawableHolder mUnlockDefault;
+    private DrawableHolder mUnlockHalo;
+    private int mLockState = STATE_RESET_LOCK;
+    private int mGrabbedState = OnTriggerListener.NO_HANDLE;
+
+    public WaveView(Context context) {
+        this(context, null);
+    }
+
+    public WaveView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WaveView);
+        // mOrientation = a.getInt(R.styleable.WaveView_orientation, HORIZONTAL);
+        // a.recycle();
+
+        initDrawables();
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mLockCenterX = 0.5f * w;
+        mLockCenterY = 0.5f * h;
+        super.onSizeChanged(w, h, oldw, oldh);
+    }
+
+    @Override
+    protected int getSuggestedMinimumWidth() {
+        // View should be large enough to contain the unlock ring + halo
+        return mUnlockRing.getWidth() + mUnlockHalo.getWidth();
+    }
+
+    @Override
+    protected int getSuggestedMinimumHeight() {
+        // View should be large enough to contain the unlock ring + halo
+        return mUnlockRing.getHeight() + mUnlockHalo.getHeight();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+        int width;
+        int height;
+
+        if (widthSpecMode == MeasureSpec.AT_MOST) {
+            width = Math.min(widthSpecSize, getSuggestedMinimumWidth());
+        } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+            width = widthSpecSize;
+        } else {
+            width = getSuggestedMinimumWidth();
+        }
+
+        if (heightSpecMode == MeasureSpec.AT_MOST) {
+            height = Math.min(heightSpecSize, getSuggestedMinimumWidth());
+        } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+            height = heightSpecSize;
+        } else {
+            height = getSuggestedMinimumHeight();
+        }
+
+        setMeasuredDimension(width, height);
+    }
+
+    private void initDrawables() {
+        mUnlockRing = new DrawableHolder(createDrawable(R.drawable.unlock_ring))
+            .setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f).setAlpha(0.0f);
+        mDrawables.add(mUnlockRing);
+
+        mUnlockDefault = new DrawableHolder(createDrawable(R.drawable.unlock_default))
+            .setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f).setAlpha(0.0f);
+        mDrawables.add(mUnlockDefault);
+
+        mUnlockHalo = new DrawableHolder(createDrawable(R.drawable.unlock_halo))
+            .setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f).setAlpha(0.0f);
+        mDrawables.add(mUnlockHalo);
+
+        BitmapDrawable wave = createDrawable(R.drawable.unlock_wave);
+        for (int i = 0; i < mWaveCount; i++) {
+            DrawableHolder holder = new DrawableHolder(wave);
+            mLightWaves.add(holder);
+            holder.setAlpha(0.0f);
+        }
+    }
+
+    private void waveUpdateFrame(float mouseX, float mouseY, boolean fingerDown) {
+        double distX = mouseX - mLockCenterX;
+        double distY = mouseY - mLockCenterY;
+        int dragDistance = (int) Math.ceil(Math.hypot(distX, distY));
+        double touchA = Math.atan2(distX, distY);
+        float ringX = (float) (mLockCenterX + mRingRadius * Math.sin(touchA));
+        float ringY = (float) (mLockCenterY + mRingRadius * Math.cos(touchA));
+
+        switch (mLockState) {
+            case STATE_RESET_LOCK:
+                if (DBG) Log.v(TAG, "State RESET_LOCK");
+                mWaveTimerDelay = mWaveDelay;
+                for (int i = 0; i < mLightWaves.size(); i++) {
+                    //TweenMax.to(mLightWave.get(i), .3, {alpha:0, ease:Quint.easeOut});
+                    DrawableHolder holder = mLightWaves.get(i);
+                    holder.addAnimTo(300, 0, "alpha", 0.0f, false);
+                }
+                for (int i = 0; i < mLightWaves.size(); i++) {
+                    mLightWaves.get(i).startAnimations(this);
+                }
+
+                //TweenMax.to(unlockRing, .5, { x: lockX, y: lockY, scaleX: .1, scaleY: .1,
+                // alpha: 0, overwrite: true, ease:Quint.easeOut   });
+                mUnlockRing.addAnimTo(DURATION, 0, "x", mLockCenterX, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "y", mLockCenterY, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 0.1f, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 0.1f, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "alpha", 0.0f, true);
+
+                //TweenMax.to(unlockDefault, 0, { x: lockX, y: lockY, scaleX: .1, scaleY: .1,
+                // alpha: 0  , overwrite: true                       });
+                mUnlockDefault.removeAnimationFor("x");
+                mUnlockDefault.removeAnimationFor("y");
+                mUnlockDefault.removeAnimationFor("scaleX");
+                mUnlockDefault.removeAnimationFor("scaleY");
+                mUnlockDefault.removeAnimationFor("alpha");
+                mUnlockDefault.setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f)
+                        .setAlpha(0.0f);
+
+                //TweenMax.to(unlockDefault, .5, { delay: .1, scaleX: 1, scaleY: 1,
+                // alpha: 1, overwrite: true, ease:Quint.easeOut   });
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
+
+                //TweenMax.to(unlockHalo, 0, { x: lockX, y: lockY, scaleX:.1, scaleY: .1,
+                // alpha: 0  , overwrite: true                       });
+                mUnlockHalo.removeAnimationFor("x");
+                mUnlockHalo.removeAnimationFor("y");
+                mUnlockHalo.removeAnimationFor("scaleX");
+                mUnlockHalo.removeAnimationFor("scaleY");
+                mUnlockHalo.removeAnimationFor("alpha");
+                mUnlockHalo.setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f)
+                        .setAlpha(0.0f);
+
+                //TweenMax.to(unlockHalo, .5, { x: lockX, y: lockY, scaleX: 1, scaleY: 1,
+                // alpha: 1  , overwrite: true, ease:Quint.easeOut   });
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "x", mLockCenterX, true);
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "y", mLockCenterY, true);
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
+
+                //lockTimer.stop();
+                removeCallbacks(mLockTimerActions);
+
+                mLockState = STATE_READY;
+                break;
+
+            case STATE_READY:
+                if (DBG) Log.v(TAG, "State READY");
+                break;
+
+            case STATE_START_ATTEMPT:
+                if (DBG) Log.v(TAG, "State START_ATTEMPT");
+                //TweenMax.to(unlockDefault, 0, {scaleX: .1, scaleY:.1, alpha: 0,
+                // x:lockX +182, y: lockY  , overwrite: true   });
+                mUnlockDefault.removeAnimationFor("x");
+                mUnlockDefault.removeAnimationFor("y");
+                mUnlockDefault.removeAnimationFor("scaleX");
+                mUnlockDefault.removeAnimationFor("scaleY");
+                mUnlockDefault.removeAnimationFor("alpha");
+                mUnlockDefault.setX(mLockCenterX + 182).setY(mLockCenterY).setScaleX(0.1f)
+                        .setScaleY(0.1f).setAlpha(0.0f);
+
+                //TweenMax.to(unlockDefault, 0.5, { delay: .1   , scaleX: 1, scaleY: 1,
+                // alpha: 1, ease:Quint.easeOut                        });
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, false);
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, false);
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, false);
+
+                //TweenMax.to(unlockRing, 0.5, {scaleX: 1, scaleY: 1,
+                // alpha: 1, ease:Quint.easeOut, overwrite: true   });
+                mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 1.0f, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 1.0f, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "alpha", 1.0f, true);
+
+                postDelayed(mAddWaveAction, mWaveTimerDelay);
+
+                mLockState = STATE_ATTEMPTING;
+                break;
+
+            case STATE_ATTEMPTING:
+                if (DBG) Log.v(TAG, "State ATTEMPTING");
+                //TweenMax.to(unlockHalo, 0.4, { x:mouseX, y:mouseY, scaleX:1, scaleY:1,
+                // alpha: 1, ease:Quint.easeOut });
+                if (dragDistance > mSnapRadius) {
+                    if (fingerDown) {
+                        //TweenMax.to(unlockHalo, 0.4, {x:ringX, y:ringY, scaleX:1, scaleY:1,
+                        // alpha: 1 , ease:Quint.easeOut    , overwrite: true });
+                        mUnlockHalo.addAnimTo(0, 0, "x", ringX, true);
+                        mUnlockHalo.addAnimTo(0, 0, "y", ringY, true);
+                        mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
+                        mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
+                        mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
+                    }  else {
+                        mLockState = STATE_UNLOCK_ATTEMPT;
+                    }
+                } else {
+                    mUnlockHalo.addAnimTo(0, 0, "x", mouseX, true);
+                    mUnlockHalo.addAnimTo(0, 0, "y", mouseY, true);
+                    mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
+                    mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
+                    mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
+                }
+                break;
+
+            case STATE_UNLOCK_ATTEMPT:
+                if (DBG) Log.v(TAG, "State UNLOCK_ATTEMPT");
+                if (dragDistance > mSnapRadius) {
+                    for (int n = 0; n < mLightWaves.size(); n++) {
+                        //TweenMax.to(this["lightWave"+n], .5,{alpha:0, delay: (6+n-currentWave)*.1,
+                        // x:ringX, y:ringY, scaleX: .1, scaleY: .1, ease:Quint.easeOut});
+                        DrawableHolder wave = mLightWaves.get(n);
+                        long delay = 1000L*(6 + n - mCurrentWave)/10L;
+                        wave.addAnimTo(DURATION, delay, "x", ringX, true);
+                        wave.addAnimTo(DURATION, delay, "y", ringY, true);
+                        wave.addAnimTo(DURATION, delay, "scaleX", 0.1f, true);
+                        wave.addAnimTo(DURATION, delay, "scaleY", 0.1f, true);
+                        wave.addAnimTo(DURATION, delay, "alpha", 0.0f, true);
+                    }
+                    for (int i = 0; i < mLightWaves.size(); i++) {
+                        mLightWaves.get(i).startAnimations(this);
+                    }
+
+                    //TweenMax.to(unlockRing, .5, {x:ringX, y: ringY, scaleX: .1, scaleY: .1,
+                    // alpha: 0, ease: Quint.easeOut   });
+                    mUnlockRing.addAnimTo(DURATION, 0, "x", ringX, false);
+                    mUnlockRing.addAnimTo(DURATION, 0, "y", ringY, false);
+                    mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 0.1f, false);
+                    mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 0.1f, false);
+                    mUnlockRing.addAnimTo(DURATION, 0, "alpha", 0.0f, false);
+
+                    //TweenMax.to(unlockRing, .5, { delay: 1.3, alpha: 0  , ease: Quint.easeOut });
+                    mUnlockRing.addAnimTo(DURATION, FINAL_DELAY, "alpha", 0.0f, false);
+
+                    //TweenMax.to(unlockDefault, 0, { x:ringX, y: ringY, scaleX: .1, scaleY: .1,
+                    // alpha: 0  , overwrite: true });
+                    mUnlockDefault.removeAnimationFor("x");
+                    mUnlockDefault.removeAnimationFor("y");
+                    mUnlockDefault.removeAnimationFor("scaleX");
+                    mUnlockDefault.removeAnimationFor("scaleY");
+                    mUnlockDefault.removeAnimationFor("alpha");
+                    mUnlockDefault.setX(ringX).setY(ringY).setScaleX(0.1f).setScaleY(0.1f)
+                            .setAlpha(0.0f);
+
+                    //TweenMax.to(unlockDefault, .5, { x:ringX, y: ringY, scaleX: 1, scaleY: 1,
+                    // alpha: 1  , ease: Quint.easeOut  , overwrite: true });
+                    mUnlockDefault.addAnimTo(DURATION, 0, "x", ringX, true);
+                    mUnlockDefault.addAnimTo(DURATION, 0, "y", ringY, true);
+                    mUnlockDefault.addAnimTo(DURATION, 0, "scaleX", 1.0f, true);
+                    mUnlockDefault.addAnimTo(DURATION, 0, "scaleY", 1.0f, true);
+                    mUnlockDefault.addAnimTo(DURATION, 0, "alpha", 1.0f, true);
+
+                    //TweenMax.to(unlockDefault, .5, { delay: 1.3, scaleX: 3, scaleY: 3,
+                    // alpha: 1, ease: Quint.easeOut });
+                    mUnlockDefault.addAnimTo(DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
+                    mUnlockDefault.addAnimTo(DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
+                    mUnlockDefault.addAnimTo(DURATION, FINAL_DELAY, "alpha", 1.0f, false);
+
+                    //TweenMax.to(unlockHalo, .5, { x:ringX, y: ringY , ease: Back.easeOut    });
+                    mUnlockHalo.addAnimTo(DURATION, 0, "x", ringX, false);
+                    mUnlockHalo.addAnimTo(DURATION, 0, "y", ringY, false);
+
+                    //TweenMax.to(unlockHalo, .5, { delay: 1.3, scaleX: 3, scaleY: 3,
+                    // alpha: 1, ease: Quint.easeOut   });
+                    mUnlockHalo.addAnimTo(DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
+                    mUnlockHalo.addAnimTo(DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
+                    mUnlockHalo.addAnimTo(DURATION, FINAL_DELAY, "alpha", 1.0f, false);
+
+                    removeCallbacks(mLockTimerActions);
+
+                    postDelayed(mLockTimerActions, RESET_TIMEOUT);
+
+                    dispatchTriggerEvent(OnTriggerListener.CENTER_HANDLE);
+                    mLockState = STATE_UNLOCK_SUCCESS;
+                } else {
+                    mLockState = STATE_RESET_LOCK;
+                }
+                break;
+
+            case STATE_UNLOCK_SUCCESS:
+                if (DBG) Log.v(TAG, "State UNLOCK_SUCCESS");
+                removeCallbacks(mAddWaveAction);
+                break;
+
+            default:
+                if (DBG) Log.v(TAG, "Unknown state " + mLockState);
+                break;
+        }
+        mUnlockDefault.startAnimations(this);
+        mUnlockHalo.startAnimations(this);
+        mUnlockRing.startAnimations(this);
+    }
+
+    BitmapDrawable createDrawable(int resId) {
+        Resources res = getResources();
+        Bitmap bitmap = BitmapFactory.decodeResource(res, resId);
+        return new BitmapDrawable(res, bitmap);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        waveUpdateFrame(mMouseX, mMouseY, mFingerDown);
+        for (int i = 0; i < mDrawables.size(); ++i) {
+            mDrawables.get(i).draw(canvas);
+        }
+        for (int i = 0; i < mLightWaves.size(); ++i) {
+            mLightWaves.get(i).draw(canvas);
+        }
+    }
+
+    private final Runnable mLockTimerActions = new Runnable() {
+        public void run() {
+            if (DBG) Log.v(TAG, "LockTimerActions");
+            // reset lock after inactivity
+            if (mLockState == STATE_ATTEMPTING) {
+                mLockState = STATE_RESET_LOCK;
+            }
+            // for prototype, reset after successful unlock
+            if (mLockState == STATE_UNLOCK_SUCCESS) {
+                mLockState = STATE_RESET_LOCK;
+            }
+            invalidate();
+        }
+    };
+
+    private final Runnable mAddWaveAction = new Runnable() {
+        public void run() {
+            double distX = mMouseX - mLockCenterX;
+            double distY = mMouseY - mLockCenterY;
+            int dragDistance = (int) Math.ceil(Math.hypot(distX, distY));
+            if (mLockState == STATE_ATTEMPTING && dragDistance < mSnapRadius
+                    && mWaveTimerDelay >= mWaveDelay) {
+                mWaveTimerDelay = Math.min(WAVE_DURATION, mWaveTimerDelay + DELAY_INCREMENT);
+
+                DrawableHolder wave = mLightWaves.get(mCurrentWave);
+                wave.setAlpha(0.0f);
+                wave.setScaleX(0.2f);
+                wave.setScaleY(0.2f);
+                wave.setX(mMouseX);
+                wave.setY(mMouseY);
+
+                //TweenMax.to(this["lightWave"+currentWave], 2, { x:lockX , y:lockY, alpha: 1.5,
+                // scaleX: 1, scaleY:1, ease:Cubic.easeOut});
+                wave.addAnimTo(WAVE_DURATION, 0, "x", mLockCenterX, true);
+                wave.addAnimTo(WAVE_DURATION, 0, "y", mLockCenterY, true);
+                wave.addAnimTo(WAVE_DURATION*2/3, 0, "alpha", 1.0f, true);
+                wave.addAnimTo(WAVE_DURATION, 0, "scaleX", 1.0f, true);
+                wave.addAnimTo(WAVE_DURATION, 0, "scaleY", 1.0f, true);
+
+                //TweenMax.to(this["lightWave"+currentWave], 1, { delay: 1.3
+                // , alpha: 0  , ease:Quint.easeOut});
+                wave.addAnimTo(1000, FINAL_DELAY, "alpha", 0.0f, false);
+                wave.startAnimations(WaveView.this);
+
+                mCurrentWave = (mCurrentWave+1) % mWaveCount;
+                if (DBG) Log.v(TAG, "WaveTimerDelay: start new wave in " + mWaveTimerDelay);
+                postDelayed(mAddWaveAction, mWaveTimerDelay);
+            } else {
+                mWaveTimerDelay += DELAY_INCREMENT2;
+            }
+        }
+    };
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final int action = event.getAction();
+        mMouseX = event.getX();
+        mMouseY = event.getY();
+        boolean handled = false;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                removeCallbacks(mLockTimerActions);
+                mFingerDown = true;
+                setGrabbedState(OnTriggerListener.CENTER_HANDLE);
+                {
+                    float x = mMouseX - mUnlockHalo.getX();
+                    float y = mMouseY - mUnlockHalo.getY();
+                    float dist = (float) Math.hypot(x, y);
+                    if (dist < mUnlockHalo.getWidth()*0.5f) {
+                        if (mLockState == STATE_READY) {
+                            mLockState = STATE_START_ATTEMPT;
+                        }
+                    }
+                }
+                handled = true;
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                handled = true;
+                break;
+
+            case MotionEvent.ACTION_UP:
+                mFingerDown = false;
+                postDelayed(mLockTimerActions, RESET_TIMEOUT);
+                setGrabbedState(OnTriggerListener.NO_HANDLE);
+                handled = true;
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+                mFingerDown = false;
+                handled = true;
+                break;
+        }
+        invalidate();
+        return handled ? true : super.onTouchEvent(event);
+    }
+
+    /**
+     * Triggers haptic feedback.
+     */
+    private synchronized void vibrate(long duration) {
+        if (mVibrator == null) {
+            mVibrator = (android.os.Vibrator)
+                    getContext().getSystemService(Context.VIBRATOR_SERVICE);
+        }
+        mVibrator.vibrate(duration);
+    }
+
+    /**
+     * Registers a callback to be invoked when the user triggers an event.
+     *
+     * @param listener the OnDialTriggerListener to attach to this view
+     */
+    public void setOnTriggerListener(OnTriggerListener listener) {
+        mOnTriggerListener = listener;
+    }
+
+    /**
+     * Dispatches a trigger event to listener. Ignored if a listener is not set.
+     * @param whichHandle the handle that triggered the event.
+     */
+    private void dispatchTriggerEvent(int whichHandle) {
+        vibrate(VIBRATE_LONG);
+        if (mOnTriggerListener != null) {
+            mOnTriggerListener.onTrigger(this, whichHandle);
+        }
+    }
+
+    /**
+     * Sets the current grabbed state, and dispatches a grabbed state change
+     * event to our listener.
+     */
+    private void setGrabbedState(int newState) {
+        if (newState != mGrabbedState) {
+            mGrabbedState = newState;
+            if (mOnTriggerListener != null) {
+                mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState);
+            }
+        }
+    }
+
+    public interface OnTriggerListener {
+        /**
+         * Sent when the user releases the handle.
+         */
+        public static final int NO_HANDLE = 0;
+
+        /**
+         * Sent when the user grabs the center handle
+         */
+        public static final int CENTER_HANDLE = 10;
+
+        /**
+         * Called when the user drags the center ring beyond a threshold.
+         */
+        void onTrigger(View v, int whichHandle);
+
+        /**
+         * Called when the "grabbed state" changes (i.e. when the user either grabs or releases
+         * one of the handles.)
+         *
+         * @param v the view that was triggered
+         * @param grabbedState the new state: {@link #NO_HANDLE}, {@link #CENTER_HANDLE},
+         */
+        void onGrabbedStateChange(View v, int grabbedState);
+    }
+
+    public void onAnimationUpdate(ValueAnimator animation) {
+        invalidate();
+    }
+
+    public void reset() {
+        mLockState = STATE_RESET_LOCK;
+    }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index d51c0b7..64a2331 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -19,12 +19,15 @@
   LOCAL_CFLAGS += -DCUSTOM_RUNTIME_HEAP_MAX=$(USE_CUSTOM_RUNTIME_HEAP_MAX)
 endif
 
+ifeq ($(USE_OPENGL_RENDERER),true)
+	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER
+endif
+
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
 
 LOCAL_SRC_FILES:= \
 	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 +51,7 @@
 	android_view_InputChannel.cpp \
 	android_view_InputQueue.cpp \
 	android_view_KeyEvent.cpp \
+	android_view_GLES20Canvas.cpp \
 	android_view_MotionEvent.cpp \
 	android_text_AndroidCharacter.cpp \
 	android_text_AndroidBidi.cpp \
@@ -101,9 +105,11 @@
 	android_graphics_PixelFormat.cpp \
 	android/graphics/Picture.cpp \
 	android/graphics/PorterDuff.cpp \
+	android/graphics/BitmapRegionDecoder.cpp \
 	android/graphics/Rasterizer.cpp \
 	android/graphics/Region.cpp \
 	android/graphics/Shader.cpp \
+	android/graphics/TextLayout.cpp \
 	android/graphics/Typeface.cpp \
 	android/graphics/Utils.cpp \
 	android/graphics/Xfermode.cpp \
@@ -123,7 +129,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 \
@@ -142,6 +147,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 \
@@ -192,6 +198,10 @@
 	libwpa_client \
 	libjpeg
 
+ifeq ($(USE_OPENGL_RENDERER),true)
+	LOCAL_SHARED_LIBRARIES += libhwui
+endif
+
 ifeq ($(BOARD_HAVE_NFC),true)
 LOCAL_SHARED_LIBRARIES += \
 	libnfc_jni \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5f73443..a4a1a70 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -53,6 +53,7 @@
 extern int register_android_os_Process(JNIEnv* env);
 extern int register_android_graphics_Bitmap(JNIEnv*);
 extern int register_android_graphics_BitmapFactory(JNIEnv*);
+extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
 extern int register_android_graphics_Camera(JNIEnv* env);
 extern int register_android_graphics_Graphics(JNIEnv* env);
 extern int register_android_graphics_Interpolator(JNIEnv* env);
@@ -114,6 +115,7 @@
 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_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);
@@ -1207,6 +1208,7 @@
     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_Surface),
     REG_JNI(register_android_view_ViewRoot),
     REG_JNI(register_com_google_android_gles_jni_EGLImpl),
@@ -1219,6 +1221,7 @@
 
     REG_JNI(register_android_graphics_Bitmap),
     REG_JNI(register_android_graphics_BitmapFactory),
+    REG_JNI(register_android_graphics_BitmapRegionDecoder),
     REG_JNI(register_android_graphics_Camera),
     REG_JNI(register_android_graphics_Canvas),
     REG_JNI(register_android_graphics_ColorFilter),
@@ -1277,7 +1280,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..880fb6e 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -12,6 +12,8 @@
 

 #include <jni.h>

 

+#include <Caches.h>

+

 #if 0

     #define TRACE_BITMAP(code)  code

 #else

@@ -251,6 +253,11 @@
 }

 

 static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {

+#ifdef USE_OPENGL_RENDERER

+    if (android::uirenderer::Caches::hasInstance()) {

+        android::uirenderer::Caches::getInstance().textureCache.remove(bitmap);

+    }

+#endif

     delete bitmap;

 }

 

@@ -313,6 +320,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 +617,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/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
new file mode 100644
index 0000000..f700791
--- /dev/null
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "BitmapRegionDecoder"
+
+#include "SkBitmap.h"
+#include "SkImageEncoder.h"
+#include "GraphicsJNI.h"
+#include "SkUtils.h"
+#include "SkTemplates.h"
+#include "SkPixelRef.h"
+#include "SkStream.h"
+#include "BitmapFactory.h"
+#include "AutoDecodeCancel.h"
+#include "SkBitmapRegionDecoder.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+#include "Utils.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include "android_util_Binder.h"
+#include "android_nio_utils.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+
+#include <binder/Parcel.h>
+#include <jni.h>
+#include <utils/Asset.h>
+#include <sys/stat.h>
+
+static jclass gFileDescriptor_class;
+static jfieldID gFileDescriptor_descriptor;
+
+#if 0
+    #define TRACE_BITMAP(code)  code
+#else
+    #define TRACE_BITMAP(code)
+#endif
+
+using namespace android;
+
+static SkMemoryStream* buildSkMemoryStream(SkStream *stream) {
+    size_t bufferSize = 4096;
+    size_t streamLen = 0;
+    size_t len;
+    char* data = (char*)sk_malloc_throw(bufferSize);
+
+    while ((len = stream->read(data + streamLen,
+                    bufferSize - streamLen)) != 0) {
+        streamLen += len;
+        if (streamLen == bufferSize) {
+            bufferSize *= 2;
+            data = (char*)sk_realloc_throw(data, bufferSize);
+        }
+    }
+    data = (char*)sk_realloc_throw(data, streamLen);
+    SkMemoryStream* streamMem = new SkMemoryStream();
+    streamMem->setMemoryOwned(data, streamLen);
+    return streamMem;
+}
+
+static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) {
+    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
+    int width, height;
+    if (NULL == decoder) {
+        doThrowIOE(env, "Image format not supported");
+        return nullObjectReturn("SkImageDecoder::Factory returned null");
+    }
+
+    JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env, true);
+    decoder->setAllocator(javaAllocator);
+    JavaMemoryUsageReporter *javaMemoryReporter = new JavaMemoryUsageReporter(env);
+    decoder->setReporter(javaMemoryReporter);
+    javaAllocator->unref();
+    javaMemoryReporter->unref();
+
+    if (!decoder->buildTileIndex(stream, &width, &height)) {
+        char msg[100];
+        snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
+                decoder->getFormatName());
+        doThrowIOE(env, msg);
+        return nullObjectReturn("decoder->buildTileIndex returned false");
+    }
+
+    SkBitmapRegionDecoder *bm = new SkBitmapRegionDecoder(decoder, width, height);
+
+    return GraphicsJNI::createBitmapRegionDecoder(env, bm);
+}
+
+static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
+                                     int offset, int length, jboolean isShareable) {
+    /*  If isShareable we could decide to just wrap the java array and
+        share it, but that means adding a globalref to the java array object
+        For now we just always copy the array's data if isShareable.
+     */
+    AutoJavaByteArray ar(env, byteArray);
+    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
+    return doBuildTileIndex(env, stream);
+}
+
+static jobject nativeNewInstanceFromFileDescriptor(JNIEnv* env, jobject clazz,
+                                          jobject fileDescriptor, jboolean isShareable) {
+    NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
+
+    jint descriptor = env->GetIntField(fileDescriptor,
+                                       gFileDescriptor_descriptor);
+    SkStream *stream = NULL;
+    struct stat fdStat;
+    int newFD;
+    if (fstat(descriptor, &fdStat) == -1) {
+        doThrowIOE(env, "broken file descriptor");
+        return nullObjectReturn("fstat return -1");
+    }
+
+    if (isShareable &&
+            S_ISREG(fdStat.st_mode) &&
+            (newFD = ::dup(descriptor)) != -1) {
+        SkFDStream* fdStream = new SkFDStream(newFD, true);
+        if (!fdStream->isValid()) {
+            fdStream->unref();
+            return NULL;
+        }
+        stream = fdStream;
+    } else {
+        SkFDStream* fdStream = new SkFDStream(descriptor, false);
+        if (!fdStream->isValid()) {
+            fdStream->unref();
+            return NULL;
+        }
+        stream = buildSkMemoryStream(fdStream);
+        fdStream->unref();
+    }
+
+    /* Restore our offset when we leave, so we can be called more than once
+       with the same descriptor. This is only required if we didn't dup the
+       file descriptor, but it is OK to do it all the time.
+    */
+    AutoFDSeek as(descriptor);
+
+    return doBuildTileIndex(env, stream);
+}
+
+static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz,
+                                  jobject is,       // InputStream
+                                  jbyteArray storage, // byte[]
+                                  jboolean isShareable) {
+    jobject largeBitmap = NULL;
+    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024);
+
+    if (stream) {
+        // for now we don't allow shareable with java inputstreams
+        SkMemoryStream *mStream = buildSkMemoryStream(stream);
+        largeBitmap = doBuildTileIndex(env, mStream);
+        stream->unref();
+    }
+    return largeBitmap;
+}
+
+static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
+                                 jint native_asset, // Asset
+                                 jboolean isShareable) {
+    SkStream* stream, *assStream;
+    Asset* asset = reinterpret_cast<Asset*>(native_asset);
+    assStream = new AssetStreamAdaptor(asset);
+    stream = buildSkMemoryStream(assStream);
+    assStream->unref();
+    return doBuildTileIndex(env, stream);
+}
+
+/*
+ * nine patch not supported
+ *
+ * purgeable not supported
+ * reportSizeToVM not supported
+ */
+static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd,
+        int start_x, int start_y, int width, int height, jobject options) {
+    SkImageDecoder *decoder = brd->getDecoder();
+    int sampleSize = 1;
+    SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
+    bool doDither = true;
+
+    if (NULL != options) {
+        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
+        // initialize these, in case we fail later on
+        env->SetIntField(options, gOptions_widthFieldID, -1);
+        env->SetIntField(options, gOptions_heightFieldID, -1);
+        env->SetObjectField(options, gOptions_mimeFieldID, 0);
+
+        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
+        prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
+        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
+    }
+
+    decoder->setDitherImage(doDither);
+    SkBitmap*           bitmap = new SkBitmap;
+    SkAutoTDelete<SkBitmap>       adb(bitmap);
+    AutoDecoderCancel   adc(options, decoder);
+
+    // To fix the race condition in case "requestCancelDecode"
+    // happens earlier than AutoDecoderCancel object is added
+    // to the gAutoDecoderCancelMutex linked list.
+    if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
+        return nullObjectReturn("gOptions_mCancelID");;
+    }
+
+    SkIRect region;
+    region.fLeft = start_x;
+    region.fTop = start_y;
+    region.fRight = start_x + width;
+    region.fBottom = start_y + height;
+
+    if (!brd->decodeRegion(bitmap, region, prefConfig, sampleSize)) {
+        return nullObjectReturn("decoder->decodeRegion returned false");
+    }
+
+    // update options (if any)
+    if (NULL != options) {
+        env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
+        env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
+        // TODO: set the mimeType field with the data from the codec.
+        // but how to reuse a set of strings, rather than allocating new one
+        // each time?
+        env->SetObjectField(options, gOptions_mimeFieldID,
+                            getMimeTypeString(env, decoder->getFormat()));
+    }
+
+    // detach bitmap from its autotdeleter, since we want to own it now
+    adb.detach();
+
+    SkPixelRef* pr;
+    pr = bitmap->pixelRef();
+    // promise we will never change our pixels (great for sharing and pictures)
+    pr->setImmutable();
+    // now create the java bitmap
+    return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
+}
+
+static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
+    return brd->getHeight();
+}
+
+static int nativeGetWidth(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
+    return brd->getWidth();
+}
+
+static void nativeClean(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
+    delete brd;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include <android_runtime/AndroidRuntime.h>
+
+static JNINativeMethod gBitmapRegionDecoderMethods[] = {
+    {   "nativeDecodeRegion",
+        "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+        (void*)nativeDecodeRegion},
+
+    {   "nativeGetHeight", "(I)I", (void*)nativeGetHeight},
+
+    {   "nativeGetWidth", "(I)I", (void*)nativeGetWidth},
+
+    {   "nativeClean", "(I)V", (void*)nativeClean},
+
+    {   "nativeNewInstance",
+        "([BIIZ)Landroid/graphics/BitmapRegionDecoder;",
+        (void*)nativeNewInstanceFromByteArray
+    },
+
+    {   "nativeNewInstance",
+        "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;",
+        (void*)nativeNewInstanceFromStream
+    },
+
+    {   "nativeNewInstance",
+        "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;",
+        (void*)nativeNewInstanceFromFileDescriptor
+    },
+
+    {   "nativeNewInstance",
+        "(IZ)Landroid/graphics/BitmapRegionDecoder;",
+        (void*)nativeNewInstanceFromAsset
+    },
+};
+
+#define kClassPathName  "android/graphics/BitmapRegionDecoder"
+
+static jclass make_globalref(JNIEnv* env, const char classname[]) {
+    jclass c = env->FindClass(classname);
+    SkASSERT(c);
+    return (jclass)env->NewGlobalRef(c);
+}
+
+static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
+                                const char fieldname[], const char type[]) {
+    jfieldID id = env->GetFieldID(clazz, fieldname, type);
+    SkASSERT(id);
+    return id;
+}
+
+int register_android_graphics_BitmapRegionDecoder(JNIEnv* env);
+int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
+{
+
+    gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
+    gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
+    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
+            gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
+}
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index e1e9536..bf150a9 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -20,7 +20,6 @@
 
 #include "SkCanvas.h"
 #include "SkDevice.h"
-#include "SkGLCanvas.h"
 #include "SkGraphics.h"
 #include "SkImageRef_GlobalPool.h"
 #include "SkPorterDuff.h"
@@ -30,6 +29,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() {
@@ -60,13 +66,8 @@
         return bitmap ? new SkCanvas(*bitmap) : new SkCanvas;
     }
     
-    static SkCanvas* initGL(JNIEnv* env, jobject) {
-        return new SkGLCanvas;
-    }
-    
     static void freeCaches(JNIEnv* env, jobject) {
         // these are called in no particular order
-        SkGLCanvas::DeleteAllTextures();
         SkImageRef_GlobalPool::SetRAMUsed(0);
         SkGraphics::SetFontCacheUsed(0);
     }
@@ -103,11 +104,6 @@
         return canvas->getDevice()->accessBitmap(false).height();
     }
 
-    static void setViewport(JNIEnv* env, jobject, SkCanvas* canvas,
-                            int width, int height) {
-        canvas->setViewport(width, height);
-    }
-    
     static void setBitmap(JNIEnv* env, jobject, SkCanvas* canvas,
                           SkBitmap* bitmap) {
         canvas->setBitmapDevice(*bitmap);
@@ -743,45 +739,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 +804,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 +828,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;
@@ -868,12 +869,10 @@
 static JNINativeMethod gCanvasMethods[] = {
     {"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer},
     {"initRaster","(I)I", (void*) SkCanvasGlue::initRaster},
-    {"initGL","()I", (void*) SkCanvasGlue::initGL},
     {"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque},
     {"getWidth","()I", (void*) SkCanvasGlue::getWidth},
     {"getHeight","()I", (void*) SkCanvasGlue::getHeight},
     {"native_setBitmap","(II)V", (void*) SkCanvasGlue::setBitmap},
-    {"nativeSetViewport", "(III)V", (void*) SkCanvasGlue::setViewport},
     {"save","()I", (void*) SkCanvasGlue::saveAll},
     {"save","(I)I", (void*) SkCanvasGlue::save},
     {"native_saveLayer","(ILandroid/graphics/RectF;II)I",
@@ -940,26 +939,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..f3be8b0 100644
--- a/core/jni/android/graphics/ColorFilter.cpp
+++ b/core/jni/android/graphics/ColorFilter.cpp
@@ -23,28 +23,69 @@
 #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 SkiaColorFilter* glCreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
+            SkPorterDuff::Mode mode) {
+#ifdef USE_OPENGL_RENDERER
+        return new SkiaBlendFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
+#else
+        return NULL;
+#endif
     }
- 
-    static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject,
-                                               jint mul, jint add) {
+
+    static SkiaColorFilter* glCreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
+#ifdef USE_OPENGL_RENDERER
+        return new SkiaLightingFilter(mul, add);
+#else
+        return NULL;
+#endif
+    }
+
+    static SkiaColorFilter* glCreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+#ifdef USE_OPENGL_RENDERER
+        AutoJavaFloatArray autoArray(env, jarray, 20);
+        const float* src = autoArray.ptr();
+
+        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);
+#else
+        return NULL;
+#endif
+    }
+
+    static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
+            SkPorterDuff::Mode mode) {
+        return SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
+    }
+
+    static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
         return SkColorFilter::CreateLightingFilter(mul, add);
     }
-    
-    static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject,
-                                                  jfloatArray jarray) {
+
+    static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
         AutoJavaFloatArray autoArray(env, jarray, 20);
         const float* src = autoArray.ptr();
 
@@ -58,26 +99,25 @@
         return new SkColorMatrixFilter(src);
 #endif
     }
- 
 };
 
 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 +125,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/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 195f4d2..9467be8 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -46,7 +46,7 @@
 }
 
 void doThrowIOE(JNIEnv* env, const char* msg) {
-    doThrow(env, "java/lang/IOException", msg);
+    doThrow(env, "java/io/IOException", msg);
 }
 
 bool GraphicsJNI::hasException(JNIEnv *env) {
@@ -168,6 +168,9 @@
 static jclass   gBitmapConfig_class;
 static jfieldID gBitmapConfig_nativeInstanceID;
 
+static jclass   gBitmapRegionDecoder_class;
+static jmethodID gBitmapRegionDecoder_constructorMethodID;
+
 static jclass   gCanvas_class;
 static jfieldID gCanvas_nativeInstanceID;
 
@@ -374,6 +377,24 @@
     return obj;
 }
 
+jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap)
+{
+    SkASSERT(bitmap != NULL);
+
+    jobject obj = env->AllocObject(gBitmapRegionDecoder_class);
+    if (hasException(env)) {
+        obj = NULL;
+        return obj;
+    }
+    if (obj) {
+        env->CallVoidMethod(obj, gBitmapRegionDecoder_constructorMethodID, (jint)bitmap);
+        if (hasException(env)) {
+            obj = NULL;
+        }
+    }
+    return obj;
+}
+
 jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region)
 {
     SkASSERT(region != NULL);
@@ -592,6 +613,9 @@
     gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
                                             "(IZ[BI)V");
 
+    gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
+    gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "<init>", "(I)V");
+
     gBitmapConfig_class = make_globalref(env, "android/graphics/Bitmap$Config");
     gBitmapConfig_nativeInstanceID = getFieldIDCheck(env, gBitmapConfig_class,
                                                      "nativeInt", "I");    
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 1f94418..d0f9125 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -4,6 +4,7 @@
 #include "SkPoint.h"
 #include "SkRect.h"
 #include "SkBitmap.h"
+#include "../images/SkBitmapRegionDecoder.h"
 #include "../images/SkImageDecoder.h"
 #include <jni.h>
 
@@ -55,6 +56,8 @@
     
     static jobject createRegion(JNIEnv* env, SkRegion* region);
 
+    static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap);
+
     /** Set a pixelref for the bitmap (needs setConfig to already be called)
         Returns true on success. If it returns false, then it failed, and the
         appropriate exception will have been raised.
diff --git a/core/jni/android/graphics/Matrix.cpp b/core/jni/android/graphics/Matrix.cpp
index b782766..cafceab 100644
--- a/core/jni/android/graphics/Matrix.cpp
+++ b/core/jni/android/graphics/Matrix.cpp
@@ -27,6 +27,8 @@
 #include "SkMatrix.h"
 #include "SkTemplates.h"
 
+#include "Matrix.h"
+
 namespace android {
 
 class SkMatrixGlue {
@@ -403,10 +405,20 @@
     {"native_equals", "(II)Z", (void*) SkMatrixGlue::equals}
 };
 
+static jfieldID sNativeInstanceField;
+
 int register_android_graphics_Matrix(JNIEnv* env) {
     int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/Matrix", methods,
         sizeof(methods) / sizeof(methods[0]));
+
+    jclass clazz = env->FindClass("android/graphics/Matrix");
+    sNativeInstanceField = env->GetFieldID(clazz, "native_instance", "I");
+
     return result;
 }
 
+SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj) {
+    return reinterpret_cast<SkMatrix*>(env->GetIntField(matrixObj, sNativeInstanceField));
+}
+
 }
diff --git a/core/jni/android/graphics/Matrix.h b/core/jni/android/graphics/Matrix.h
new file mode 100644
index 0000000..31edf88
--- /dev/null
+++ b/core/jni/android/graphics/Matrix.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_GRAPHICS_MATRIX_H
+#define _ANDROID_GRAPHICS_MATRIX_H
+
+#include "jni.h"
+#include "SkMatrix.h"
+
+namespace android {
+
+/* Gets the underlying SkMatrix from a Matrix object. */
+extern SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj);
+
+} // namespace android
+
+#endif // _ANDROID_GRAPHICS_MATRIX_H
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 780badc..ca9f371 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,13 +725,20 @@
     {"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",
                                     (void*) SkPaintGlue::getCharArrayBounds },
-    {"setShadowLayer", "(FFFI)V", (void*)SkPaintGlue::setShadowLayer}
+    {"nSetShadowLayer", "(FFFI)V", (void*)SkPaintGlue::setShadowLayer}
 };
 
 static jfieldID req_fieldID(jfieldID id) {
diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp
index b8b2a10..abe33f4 100644
--- a/core/jni/android/graphics/Path.cpp
+++ b/core/jni/android/graphics/Path.cpp
@@ -26,12 +26,19 @@
 
 #include "SkPath.h"
 
+#include <Caches.h>
+
 namespace android {
 
 class SkPathGlue {
 public:
 
     static void finalizer(JNIEnv* env, jobject clazz, SkPath* obj) {
+#ifdef USE_OPENGL_RENDERER
+        if (android::uirenderer::Caches::hasInstance()) {
+            android::uirenderer::Caches::getInstance().pathCache.remove(obj);
+        }
+#endif
         delete obj;
     }
 
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index b09c62b..ee44747 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -8,6 +8,16 @@
 #include "SkTemplates.h"
 #include "SkXfermode.h"
 
+#include <SkiaShader.h>
+#include <Caches.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 +51,14 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static void Shader_destructor(JNIEnv* env, jobject, SkShader* shader)
+static void Shader_destructor(JNIEnv* env, jobject o, SkShader* shader, SkiaShader* skiaShader)
 {
+#ifdef USE_OPENGL_RENDERER
+    if (android::uirenderer::Caches::hasInstance()) {
+        android::uirenderer::Caches::getInstance().gradientCache.remove(shader);
+    }
+#endif
+    delete skiaShader;
     shader->safeUnref();
 }
 
@@ -51,7 +67,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 +77,40 @@
         else {
             shader->setLocalMatrix(*matrix);
         }
+#ifdef USE_OPENGL_RENDERER
+        skiaShader->setMatrix(const_cast<SkMatrix*>(matrix));
+#endif
     }
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-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) {
+#ifdef USE_OPENGL_RENDERER
+    SkiaShader* skiaShader = new SkiaBitmapShader(bitmap, shader,
+            static_cast<SkShader::TileMode>(tileModeX), static_cast<SkShader::TileMode>(tileModeY),
+            NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
     
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-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 +118,95 @@
     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) {
+#ifdef USE_OPENGL_RENDERER
+    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];
+    for (size_t i = 0; i < count; i++) {
+        storedColors[i] = static_cast<uint32_t>(colorValues[i]);
+    }
+
+    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;
+#else
+    return NULL;
+#endif
+}
+
+static SkiaShader* LinearGradient_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
+        float x0, float y0, float x1, float y1, int color0, int color1, int tileMode) {
+#ifdef USE_OPENGL_RENDERER
+    float* storedBounds = new float[4];
+    storedBounds[0] = x0; storedBounds[1] = y0;
+    storedBounds[2] = x1; storedBounds[3] = y1;
+
+    float* storedPositions = new float[2];
+    storedPositions[0] = 0.0f;
+    storedPositions[1] = 1.0f;
+
+    uint32_t* storedColors = new uint32_t[2];
+    storedColors[0] = static_cast<uint32_t>(color0);
+    storedColors[1] = static_cast<uint32_t>(color1);
+
+    SkiaShader* skiaShader = new SkiaLinearGradientShader(storedBounds, storedColors,
+            storedPositions, 2, shader, static_cast<SkShader::TileMode>(tileMode), NULL,
+            (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
+
+static SkShader* LinearGradient_create2(JNIEnv* env, jobject o,
                                         float x0, float y0, float x1, float y1,
                                         int color0, int color1, int tileMode)
 {
@@ -120,18 +217,17 @@
     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;
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static SkShader* RadialGradient_create1(JNIEnv* env, jobject,
-                                        float x, float y, float radius,
-                                        jintArray colorArray, jfloatArray posArray, int tileMode)
-{
+static SkShader* RadialGradient_create1(JNIEnv* env, jobject, float x, float y, float radius,
+        jintArray colorArray, jfloatArray posArray, int tileMode) {
     SkPoint center;
     center.set(SkFloatToScalar(x), SkFloatToScalar(y));
 
@@ -161,10 +257,8 @@
     return shader;
 }
 
-static SkShader* RadialGradient_create2(JNIEnv* env, jobject,
-                                        float x, float y, float radius,
-                                        int color0, int color1, int tileMode)
-{
+static SkShader* RadialGradient_create2(JNIEnv* env, jobject, float x, float y, float radius,
+        int color0, int color1, int tileMode) {
     SkPoint center;
     center.set(SkFloatToScalar(x), SkFloatToScalar(y));
 
@@ -178,11 +272,65 @@
     return s;
 }
 
+static SkiaShader* RadialGradient_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
+        float x, float y, float radius, jintArray colorArray, jfloatArray posArray, int tileMode) {
+#ifdef USE_OPENGL_RENDERER
+    size_t count = env->GetArrayLength(colorArray);
+    const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
+
+    jfloat* storedPositions = new jfloat[count];
+    uint32_t* storedColors = new uint32_t[count];
+    for (size_t i = 0; i < count; i++) {
+        storedColors[i] = static_cast<uint32_t>(colorValues[i]);
+    }
+
+    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 SkiaCircularGradientShader(x, y, radius, storedColors,
+            storedPositions, count, shader, (SkShader::TileMode) tileMode, NULL,
+            (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
+
+static SkiaShader* RadialGradient_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
+        float x, float y, float radius, int color0, int color1, int tileMode) {
+#ifdef USE_OPENGL_RENDERER
+    float* storedPositions = new float[2];
+    storedPositions[0] = 0.0f;
+    storedPositions[1] = 1.0f;
+
+    uint32_t* storedColors = new uint32_t[2];
+    storedColors[0] = static_cast<uint32_t>(color0);
+    storedColors[1] = static_cast<uint32_t>(color1);
+
+    SkiaShader* skiaShader = new SkiaCircularGradientShader(x, y, radius, storedColors,
+            storedPositions, 2, shader, (SkShader::TileMode) tileMode, NULL,
+            (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static SkShader* SweepGradient_create1(JNIEnv* env, jobject, float x, float y,
-                                    jintArray jcolors, jfloatArray jpositions)
-{
+        jintArray jcolors, jfloatArray jpositions) {
     size_t      count = env->GetArrayLength(jcolors);
     const jint* colors = env->GetIntArrayElements(jcolors, NULL);
     
@@ -209,8 +357,7 @@
 }
 
 static SkShader* SweepGradient_create2(JNIEnv* env, jobject, float x, float y,
-                                        int color0, int color1)
-{
+        int color0, int color1) {
     SkColor colors[2];
     colors[0] = color0;
     colors[1] = color1;
@@ -220,20 +367,97 @@
     return s;
 }
 
+static SkiaShader* SweepGradient_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
+        float x, float y, jintArray colorArray, jfloatArray posArray) {
+#ifdef USE_OPENGL_RENDERER
+    size_t count = env->GetArrayLength(colorArray);
+    const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
+
+    jfloat* storedPositions = new jfloat[count];
+    uint32_t* storedColors = new uint32_t[count];
+    for (size_t i = 0; i < count; i++) {
+        storedColors[i] = static_cast<uint32_t>(colorValues[i]);
+    }
+
+    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 SkiaSweepGradientShader(x, y, storedColors, storedPositions, count,
+            shader, NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
+
+static SkiaShader* SweepGradient_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
+        float x, float y, int color0, int color1) {
+#ifdef USE_OPENGL_RENDERER
+    float* storedPositions = new float[2];
+    storedPositions[0] = 0.0f;
+    storedPositions[1] = 1.0f;
+
+    uint32_t* storedColors = new uint32_t[2];
+    storedColors[0] = static_cast<uint32_t>(color0);
+    storedColors[1] = static_cast<uint32_t>(color1);
+
+    SkiaShader* skiaShader = new SkiaSweepGradientShader(x, y, storedColors, storedPositions, 2,
+            shader, NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-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) {
+#ifdef USE_OPENGL_RENDERER
+    SkXfermode::Mode mode = SkPorterDuff::ToXfermodeMode(porterDuffMode);
+    return new SkiaComposeShader(shaderA, shaderB, mode, shader);
+#else
+    return NULL;
+#endif
+}
+
+static SkiaShader* ComposeShader_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
+        SkiaShader* shaderA, SkiaShader* shaderB, SkXfermode* mode) {
+#ifdef USE_OPENGL_RENDERER
+    SkXfermode::Mode skiaMode;
+    if (!SkXfermode::IsMode(mode, &skiaMode)) {
+        // TODO: Support other modes
+        skiaMode = SkXfermode::kSrcOver_Mode;
+    }
+    return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader);
+#else
+    return NULL;
+#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -244,33 +468,42 @@
 };
 
 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[] = {
-    {"nativeCreate1",   "(FFF[I[FI)I",  (void*)RadialGradient_create1   },
-    {"nativeCreate2",   "(FFFIII)I",    (void*)RadialGradient_create2   }
+    { "nativeCreate1",     "(FFF[I[FI)I",  (void*)RadialGradient_create1     },
+    { "nativeCreate2",     "(FFFIII)I",    (void*)RadialGradient_create2     },
+    { "nativePostCreate1", "(IFFF[I[FI)I", (void*)RadialGradient_postCreate1 },
+    { "nativePostCreate2", "(IFFFIII)I",   (void*)RadialGradient_postCreate2 }
 };
 
 static JNINativeMethod gSweepGradientMethods[] = {
-    {"nativeCreate1",   "(FF[I[F)I",  (void*)SweepGradient_create1   },
-    {"nativeCreate2",   "(FFII)I",    (void*)SweepGradient_create2   }
+    { "nativeCreate1",     "(FF[I[F)I",  (void*)SweepGradient_create1     },
+    { "nativeCreate2",     "(FFII)I",    (void*)SweepGradient_create2     },
+    { "nativePostCreate1", "(IFF[I[F)I", (void*)SweepGradient_postCreate1 },
+    { "nativePostCreate2", "(IFFII)I",   (void*)SweepGradient_postCreate2 }
 };
 
 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..147e1fa
--- /dev/null
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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);
+    }
+}
+
+bool TextLayout::prepareRtlTextRun(const jchar* context, jsize start, jsize& count,
+        jsize contextCount, jchar* shaped) {
+    UErrorCode status = U_ZERO_ERROR;
+    count = shapeRtlText(context, start, count, contextCount, shaped, status);
+    if (U_SUCCESS(status)) {
+        return true;
+    } else {
+        LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status);
+    }
+    return false;
+}
+
+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);
+         if (prepareRtlTextRun(chars, start, count, contextCount, buffer.get())) {
+             canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
+         }
+     } 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..8f666c0
--- /dev/null
+++ b/core/jni/android/graphics/TextLayout.h
@@ -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.
+ */
+
+#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);
+    static bool prepareRtlTextRun(const jchar* context, jsize start, jsize& count,
+        jsize contextCount, jchar* shaped);
+        
+
+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..aae0f21 100644
--- a/core/jni/android_bluetooth_common.cpp
+++ b/core/jni/android_bluetooth_common.cpp
@@ -69,6 +69,16 @@
     {"UUIDs", DBUS_TYPE_ARRAY},
 };
 
+static Properties input_properties[] = {
+    {"Connected", DBUS_TYPE_BOOLEAN},
+};
+
+static Properties pan_properties[] = {
+    {"Connected", DBUS_TYPE_BOOLEAN},
+    {"Interface", DBUS_TYPE_STRING},
+    {"UUID", DBUS_TYPE_STRING},
+};
+
 typedef union {
     char *str_val;
     int int_val;
@@ -187,6 +197,7 @@
     dbus_bool_t ret;
     va_list lst;
     va_start(lst, first_arg_type);
+
     ret = dbus_func_args_async_valist(env, conn,
                                       timeout_ms,
                                       reply, user, nat,
@@ -699,6 +710,16 @@
                     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_pan_property_change(JNIEnv *env, DBusMessage *msg) {
+    return parse_property_change(env, msg, (Properties *) &pan_properties,
+                    sizeof(pan_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 +730,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..9222e1a 100644
--- a/core/jni/android_bluetooth_common.h
+++ b/core/jni/android_bluetooth_common.h
@@ -162,6 +162,9 @@
 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);
+jobjectArray parse_pan_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..1b68ba9 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>
@@ -51,6 +51,8 @@
 /* uncomment the next line to force-enable logging of all statements */
 // #define DB_LOG_STATEMENTS
 
+#define DEBUG_JNI 0
+
 namespace android {
 
 enum {
@@ -62,9 +64,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;
@@ -73,7 +77,7 @@
 
 static void sqlLogger(void *databaseName, int iErrCode, const char *zMsg) {
     // skip printing this message if it is due to certain types of errors
-    if (iErrCode == SQLITE_CONSTRAINT) return;
+    if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT) return;
     LOGI("sqlite returned: error code = %d, msg = %s\n", iErrCode, zMsg);
 }
 
@@ -85,9 +89,9 @@
     }
 
     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);
+        LOGW("sqlite returned error = %d when trying to register logging func.\n", err);
         return;
     }
     loggingFuncSet = true;
@@ -176,13 +180,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 +200,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 +212,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 +248,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 +384,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_IF(DEBUG_JNI, "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_IF(DEBUG_JNI, "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 +489,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 +550,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 +568,36 @@
         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;
+        case SQLITE_UNCLOSED:
+           exceptionClass = "android/database/sqlite/SQLiteUnfinalizedObjectsException";
+           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..9e42189 100644
--- a/core/jni/android_database_SQLiteQuery.cpp
+++ b/core/jni/android_database_SQLiteQuery.cpp
@@ -15,7 +15,7 @@
  */
 
 #undef LOG_TAG
-#define LOG_TAG "Cursor"
+#define LOG_TAG "SqliteCursor.cpp"
 
 #include <jni.h>
 #include <JNIHelp.h>
@@ -29,7 +29,7 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "CursorWindow.h"
+#include "binder/CursorWindow.h"
 #include "sqlite3_exception.h"
 
 
@@ -73,7 +73,7 @@
             return -1;
         }
     }
-    LOGD("skip_rows row %d", maxRows);
+    LOG_WINDOW("skip_rows row %d", maxRows);
     return maxRows;
 }
 
@@ -101,7 +101,7 @@
         }
     }
     sqlite3_reset(statement);
-    LOGD("finish_program_and_get_row_count row %d", numRows);
+    LOG_WINDOW("finish_program_and_get_row_count row %d", numRows);
     return numRows;
 }
 
@@ -116,6 +116,8 @@
     int retryCount;
     int boundParams;
     CursorWindow * window;
+    bool gotAllRows = true;
+    bool gotException = false;
     
     if (statement == NULL) {
         LOGE("Invalid statement in fillWindow()");
@@ -131,8 +133,7 @@
         err = sqlite3_bind_int(statement, offsetParam, startPos);
         if (err != SQLITE_OK) {
             LOGE("Unable to bind offset position, offsetParam = %d", offsetParam);
-            jniThrowException(env, "java/lang/IllegalArgumentException",
-                              sqlite3_errmsg(GET_HANDLE(env, object)));
+            throw_sqlite3_exception(env, GET_HANDLE(env, object));
             return 0;
         }
         LOG_WINDOW("Bound to startPos %d", startPos);
@@ -167,7 +168,7 @@
             LOGE("startPos %d > actual rows %d", startPos, num);
             return num;
         }
-    } 
+    }
     
     while(startPos != 0 || numRows < maxRead) {
         err = sqlite3_step(statement);
@@ -181,8 +182,9 @@
             {
                 field_slot_t * fieldDir = window->allocRow();
                 if (!fieldDir) {
-                    LOGE("Failed allocating fieldDir at startPos %d row %d", startPos, numRows);
-                    return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                    LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d", startPos, numRows);
+                    gotAllRows = false;
+                    goto return_count;
                 }
             }
 
@@ -205,9 +207,10 @@
                     int offset = window->alloc(size);
                     if (!offset) {
                         window->freeLastRow();
-                        LOGE("Failed allocating %u bytes for text/blob at %d,%d", size,
+                        LOG_WINDOW("Failed allocating %u bytes for text/blob at %d,%d", size,
                                    startPos + numRows, i);
-                        return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                        gotAllRows = false;
+                        goto return_count;
                     }
 
                     window->copyIn(offset, text, size);
@@ -225,8 +228,9 @@
                     int64_t value = sqlite3_column_int64(statement, i);
                     if (!window->putLong(numRows, i, value)) {
                         window->freeLastRow();
-                        LOGE("Failed allocating space for a long in column %d", i);
-                        return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                        LOG_WINDOW("Failed allocating space for a long in column %d", i);
+                        gotAllRows = false;
+                        goto return_count;
                     }
                     LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + numRows, i, value);
                 } else if (type == SQLITE_FLOAT) {
@@ -234,8 +238,9 @@
                     double value = sqlite3_column_double(statement, i);
                     if (!window->putDouble(numRows, i, value)) {
                         window->freeLastRow();
-                        LOGE("Failed allocating space for a double in column %d", i);
-                        return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                        LOG_WINDOW("Failed allocating space for a double in column %d", i);
+                        gotAllRows = false;
+                        goto return_count;
                     }
                     LOG_WINDOW("%d,%d is FLOAT %lf", startPos + numRows, i, value);
                 } else if (type == SQLITE_BLOB) {
@@ -245,9 +250,10 @@
                     int offset = window->alloc(size);
                     if (!offset) {
                         window->freeLastRow();
-                        LOGE("Failed allocating %u bytes for blob at %d,%d", size,
+                        LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d", size,
                                    startPos + numRows, i);
-                        return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
+                        gotAllRows = false;
+                        goto return_count;
                     }
 
                     window->copyIn(offset, blob, size);
@@ -269,6 +275,7 @@
                     // Unknown data
                     LOGE("Unknown column type when filling database window");
                     throw_sqlite3_exception(env, "Unknown column type when filling window");
+                    gotException = true;
                     break;
                 }
             }
@@ -290,6 +297,8 @@
             LOG_WINDOW("Database locked, retrying");
             if (retryCount > 50) {
                 LOGE("Bailing on database busy rety");
+                throw_sqlite3_exception(env, GET_HANDLE(env, object), "retrycount exceeded");
+                gotException = true;
                 break;
             }
 
@@ -300,18 +309,33 @@
             continue;
         } else {
             throw_sqlite3_exception(env, GET_HANDLE(env, object));
+            gotException = true;
             break;
         }
     }
 
     LOG_WINDOW("Resetting statement %p after fetching %d rows in %d bytes\n\n\n\n", statement,
             numRows, window->size() - window->freeSpace());
-//    LOGI("Filled window with %d rows in %d bytes", numRows, window->size() - window->freeSpace());
+    LOG_WINDOW("Filled window with %d rows in %d bytes", numRows,
+            window->size() - window->freeSpace());
     if (err == SQLITE_ROW) {
+        // there is more data to be returned. let the caller know by returning -1
         return -1;
-    } else {
+    }
+  return_count:
+    if (startPos) {
         sqlite3_reset(statement);
-        return startPos + numRows;
+        LOG_WINDOW("Not doing count(*) because startPos %d is non-zero", startPos);
+        return 0;
+    } else if (gotAllRows) {
+        sqlite3_reset(statement);
+        LOG_WINDOW("Not doing count(*) because we already know the count(*)");
+        return numRows;
+    } else if (gotException) {
+        return 0;
+    } else {
+        // since startPos == 0, we need to get the count(*) of the result set
+        return numRows + 1 + finish_program_and_get_row_count(statement);
     }
 }
 
@@ -336,7 +360,8 @@
 static JNINativeMethod sMethods[] =
 {
      /* name, signature, funcPtr */
-    {"native_fill_window", "(Landroid/database/CursorWindow;IIII)I", (void *)native_fill_window},
+    {"native_fill_window", "(Landroid/database/CursorWindow;IIII)I",
+            (void *)native_fill_window},
     {"native_column_count", "()I", (void*)native_column_count},
     {"native_column_name", "(I)Ljava/lang/String;", (void *)native_column_name},
 };
diff --git a/core/jni/android_database_SQLiteStatement.cpp b/core/jni/android_database_SQLiteStatement.cpp
index ff2ed5d..97e0483 100644
--- a/core/jni/android_database_SQLiteStatement.cpp
+++ b/core/jni/android_database_SQLiteStatement.cpp
@@ -16,7 +16,9 @@
 */
 
 #undef LOG_TAG
-#define LOG_TAG "Cursor"
+#define LOG_TAG "SQLiteStatementCpp"
+
+#include "android_util_Binder.h"
 
 #include <jni.h>
 #include <JNIHelp.h>
@@ -24,11 +26,16 @@
 
 #include <sqlite3.h>
 
+#include <cutils/ashmem.h>
 #include <utils/Log.h>
 
+#include <fcntl.h>
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 #include "sqlite3_exception.h"
 
@@ -48,22 +55,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,13 +140,125 @@
     return value;
 }
 
+static jobject createParcelFileDescriptor(JNIEnv * env, int fd)
+{
+    // Create FileDescriptor object
+    jobject fileDesc = newFileDescriptor(env, fd);
+    if (fileDesc == NULL) {
+        // FileDescriptor constructor has thrown an exception
+        close(fd);
+        return NULL;
+    }
+
+    // Wrap it in a ParcelFileDescriptor
+    jobject parcelFileDesc = newParcelFileDescriptor(env, fileDesc);
+    if (parcelFileDesc == NULL) {
+        // ParcelFileDescriptor constructor has thrown an exception
+        close(fd);
+        return NULL;
+    }
+
+    return parcelFileDesc;
+}
+
+// Creates an ashmem area, copies some data into it, and returns
+// a ParcelFileDescriptor for the ashmem area.
+static jobject create_ashmem_region_with_data(JNIEnv * env,
+                                              const void * data, int length)
+{
+    // Create ashmem area
+    int fd = ashmem_create_region(NULL, length);
+    if (fd < 0) {
+        LOGE("ashmem_create_region failed: %s", strerror(errno));
+        jniThrowIOException(env, errno);
+        return NULL;
+    }
+
+    if (length > 0) {
+        // mmap the ashmem area
+        void * ashmem_ptr =
+                mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+        if (ashmem_ptr == MAP_FAILED) {
+            LOGE("mmap failed: %s", strerror(errno));
+            jniThrowIOException(env, errno);
+            close(fd);
+            return NULL;
+        }
+
+        // Copy data to ashmem area
+        memcpy(ashmem_ptr, data, length);
+
+        // munmap ashmem area
+        if (munmap(ashmem_ptr, length) < 0) {
+            LOGE("munmap failed: %s", strerror(errno));
+            jniThrowIOException(env, errno);
+            close(fd);
+            return NULL;
+        }
+    }
+
+    // Make ashmem area read-only
+    if (ashmem_set_prot_region(fd, PROT_READ) < 0) {
+        LOGE("ashmem_set_prot_region failed: %s", strerror(errno));
+        jniThrowIOException(env, errno);
+        close(fd);
+        return NULL;
+    }
+
+    // Wrap it in a ParcelFileDescriptor
+    return createParcelFileDescriptor(env, fd);
+}
+
+static jobject native_1x1_blob_ashmem(JNIEnv* env, jobject object)
+{
+    int err;
+    sqlite3 * handle = GET_HANDLE(env, object);
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+    jobject value = NULL;
+
+    // Execute the statement
+    err = sqlite3_step(statement);
+
+    // Handle the result
+    if (err == SQLITE_ROW) {
+        // No errors, read the data and return it
+        const void * blob = sqlite3_column_blob(statement, 0);
+        if (blob != NULL) {
+            int len = sqlite3_column_bytes(statement, 0);
+            if (len >= 0) {
+                value = create_ashmem_region_with_data(env, blob, len);
+            }
+        }
+    } else {
+        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
+    }
+
+    // Reset the statment so it's ready to use again
+    sqlite3_reset(statement);
+
+    return value;
+}
+
+static void native_executeSql(JNIEnv* env, jobject object, jstring sql)
+{
+    char const* sqlString = env->GetStringUTFChars(sql, NULL);
+    sqlite3 * handle = GET_HANDLE(env, object);
+    int err = sqlite3_exec(handle, sqlString, NULL, NULL, NULL);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, handle);
+    }
+    env->ReleaseStringUTFChars(sql, sqlString);
+}
 
 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},
+    {"native_1x1_blob_ashmem", "()Landroid/os/ParcelFileDescriptor;", (void *)native_1x1_blob_ashmem},
+    {"native_executeSql", "(Ljava/lang/String;)V", (void *)native_executeSql},
 };
 
 int register_android_database_SQLiteStatement(JNIEnv * env)
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 6575b9a..351f264 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -224,7 +224,8 @@
     { "addRoute", "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)I",
        (void *)android_net_utils_addRoute },
     { "removeHostRoutes", "(Ljava/lang/String;)I",  (void *)android_net_utils_removeHostRoutes },
-    { "getDefaultRoute", "(Ljava/lang/String;)I",  (void *)android_net_utils_getDefaultRoute },
+    { "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 fb029e6..defd0a0 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -30,6 +30,8 @@
 
 static jboolean sScanModeActive = false;
 
+//TODO: check general errors in addition to overflow on snprintf
+
 /*
  * The following remembers the jfieldID's of the fields
  * of the DhcpInfo Java object, so that we don't have
@@ -98,6 +100,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);
@@ -150,6 +157,36 @@
     return doIntCommand("ADD_NETWORK");
 }
 
+static jboolean android_net_wifi_wpsPbcCommand(JNIEnv* env, jobject clazz, jstring bssid)
+{
+    char cmdstr[50];
+    jboolean isCopy;
+
+    const char *bssidStr = env->GetStringUTFChars(bssid, &isCopy);
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "WPS_PBC %s", bssidStr);
+    env->ReleaseStringUTFChars(bssid, bssidStr);
+
+    if ((numWritten == -1) || (numWritten >= sizeof(cmdstr))) {
+        return false;
+    }
+    return doBooleanCommand(cmdstr, "OK");
+}
+
+static jboolean android_net_wifi_wpsPinCommand(JNIEnv* env, jobject clazz, jstring bssid, int apPin)
+{
+    char cmdstr[50];
+    jboolean isCopy;
+
+    const char *bssidStr = env->GetStringUTFChars(bssid, &isCopy);
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "WPS_REG %s %d", bssidStr, apPin);
+    env->ReleaseStringUTFChars(bssid, bssidStr);
+
+    if ((numWritten == -1) || (numWritten >= (int)sizeof(cmdstr))) {
+        return false;
+    }
+    return doBooleanCommand(cmdstr, "OK");
+}
+
 static jboolean android_net_wifi_setNetworkVariableCommand(JNIEnv* env,
                                                            jobject clazz,
                                                            jint netId,
@@ -406,6 +443,30 @@
     return (jint)power;
 }
 
+static jboolean android_net_wifi_setBandCommand(JNIEnv* env, jobject clazz, jint band)
+{
+    char cmdstr[25];
+
+    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER SETBAND %d", band);
+    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
+
+    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+}
+
+static jint android_net_wifi_getBandCommand(JNIEnv* env, jobject clazz)
+{
+    char reply[25];
+    int band;
+
+    if (doCommand("DRIVER GETBAND", reply, sizeof(reply)) != 0) {
+        return (jint)-1;
+    }
+    // reply comes back in the form "Band X" where X is the
+    // number we're interested in.
+    sscanf(reply, "%*s %u", &band);
+    return (jint)band;
+}
+
 static jboolean android_net_wifi_setNumAllowedChannelsCommand(JNIEnv* env, jobject clazz, jint numChannels)
 {
     char cmdstr[256];
@@ -493,15 +554,6 @@
     return doBooleanCommand("BLACKLIST clear", "OK");
 }
 
-static jboolean android_net_wifi_setSuspendOptimizationsCommand(JNIEnv* env, jobject clazz, jboolean enabled)
-{
-    char cmdstr[25];
-
-    snprintf(cmdstr, sizeof(cmdstr), "DRIVER SETSUSPENDOPT %d", enabled ? 0 : 1);
-    return doBooleanCommand(cmdstr, "OK");
-}
-
-
 static jboolean android_net_wifi_doDhcpRequest(JNIEnv* env, jobject clazz, jobject info)
 {
     jint ipaddr, gateway, mask, dns1, dns2, server, lease;
@@ -533,6 +585,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 },
@@ -564,6 +617,8 @@
     { "stopPacketFiltering", "()Z", (void*) android_net_wifi_stopPacketFiltering },
     { "setPowerModeCommand", "(I)Z", (void*) android_net_wifi_setPowerModeCommand },
     { "getPowerModeCommand", "()I", (void*) android_net_wifi_getPowerModeCommand },
+    { "setBandCommand", "(I)Z", (void*) android_net_wifi_setBandCommand},
+    { "getBandCommand", "()I", (void*) android_net_wifi_getBandCommand},
     { "setNumAllowedChannelsCommand", "(I)Z", (void*) android_net_wifi_setNumAllowedChannelsCommand },
     { "getNumAllowedChannelsCommand", "()I", (void*) android_net_wifi_getNumAllowedChannelsCommand },
     { "setBluetoothCoexistenceModeCommand", "(I)Z",
@@ -580,8 +635,8 @@
     { "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 },
-    { "setSuspendOptimizationsCommand", "(Z)Z", (void*) android_net_wifi_setSuspendOptimizationsCommand},
-
+    { "startWpsPbcCommand", "(Ljava/lang/String;)Z", (void*) android_net_wifi_wpsPbcCommand },
+    { "startWpsPinCommand", "(Ljava/lang/String;I)Z", (void*) android_net_wifi_wpsPinCommand },
     { "doDhcpRequest", "(Landroid/net/DhcpInfo;)Z", (void*) android_net_wifi_doDhcpRequest },
     { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_wifi_getDhcpError },
 };
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_os_FileUtils.cpp b/core/jni/android_os_FileUtils.cpp
index 21cb919..d3faa2f 100644
--- a/core/jni/android_os_FileUtils.cpp
+++ b/core/jni/android_os_FileUtils.cpp
@@ -113,6 +113,11 @@
     #endif
 }
 
+jint android_os_FileUtils_setUMask(JNIEnv* env, jobject clazz, jint mask)
+{
+    return umask(mask);
+}
+
 jint android_os_FileUtils_getFatVolumeId(JNIEnv* env, jobject clazz, jstring path)
 {
     #if HAVE_ANDROID_OS
@@ -170,6 +175,7 @@
 static const JNINativeMethod methods[] = {
     {"setPermissions",  "(Ljava/lang/String;III)I", (void*)android_os_FileUtils_setPermissions},
     {"getPermissions",  "(Ljava/lang/String;[I)I", (void*)android_os_FileUtils_getPermissions},
+    {"setUMask",        "(I)I",                    (void*)android_os_FileUtils_setUMask},
     {"getFatVolumeId",  "(Ljava/lang/String;)I", (void*)android_os_FileUtils_getFatVolumeId},
     {"getFileStatus",  "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z", (void*)android_os_FileUtils_getFileStatus},
 };
diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp
index 848a57a..eceef1c 100644
--- a/core/jni/android_os_ParcelFileDescriptor.cpp
+++ b/core/jni/android_os_ParcelFileDescriptor.cpp
@@ -66,6 +66,26 @@
     return fileDescriptorClone;
 }
 
+static int android_os_ParcelFileDescriptor_createPipeNative(JNIEnv* env,
+    jobject clazz, jobjectArray outFds)
+{
+    int fds[2];
+    if (pipe(fds) < 0) {
+        return -errno;
+    }
+
+    for (int i=0; i<2; i++) {
+        jobject fdObj = env->NewObject(gFileDescriptorOffsets.mClass,
+                gFileDescriptorOffsets.mConstructor);
+        if (fdObj != NULL) {
+            env->SetIntField(fdObj, gFileDescriptorOffsets.mDescriptor, fds[i]);
+        }
+        env->SetObjectArrayElement(outFds, i, fdObj);
+    }
+
+    return 0;
+}
+
 static jint getFd(JNIEnv* env, jobject clazz)
 {
     jobject descriptor = env->GetObjectField(clazz, gParcelFileDescriptorOffsets.mFileDescriptor);
@@ -109,6 +129,8 @@
 static const JNINativeMethod gParcelFileDescriptorMethods[] = {
     {"getFileDescriptorFromSocket", "(Ljava/net/Socket;)Ljava/io/FileDescriptor;",
         (void*)android_os_ParcelFileDescriptor_getFileDescriptorFromSocket},
+    {"createPipeNative", "([Ljava/io/FileDescriptor;)I",
+        (void*)android_os_ParcelFileDescriptor_createPipeNative},
     {"getStatSize", "()J",
         (void*)android_os_ParcelFileDescriptor_getStatSize},
     {"seekTo", "(J)J",
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index d8e049d..a9b91b04 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -50,6 +50,8 @@
 static jmethodID method_onDeviceCreated;
 static jmethodID method_onDeviceRemoved;
 static jmethodID method_onDeviceDisconnectRequested;
+static jmethodID method_onNetworkDeviceDisconnected;
+static jmethodID method_onNetworkDeviceConnected;
 
 static jmethodID method_onCreatePairedDeviceResult;
 static jmethodID method_onCreateDeviceResult;
@@ -66,6 +68,11 @@
 static jmethodID method_onAgentAuthorize;
 static jmethodID method_onAgentCancel;
 
+static jmethodID method_onInputDevicePropertyChanged;
+static jmethodID method_onInputDeviceConnectionResult;
+static jmethodID method_onPanDevicePropertyChanged;
+static jmethodID method_onPanDeviceConnectionResult;
+
 typedef event_loop_native_data_t native_data_t;
 
 #define EVENT_LOOP_REFS 10
@@ -97,6 +104,10 @@
     method_onDeviceRemoved = env->GetMethodID(clazz, "onDeviceRemoved", "(Ljava/lang/String;)V");
     method_onDeviceDisconnectRequested = env->GetMethodID(clazz, "onDeviceDisconnectRequested",
                                                         "(Ljava/lang/String;)V");
+    method_onNetworkDeviceConnected = env->GetMethodID(clazz, "onNetworkDeviceConnected",
+                                                     "(Ljava/lang/String;Ljava/lang/String;I)V");
+    method_onNetworkDeviceDisconnected = env->GetMethodID(clazz, "onNetworkDeviceDisconnected",
+                                                              "(Ljava/lang/String;)V");
 
     method_onCreatePairedDeviceResult = env->GetMethodID(clazz, "onCreatePairedDeviceResult",
                                                          "(Ljava/lang/String;I)V");
@@ -120,6 +131,14 @@
                                                "(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");
+    method_onPanDevicePropertyChanged = env->GetMethodID(clazz, "onPanDevicePropertyChanged",
+                                               "(Ljava/lang/String;[Ljava/lang/String;)V");
+    method_onPanDeviceConnectionResult = env->GetMethodID(clazz, "onPanDeviceConnectionResult",
+                                               "(Ljava/lang/String;Z)V");
     method_onRequestOobData = env->GetMethodID(clazz, "onRequestOobData",
                                                "(Ljava/lang/String;I)V");
 
@@ -232,6 +251,27 @@
             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='"BLUEZ_DBUS_BASE_IFC".NetworkServer'",
+                &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)) {
@@ -390,13 +430,31 @@
         dbus_connection_unregister_object_path(nat->conn, agent_path);
 
         dbus_bus_remove_match(nat->conn,
-                "type='signal',interface='org.bluez.AudioSink'",
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".AudioSink'",
                 &err);
         if (dbus_error_is_set(&err)) {
             LOG_AND_FREE_DBUS_ERROR(&err);
         }
         dbus_bus_remove_match(nat->conn,
-                "type='signal',interface='org.bluez.Device'",
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Device'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+        }
+        dbus_bus_remove_match(nat->conn,
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Input'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+        }
+        dbus_bus_remove_match(nat->conn,
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Network'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+        }
+        dbus_bus_remove_match(nat->conn,
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".NetworkServer'",
                 &err);
         if (dbus_error_is_set(&err)) {
             LOG_AND_FREE_DBUS_ERROR(&err);
@@ -861,6 +919,73 @@
                             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;
+    } else if (dbus_message_is_signal(msg,
+                                     "org.bluez.Network",
+                                     "PropertyChanged")) {
+
+       jobjectArray str_array =
+                   parse_pan_property_change(env, msg);
+       if (str_array != NULL) {
+           const char *c_path = dbus_message_get_path(msg);
+           env->CallVoidMethod(nat->me,
+                               method_onPanDevicePropertyChanged,
+                               env->NewStringUTF(c_path),
+                               str_array);
+       } else {
+           LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+       }
+       goto success;
+    } else if (dbus_message_is_signal(msg,
+                                     "org.bluez.NetworkServer",
+                                     "DeviceDisconnected")) {
+       char *c_address;
+       if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_INVALID)) {
+           env->CallVoidMethod(nat->me,
+                               method_onNetworkDeviceDisconnected,
+                               env->NewStringUTF(c_address));
+       } else {
+           LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+       }
+       goto success;
+    } else if (dbus_message_is_signal(msg,
+                                     "org.bluez.NetworkServer",
+                                     "DeviceConnected")) {
+       char *c_address;
+       char *c_iface;
+       uint16_t uuid;
+
+       if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_STRING, &c_iface,
+                                  DBUS_TYPE_UINT16, &uuid,
+                                  DBUS_TYPE_INVALID)) {
+           env->CallVoidMethod(nat->me,
+                               method_onNetworkDeviceConnected,
+                               env->NewStringUTF(c_address),
+                               env->NewStringUTF(c_iface),
+                               uuid);
+       } else {
+           LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+       }
+       goto success;
     }
 
     ret = a2dp_event_filter(msg, env);
@@ -1270,6 +1395,57 @@
     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);
+}
+
+void onPanDeviceConnectionResult(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("... Pan Device Path = %s, result = %d", path, result);
+    jstring jPath = env->NewStringUTF(path);
+    env->CallVoidMethod(nat->me,
+                        method_onPanDeviceConnectionResult,
+                        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 daa59a6..337dccc 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -16,6 +16,11 @@
 
 #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 DBUS_NETWORK_IFACE BLUEZ_DBUS_BASE_IFC ".Network"
+#define DBUS_NETWORKSERVER_IFACE BLUEZ_DBUS_BASE_IFC ".NetworkServer"
+
+
 #define LOG_TAG "BluetoothService.cpp"
 
 #include "android_bluetooth_common.h"
@@ -68,6 +73,9 @@
 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);
+void onConnectPanResult(DBusMessage *msg, void *user, void *n);
+void onPanDeviceConnectionResult(DBusMessage *msg, void *user, void *nat);
 
 
 /** Get native data stored in the opaque (Java code maintained) pointer mNativeData
@@ -990,6 +998,160 @@
     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 jboolean setBluetoothTetheringNative(JNIEnv *env, jobject object, jboolean value,
+                                            jstring src_role, jstring bridge) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply;
+        const char *c_role = env->GetStringUTFChars(src_role, NULL);
+        const char *c_bridge = env->GetStringUTFChars(bridge, NULL);
+        if (value) {
+            LOGE("setBluetoothTetheringNative true");
+            reply = dbus_func_args(env, nat->conn,
+                                  get_adapter_path(env, object),
+                                  DBUS_NETWORKSERVER_IFACE,
+                                  "Register",
+                                  DBUS_TYPE_STRING, &c_role,
+                                  DBUS_TYPE_STRING, &c_bridge,
+                                  DBUS_TYPE_INVALID);
+        } else {
+            LOGE("setBluetoothTetheringNative false");
+            reply = dbus_func_args(env, nat->conn,
+                                  get_adapter_path(env, object),
+                                  DBUS_NETWORKSERVER_IFACE,
+                                  "Unregister",
+                                  DBUS_TYPE_STRING, &c_role,
+                                  DBUS_TYPE_INVALID);
+        }
+        env->ReleaseStringUTFChars(src_role, c_role);
+        env->ReleaseStringUTFChars(bridge, c_bridge);
+        return reply ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean connectPanDeviceNative(JNIEnv *env, jobject object, jstring path,
+                                       jstring srcRole, jstring dstRole) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    LOGE("connectPanDeviceNative");
+    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);
+        const char *src = env->GetStringUTFChars(srcRole, NULL);
+        const char *dst = env->GetStringUTFChars(dstRole, 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,onPanDeviceConnectionResult,
+                                    context_path, eventLoopNat, c_path,
+                                    DBUS_NETWORK_IFACE, "Connect",
+                                    DBUS_TYPE_STRING, &src,
+                                    DBUS_TYPE_STRING, &dst,
+                                    DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(path, c_path);
+        env->ReleaseStringUTFChars(srcRole, src);
+        env->ReleaseStringUTFChars(dstRole, dst);
+        return ret ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean disconnectPanDeviceNative(JNIEnv *env, jobject object,
+                                     jstring path) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    LOGE("disconnectPanDeviceNative");
+    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,onPanDeviceConnectionResult,
+                                        context_path, eventLoopNat, c_path,
+                                        DBUS_NETWORK_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},
@@ -1039,8 +1201,18 @@
     {"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},
+
+    {"setBluetoothTetheringNative", "(ZLjava/lang/String;Ljava/lang/String;)Z",
+              (void *)setBluetoothTetheringNative},
+    {"connectPanDeviceNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+              (void *)connectPanDeviceNative},
+    {"disconnectPanDeviceNative", "(Ljava/lang/String;)Z", (void *)disconnectPanDeviceNative},
 };
 
+
 int register_android_server_BluetoothService(JNIEnv *env) {
     return AndroidRuntime::registerNativeMethods(env,
                 "android/server/BluetoothService", sMethods, NELEM(sMethods));
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
new file mode 100644
index 0000000..7521af4
--- /dev/null
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <SkTemplates.h>
+#include <SkXfermode.h>
+
+#include <DisplayListRenderer.h>
+#include <OpenGLDebugRenderer.h>
+#include <OpenGLRenderer.h>
+#include <SkiaShader.h>
+#include <SkiaColorFilter.h>
+#include <Rect.h>
+
+#include "TextLayout.h"
+
+namespace android {
+
+using namespace uirenderer;
+
+/**
+ * Note: OpenGLRenderer JNI layer is generated and compiled only on supported
+ *       devices. This means all the logic must be compiled only when the
+ *       preprocessor variable USE_OPENGL_RENDERER is defined.
+ */
+#ifdef USE_OPENGL_RENDERER
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_RENDERER 0
+#define PROFILE_RENDERER 0
+
+// Debug
+#if DEBUG_RENDERER
+    #define RENDERER_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define RENDERER_LOGD(...)
+#endif
+
+// ----------------------------------------------------------------------------
+// Java APIs
+// ----------------------------------------------------------------------------
+
+static struct {
+    jclass clazz;
+    jmethodID set;
+} gRectClassInfo;
+
+// ----------------------------------------------------------------------------
+// Constructors
+// ----------------------------------------------------------------------------
+
+static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject canvas) {
+    RENDERER_LOGD("Create OpenGLRenderer");
+#if PROFILE_RENDERER
+    return new OpenGLDebugRenderer;
+#else
+    return new OpenGLRenderer;
+#endif
+}
+
+static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    RENDERER_LOGD("Destroy OpenGLRenderer");
+    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();
+}
+
+static void android_view_GLES20Canvas_finish(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    renderer->finish();
+}
+
+static void android_view_GLES20Canvas_acquireContext(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    renderer->acquireContext();
+}
+
+static void android_view_GLES20Canvas_releaseContext(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    renderer->releaseContext();
+}
+
+// ----------------------------------------------------------------------------
+// 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->xDivs[0], &patch->yDivs[0],
+            patch->numXDivs, patch->numYDivs, 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);
+}
+
+static void android_view_GLES20Canvas_drawRects(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkRegion* region, SkPaint* paint) {
+    SkRegion::Iterator it(*region);
+    while (!it.done()) {
+        const SkIRect& r = it.rect();
+        renderer->drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint);
+        it.next();
+    }
+}
+
+static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkPath* path, SkPaint* paint) {
+    renderer->drawPath(path, paint);
+}
+
+static void android_view_GLES20Canvas_drawLines(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloatArray points, jint offset, jint count, SkPaint* paint) {
+    jfloat* storage = env->GetFloatArrayElements(points, NULL);
+
+    renderer->drawLines(storage + offset, count, paint);
+
+    env->ReleaseFloatArrayElements(points, storage, 0);
+}
+
+// ----------------------------------------------------------------------------
+// Shaders and color filters
+// ----------------------------------------------------------------------------
+
+static void android_view_GLES20Canvas_resetModifiers(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    renderer->resetShader();
+    renderer->resetColorFilter();
+    renderer->resetShadow();
+}
+
+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);
+}
+
+static void android_view_GLES20Canvas_setupShadow(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat radius, jfloat dx, jfloat dy, jint color) {
+    renderer->setupShadow(radius, dx, dy, color);
+}
+
+// ----------------------------------------------------------------------------
+// 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 renderTextRun(OpenGLRenderer* renderer, const jchar* text,
+        jint start, jint count, jint contextCount, jfloat x, jfloat y,
+        int flags, SkPaint* paint) {
+    uint8_t rtl = flags & 0x1;
+    if (rtl) {
+        SkAutoSTMalloc<80, jchar> buffer(contextCount);
+        jchar* shaped = buffer.get();
+        if (TextLayout::prepareRtlTextRun(text, start, count, contextCount, shaped)) {
+            renderer->drawText((const char*) shaped, count << 1, count, x, y, paint);
+        } else {
+            LOGW("drawTextRun error");
+        }
+    } else {
+        renderer->drawText((const char*) (text + start), count << 1, count, x, y, paint);
+    }
+}
+
+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);
+}
+
+static void android_view_GLES20Canvas_drawTextRunArray(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jcharArray text, int index, int count,
+        int contextIndex, int contextCount, jfloat x, jfloat y, int dirFlags,
+        SkPaint* paint) {
+    jchar* textArray = env->GetCharArrayElements(text, NULL);
+    renderTextRun(renderer, textArray + contextIndex, index - contextIndex,
+            count, contextCount, x, y, dirFlags, paint);
+    env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+ }
+
+static void android_view_GLES20Canvas_drawTextRun(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jstring text, int start, int end,
+        int contextStart, int contextEnd, jfloat x, jfloat y, int dirFlags,
+        SkPaint* paint) {
+    const jchar* textArray = env->GetStringChars(text, NULL);
+    jint count = end - start;
+    jint contextCount = contextEnd - contextStart;
+    renderTextRun(renderer, textArray + contextStart, start - contextStart,
+            count, contextCount, x, y, dirFlags, paint);
+    env->ReleaseStringChars(text, textArray);
+}
+
+// ----------------------------------------------------------------------------
+// Display lists
+// ----------------------------------------------------------------------------
+
+static OpenGLRenderer* android_view_GLES20Canvas_createDisplayListRenderer(
+        JNIEnv* env, jobject canvas) {
+    return new DisplayListRenderer;
+}
+
+static DisplayList* android_view_GLES20Canvas_createDisplayList(JNIEnv* env,
+        jobject canvas, DisplayListRenderer* renderer) {
+    return renderer->getDisplayList();
+}
+
+static void android_view_GLES20Canvas_destroyDisplayList(JNIEnv* env,
+        jobject canvas, DisplayList* displayList) {
+    delete displayList;
+}
+
+static void android_view_GLES20Canvas_drawDisplayList(JNIEnv* env,
+        jobject canvas, OpenGLRenderer* renderer, DisplayList* displayList) {
+    displayList->replay(*renderer);
+}
+
+#endif // USE_OPENGL_RENDERER
+
+// ----------------------------------------------------------------------------
+// Common
+// ----------------------------------------------------------------------------
+
+static jboolean android_view_GLES20Canvas_isAvailable(JNIEnv* env, jobject clazz) {
+#ifdef USE_OPENGL_RENDERER
+    return JNI_TRUE;
+#else
+    return JNI_FALSE;
+#endif
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/view/GLES20Canvas";
+
+static JNINativeMethod gMethods[] = {
+    { "nIsAvailable",       "()Z",             (void*) android_view_GLES20Canvas_isAvailable },
+
+#ifdef USE_OPENGL_RENDERER
+    { "nCreateRenderer",    "()I",             (void*) android_view_GLES20Canvas_createRenderer },
+    { "nDestroyRenderer",   "(I)V",            (void*) android_view_GLES20Canvas_destroyRenderer },
+    { "nSetViewport",       "(III)V",          (void*) android_view_GLES20Canvas_setViewport },
+    { "nPrepare",           "(I)V",            (void*) android_view_GLES20Canvas_prepare },
+    { "nFinish",            "(I)V",            (void*) android_view_GLES20Canvas_finish },
+    { "nAcquireContext",    "(I)V",            (void*) android_view_GLES20Canvas_acquireContext },
+    { "nReleaseContext",    "(I)V",            (void*) android_view_GLES20Canvas_releaseContext },
+
+    { "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 },
+    { "nDrawRects",         "(III)V",          (void*) android_view_GLES20Canvas_drawRects },
+    { "nDrawPath",          "(III)V",          (void*) android_view_GLES20Canvas_drawPath },
+    { "nDrawLines",         "(I[FIII)V",       (void*) android_view_GLES20Canvas_drawLines },
+
+    { "nResetModifiers",    "(I)V",            (void*) android_view_GLES20Canvas_resetModifiers },
+    { "nSetupShader",       "(II)V",           (void*) android_view_GLES20Canvas_setupShader },
+    { "nSetupColorFilter",  "(II)V",           (void*) android_view_GLES20Canvas_setupColorFilter },
+    { "nSetupShadow",       "(IFFFI)V",        (void*) android_view_GLES20Canvas_setupShadow },
+
+    { "nDrawText",          "(I[CIIFFII)V",    (void*) android_view_GLES20Canvas_drawTextArray },
+    { "nDrawText",          "(ILjava/lang/String;IIFFII)V",
+            (void*) android_view_GLES20Canvas_drawText },
+
+    { "nDrawTextRun",       "(I[CIIIIFFII)V",  (void*) android_view_GLES20Canvas_drawTextRunArray },
+    { "nDrawTextRun",       "(ILjava/lang/String;IIIIFFII)V",
+            (void*) android_view_GLES20Canvas_drawTextRun },
+
+    { "nGetClipBounds",     "(ILandroid/graphics/Rect;)Z",
+            (void*) android_view_GLES20Canvas_getClipBounds },
+
+    { "nCreateDisplayListRenderer", "()I",     (void*) android_view_GLES20Canvas_createDisplayListRenderer },
+    { "nCreateDisplayList",  "(I)I",           (void*) android_view_GLES20Canvas_createDisplayList },
+    { "nDestroyDisplayList", "(I)V",           (void*) android_view_GLES20Canvas_destroyDisplayList },
+    { "nDrawDisplayList",    "(II)V",          (void*) android_view_GLES20Canvas_drawDisplayList },
+
+#endif
+};
+
+#ifdef USE_OPENGL_RENDERER
+    #define FIND_CLASS(var, className) \
+            var = env->FindClass(className); \
+            LOG_FATAL_IF(! var, "Unable to find class " className); \
+            var = jclass(env->NewGlobalRef(var));
+    
+    #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+            var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+            LOG_FATAL_IF(! var, "Unable to find method " methodName);
+#else
+    #define FIND_CLASS(var, className)
+    #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor)
+#endif
+
+int register_android_view_GLES20Canvas(JNIEnv* env) {
+    FIND_CLASS(gRectClassInfo.clazz, "android/graphics/Rect");
+    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_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 93fd54f..537ac72 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -22,10 +22,26 @@
 #include <utils/Log.h>
 #include <ui/Input.h>
 #include "android_view_MotionEvent.h"
+#include "android/graphics/Matrix.h"
+
+#include <math.h>
+#include "SkMatrix.h"
+#include "SkScalar.h"
 
 // Number of float items per entry in a DVM sample data array
 #define NUM_SAMPLE_DATA 9
 
+#define SAMPLE_X 0
+#define SAMPLE_Y 1
+#define SAMPLE_PRESSURE 2
+#define SAMPLE_SIZE 3
+#define SAMPLE_TOUCH_MAJOR 4
+#define SAMPLE_TOUCH_MINOR 5
+#define SAMPLE_TOOL_MAJOR 6
+#define SAMPLE_TOOL_MINOR 7
+#define SAMPLE_ORIENTATION 8
+
+
 namespace android {
 
 // ----------------------------------------------------------------------------
@@ -238,8 +254,87 @@
     }
 }
 
+static inline float transformAngle(const SkMatrix* matrix, float angleRadians) {
+    // Construct and transform a vector oriented at the specified clockwise angle from vertical.
+    // Coordinate system: down is increasing Y, right is increasing X.
+    SkPoint vector;
+    vector.fX = SkFloatToScalar(sinf(angleRadians));
+    vector.fY = SkFloatToScalar(- cosf(angleRadians));
+    matrix->mapVectors(& vector, 1);
+
+    // Derive the transformed vector's clockwise angle from vertical.
+    float result = atan2f(SkScalarToFloat(vector.fX), SkScalarToFloat(- vector.fY));
+    if (result < - M_PI_2) {
+        result += M_PI;
+    } else if (result > M_PI_2) {
+        result -= M_PI;
+    }
+    return result;
+}
+
+static void android_view_MotionEvent_nativeTransform(JNIEnv* env,
+        jobject eventObj, jobject matrixObj) {
+    SkMatrix* matrix = android_graphics_Matrix_getSkMatrix(env, matrixObj);
+
+    jfloat oldXOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mXOffset);
+    jfloat oldYOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mYOffset);
+    jint numPointers = env->GetIntField(eventObj, gMotionEventClassInfo.mNumPointers);
+    jint numSamples = env->GetIntField(eventObj, gMotionEventClassInfo.mNumSamples);
+    jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mDataSamples));
+    jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL);
+
+    // The tricky part of this implementation is to preserve the value of
+    // rawX and rawY.  So we apply the transformation to the first point
+    // then derive an appropriate new X/Y offset that will preserve rawX and rawY.
+    SkPoint point;
+    jfloat rawX = dataSamples[SAMPLE_X];
+    jfloat rawY = dataSamples[SAMPLE_Y];
+    matrix->mapXY(SkFloatToScalar(rawX + oldXOffset), SkFloatToScalar(rawY + oldYOffset),
+            & point);
+    jfloat newX = SkScalarToFloat(point.fX);
+    jfloat newY = SkScalarToFloat(point.fY);
+    jfloat newXOffset = newX - rawX;
+    jfloat newYOffset = newY - rawY;
+
+    dataSamples[SAMPLE_ORIENTATION] = transformAngle(matrix, dataSamples[SAMPLE_ORIENTATION]);
+
+    // Apply the transformation to all samples.
+    jfloat* currentDataSample = dataSamples;
+    jfloat* endDataSample = dataSamples + numPointers * numSamples * NUM_SAMPLE_DATA;
+    for (;;) {
+        currentDataSample += NUM_SAMPLE_DATA;
+        if (currentDataSample == endDataSample) {
+            break;
+        }
+
+        jfloat x = currentDataSample[SAMPLE_X] + oldXOffset;
+        jfloat y = currentDataSample[SAMPLE_Y] + oldYOffset;
+        matrix->mapXY(SkFloatToScalar(x), SkFloatToScalar(y), & point);
+        currentDataSample[SAMPLE_X] = SkScalarToFloat(point.fX) - newXOffset;
+        currentDataSample[SAMPLE_Y] = SkScalarToFloat(point.fY) - newYOffset;
+
+        currentDataSample[SAMPLE_ORIENTATION] = transformAngle(matrix,
+                currentDataSample[SAMPLE_ORIENTATION]);
+    }
+
+    env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, 0);
+
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mXOffset, newXOffset);
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mYOffset, newYOffset);
+
+    env->DeleteLocalRef(dataSampleArray);
+}
+
 // ----------------------------------------------------------------------------
 
+static JNINativeMethod gMotionEventMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeTransform",
+            "(Landroid/graphics/Matrix;)V",
+            (void*)android_view_MotionEvent_nativeTransform },
+};
+
 #define FIND_CLASS(var, className) \
         var = env->FindClass(className); \
         LOG_FATAL_IF(! var, "Unable to find class " className); \
@@ -258,6 +353,10 @@
         LOG_FATAL_IF(! var, "Unable to find field " fieldName);
 
 int register_android_view_MotionEvent(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "android/view/MotionEvent",
+            gMotionEventMethods, NELEM(gMotionEventMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
     FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent");
 
     GET_STATIC_METHOD_ID(gMotionEventClassInfo.obtain, gMotionEventClassInfo.clazz,
diff --git a/core/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/Android.mk b/core/res/Android.mk
index 7d11148..22f4fdc 100644
--- a/core/res/Android.mk
+++ b/core/res/Android.mk
@@ -33,8 +33,16 @@
 # PRODUCT-agnostic resource data like IDs and type definitions.
 LOCAL_EXPORT_PACKAGE_RESOURCES := true
 
+# Include resources generated by system RenderScript files.
+framework_GENERATED_SOURCE_DIR := $(call intermediates-dir-for,JAVA_LIBRARIES,framework,,COMMON)/src
+framework_RenderScript_STAMP_FILE := $(framework_GENERATED_SOURCE_DIR)/RenderScript.stamp
+#LOCAL_RESOURCE_DIR := $(framework_GENERATED_SOURCE_DIR)/renderscript/res $(LOCAL_PATH)/res
+
 include $(BUILD_PACKAGE)
 
+# Make sure the system .rs files get compiled before building the package-export.apk.
+# $(resource_export_package): $(framework_RenderScript_STAMP_FILE)
+
 # define a global intermediate target that other module may depend on.
 .PHONY: framework-res-package-target
 framework-res-package-target: $(LOCAL_BUILT_MODULE)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b9eb5d6..3a1d76f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -85,6 +85,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" />
 
     <protected-broadcast android:name="com.trustedlogic.trustednfc.android.action.NDEF_TAG_DISCOVERED" />
     <protected-broadcast android:name="com.trustedlogic.trustednfc.android.action.TRANSACTION_DETECTED" />
@@ -372,6 +374,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 -->
     <!-- ================================== -->
@@ -1310,7 +1319,7 @@
                 android:excludeFromRecents="true">
         </activity>
         <activity android:name="com.android.internal.app.PlatLogoActivity"
-                android:theme="@style/Theme.NoTitleBar.Fullscreen">
+                android:theme="@style/Theme.Wallpaper.NoTitleBar.Fullscreen">
         </activity>
         <activity android:name="com.android.internal.app.DisableCarModeActivity"
                 android:theme="@style/Theme.NoDisplay"
@@ -1347,6 +1356,10 @@
                 <action android:name="android.intent.action.ACTION_REQUEST_SHUTDOWN" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.REBOOT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
         <activity android:name="com.android.internal.app.NetInitiatedActivity"
                 android:theme="@style/Theme.Dialog.Alert"
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/assets/webkit/incognito_mode_start_page.html b/core/res/assets/webkit/incognito_mode_start_page.html
new file mode 100644
index 0000000..b070c6d
--- /dev/null
+++ b/core/res/assets/webkit/incognito_mode_start_page.html
@@ -0,0 +1,24 @@
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+    <title>New incognito window</title>
+  </head>
+  <body>
+    <p><strong>You've gone incognito</strong>. Pages you view in this window
+      won't appear in your browser history or search history, and they won't
+      leave other traces, like cookies, on your computer after you close the
+      incognito window. Any files you download or bookmarks you create will be
+      preserved, however.</p>
+
+    <p><strong>Going incognito doesn't affect the behavior of other people,
+	servers, or software. Be wary of:</strong></p>
+
+    <ul>
+      <li>Websites that collect or share information about you</li>
+      <li>Internet service providers or employers that track the pages you visit</li>
+      <li>Malicious software that tracks your keystrokes in exchange for free smileys</li>
+      <li>Surveillance by secret agents</li>
+      <li>People standing behind you</li>
+    </ul>
+  </body>
+</html>
diff --git a/core/res/res/anim/animator_fade_in.xml b/core/res/res/anim/animator_fade_in.xml
new file mode 100644
index 0000000..2a28b4d
--- /dev/null
+++ b/core/res/res/anim/animator_fade_in.xml
@@ -0,0 +1,26 @@
+<?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.
+*/
+-->
+
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@anim/accelerate_interpolator"
+    android:valueFrom="0"
+    android:valueTo="1"
+    android:propertyName="alpha"
+    android:duration="@android:integer/config_mediumAnimTime"
+/>
diff --git a/core/res/res/anim/animator_fade_out.xml b/core/res/res/anim/animator_fade_out.xml
new file mode 100644
index 0000000..4db6591
--- /dev/null
+++ b/core/res/res/anim/animator_fade_out.xml
@@ -0,0 +1,26 @@
+<?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.
+*/
+-->
+
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@anim/accelerate_interpolator"
+    android:valueFrom="1.0"
+    android:valueTo="0.0"
+    android:propertyName="alpha"
+    android:duration="@android:integer/config_mediumAnimTime"
+/>
diff --git a/core/res/res/anim/fragment_close_enter.xml b/core/res/res/anim/fragment_close_enter.xml
new file mode 100644
index 0000000..7a9a3b9
--- /dev/null
+++ b/core/res/res/anim/fragment_close_enter.xml
@@ -0,0 +1,34 @@
+<?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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:interpolator="@anim/decelerate_interpolator"
+        android:valueFrom="0"
+        android:valueTo="1"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <objectAnimator
+        android:interpolator="@anim/decelerate_interpolator"
+        android:valueFrom="-400"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="translationX"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_close_exit.xml b/core/res/res/anim/fragment_close_exit.xml
new file mode 100644
index 0000000..0743577
--- /dev/null
+++ b/core/res/res/anim/fragment_close_exit.xml
@@ -0,0 +1,34 @@
+<?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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:interpolator="@anim/accelerate_interpolator"
+        android:valueFrom="1"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <objectAnimator
+        android:interpolator="@anim/accelerate_interpolator"
+        android:valueFrom="0"
+        android:valueTo="400"
+        android:valueType="floatType"
+        android:propertyName="translationX"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_next_enter.xml b/core/res/res/anim/fragment_next_enter.xml
new file mode 100644
index 0000000..d2d6ec9
--- /dev/null
+++ b/core/res/res/anim/fragment_next_enter.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="1"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_longAnimTime"/>
+    <objectAnimator
+        android:valueFrom="50"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="translationY"
+        android:duration="@android:integer/config_longAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_next_exit.xml b/core/res/res/anim/fragment_next_exit.xml
new file mode 100644
index 0000000..fbb82d9
--- /dev/null
+++ b/core/res/res/anim/fragment_next_exit.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="1"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="-50"
+        android:valueType="floatType"
+        android:propertyName="translationY"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_open_enter.xml b/core/res/res/anim/fragment_open_enter.xml
new file mode 100644
index 0000000..ac60494
--- /dev/null
+++ b/core/res/res/anim/fragment_open_enter.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="1"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <objectAnimator
+        android:valueFrom="400"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="translationX"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_open_exit.xml b/core/res/res/anim/fragment_open_exit.xml
new file mode 100644
index 0000000..3bf1ad4
--- /dev/null
+++ b/core/res/res/anim/fragment_open_exit.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="1"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="-400"
+        android:valueType="floatType"
+        android:propertyName="translationX"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_prev_enter.xml b/core/res/res/anim/fragment_prev_enter.xml
new file mode 100644
index 0000000..d37afd0
--- /dev/null
+++ b/core/res/res/anim/fragment_prev_enter.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="1"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_longAnimTime"/>
+    <objectAnimator
+        android:valueFrom="-50"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="translationY"
+        android:duration="@android:integer/config_longAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_prev_exit.xml b/core/res/res/anim/fragment_prev_exit.xml
new file mode 100644
index 0000000..a445a4d
--- /dev/null
+++ b/core/res/res/anim/fragment_prev_exit.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="1"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="50"
+        android:valueType="floatType"
+        android:propertyName="translationY"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/push_down_in_no_alpha.xml b/core/res/res/anim/push_down_in_no_alpha.xml
new file mode 100644
index 0000000..045d691
--- /dev/null
+++ b/core/res/res/anim/push_down_in_no_alpha.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="-100%p" android:toYDelta="0"
+            android:duration="@android:integer/config_longAnimTime"/>
+</set>
diff --git a/core/res/res/anim/push_down_out_no_alpha.xml b/core/res/res/anim/push_down_out_no_alpha.xml
new file mode 100644
index 0000000..3c2474a
--- /dev/null
+++ b/core/res/res/anim/push_down_out_no_alpha.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromYDelta="0" android:toYDelta="100%p"
+            android:duration="@android:integer/config_longAnimTime"/>
+</set>
diff --git a/core/res/res/color/primary_text_dark.xml b/core/res/res/color/primary_text_dark.xml
index f0812ed..3dd73e7 100644
--- a/core/res/res/color/primary_text_dark.xml
+++ b/core/res/res/color/primary_text_dark.xml
@@ -19,8 +19,6 @@
     <item android:state_window_focused="false" android:color="@android:color/bright_foreground_dark"/>
     <item android:state_pressed="true" android:color="@android:color/bright_foreground_dark_inverse"/>
     <item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse"/>
-    <item android:state_focused="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
     <item android:color="@android:color/bright_foreground_dark"/> <!-- not selected -->
-
 </selector>
-
diff --git a/core/res/res/color/primary_text_dark_focused.xml b/core/res/res/color/primary_text_dark_focused.xml
index 7f3906a..c97c0bd 100644
--- a/core/res/res/color/primary_text_dark_focused.xml
+++ b/core/res/res/color/primary_text_dark_focused.xml
@@ -16,6 +16,7 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse" />
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse" />
     <item android:state_focused="true" android:color="@android:color/bright_foreground_dark_inverse" />
     <item android:state_pressed="true" android:color="@android:color/bright_foreground_dark_inverse" />
     <item android:color="@android:color/bright_foreground_dark" />
diff --git a/core/res/res/color/primary_text_dark_nodisable.xml b/core/res/res/color/primary_text_dark_nodisable.xml
index be1b9f9..443f7f4 100644
--- a/core/res/res/color/primary_text_dark_nodisable.xml
+++ b/core/res/res/color/primary_text_dark_nodisable.xml
@@ -16,6 +16,7 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
     <item android:color="@android:color/bright_foreground_dark"/> <!-- not selected -->
 </selector>
 
diff --git a/core/res/res/color/primary_text_disable_only_holo_dark.xml b/core/res/res/color/primary_text_disable_only_holo_dark.xml
new file mode 100644
index 0000000..6de4583
--- /dev/null
+++ b/core/res/res/color/primary_text_disable_only_holo_dark.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_enabled="false" android:color="@android:color/bright_foreground_dark_disabled"/>
+    <item android:color="@android:color/bright_foreground_dark"/>
+</selector>
+
diff --git a/core/res/res/color/primary_text_disable_only_holo_light.xml b/core/res/res/color/primary_text_disable_only_holo_light.xml
new file mode 100644
index 0000000..87a92c8
--- /dev/null
+++ b/core/res/res/color/primary_text_disable_only_holo_light.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_enabled="false" android:color="@android:color/bright_foreground_light_disabled"/>
+    <item android:color="@android:color/bright_foreground_light"/>
+</selector>
+
diff --git a/core/res/res/color/primary_text_focused_holo_dark.xml b/core/res/res/color/primary_text_focused_holo_dark.xml
new file mode 100644
index 0000000..80c68a4
--- /dev/null
+++ b/core/res/res/color/primary_text_focused_holo_dark.xml
@@ -0,0 +1,24 @@
+<?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_selected="true" android:color="@android:color/bright_foreground_dark_inverse" />
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse" />
+    <item android:state_focused="true" android:color="@android:color/bright_foreground_dark_inverse" />
+    <item android:state_pressed="true" android:color="@android:color/bright_foreground_dark_inverse" />
+    <item android:color="@android:color/bright_foreground_dark" />
+</selector>
+
diff --git a/core/res/res/color/primary_text_holo_dark.xml b/core/res/res/color/primary_text_holo_dark.xml
new file mode 100644
index 0000000..69ee309
--- /dev/null
+++ b/core/res/res/color/primary_text_holo_dark.xml
@@ -0,0 +1,24 @@
+<?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_enabled="false" android:color="@android:color/bright_foreground_dark_disabled"/>
+    <item android:state_window_focused="false" android:color="@android:color/bright_foreground_dark"/>
+    <item android:state_pressed="true" android:color="@android:color/bright_foreground_dark"/>
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_dark"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark"/>
+    <item android:color="@android:color/bright_foreground_dark"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/primary_text_holo_light.xml b/core/res/res/color/primary_text_holo_light.xml
new file mode 100644
index 0000000..a8d31ce
--- /dev/null
+++ b/core/res/res/color/primary_text_holo_light.xml
@@ -0,0 +1,26 @@
+<?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_enabled="false" android:color="@android:color/bright_foreground_light_disabled"/>
+    <item android:state_window_focused="false" android:color="@android:color/bright_foreground_light"/>
+    <item android:state_pressed="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:color="@android:color/bright_foreground_light"/> <!-- not selected -->
+    
+</selector>
+
diff --git a/core/res/res/color/primary_text_light.xml b/core/res/res/color/primary_text_light.xml
index e112034..a12c6b4 100644
--- a/core/res/res/color/primary_text_light.xml
+++ b/core/res/res/color/primary_text_light.xml
@@ -19,6 +19,7 @@
     <item android:state_window_focused="false" android:color="@android:color/bright_foreground_light"/>
     <item android:state_pressed="true" android:color="@android:color/bright_foreground_light"/>
     <item android:state_selected="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/>
     <item android:color="@android:color/bright_foreground_light"/> <!-- not selected -->
     
 </selector>
diff --git a/core/res/res/color/primary_text_light_nodisable.xml b/core/res/res/color/primary_text_light_nodisable.xml
index 2d35470..051cccf 100644
--- a/core/res/res/color/primary_text_light_nodisable.xml
+++ b/core/res/res/color/primary_text_light_nodisable.xml
@@ -16,6 +16,7 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_selected="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/>
     <item android:color="@android:color/bright_foreground_light"/> <!-- not selected -->
 </selector>
 
diff --git a/core/res/res/color/primary_text_nodisable_holo_dark.xml b/core/res/res/color/primary_text_nodisable_holo_dark.xml
new file mode 100644
index 0000000..f45088f
--- /dev/null
+++ b/core/res/res/color/primary_text_nodisable_holo_dark.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:color="@android:color/bright_foreground_dark"/> <!-- not selected -->
+</selector>
+
diff --git a/core/res/res/color/primary_text_nodisable_holo_light.xml b/core/res/res/color/primary_text_nodisable_holo_light.xml
new file mode 100644
index 0000000..331e8c3
--- /dev/null
+++ b/core/res/res/color/primary_text_nodisable_holo_light.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:color="@android:color/bright_foreground_light"/> <!-- not selected -->
+</selector>
+
diff --git a/core/res/res/color/search_url_text_holo.xml b/core/res/res/color/search_url_text_holo.xml
new file mode 100644
index 0000000..78093ba
--- /dev/null
+++ b/core/res/res/color/search_url_text_holo.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:color="@android:color/search_url_text_pressed"/>
+    <item android:state_selected="true" android:color="@android:color/search_url_text_selected"/>
+    <item android:color="@android:color/search_url_text_normal"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/secondary_text_dark.xml b/core/res/res/color/secondary_text_dark.xml
index c195ef0..1a38fa9 100644
--- a/core/res/res/color/secondary_text_dark.xml
+++ b/core/res/res/color/secondary_text_dark.xml
@@ -20,6 +20,7 @@
     <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
     <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
     <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
     <item android:state_pressed="true" android:color="@android:color/dim_foreground_dark_inverse"/>
     <item android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/>
     <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
diff --git a/core/res/res/color/secondary_text_dark_nodisable.xml b/core/res/res/color/secondary_text_dark_nodisable.xml
index 2c87a25..cbc7b02 100644
--- a/core/res/res/color/secondary_text_dark_nodisable.xml
+++ b/core/res/res/color/secondary_text_dark_nodisable.xml
@@ -16,5 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
     <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
 </selector>
diff --git a/core/res/res/color/secondary_text_holo_dark.xml b/core/res/res/color/secondary_text_holo_dark.xml
new file mode 100644
index 0000000..376156e
--- /dev/null
+++ b/core/res/res/color/secondary_text_holo_dark.xml
@@ -0,0 +1,27 @@
+<?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_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/>
+    <item android:state_window_focused="false" android:color="@android:color/dim_foreground_dark"/>
+    <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
+    <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:state_pressed="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/>
+    <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/secondary_text_holo_light.xml b/core/res/res/color/secondary_text_holo_light.xml
new file mode 100644
index 0000000..b791aeb
--- /dev/null
+++ b/core/res/res/color/secondary_text_holo_light.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
+    <item android:state_window_focused="false" android:color="@android:color/dim_foreground_light"/>
+    <!-- Since there is only one selector (for both light and dark), the light's selected state shouldn't be inversed like the dark's. -->
+    <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
+    <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
+    <item android:state_pressed="true" android:color="@android:color/dim_foreground_light"/>
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_light"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/>
+    <item android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
+    <item android:color="@android:color/dim_foreground_light"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/secondary_text_light.xml b/core/res/res/color/secondary_text_light.xml
index 99249a5..293f4aa 100644
--- a/core/res/res/color/secondary_text_light.xml
+++ b/core/res/res/color/secondary_text_light.xml
@@ -22,6 +22,7 @@
     <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
     <item android:state_pressed="true" android:color="@android:color/dim_foreground_light"/>
     <item android:state_selected="true" android:color="@android:color/dim_foreground_light"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/>
     <item android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
     <item android:color="@android:color/dim_foreground_light"/> <!-- not selected -->
 </selector>
diff --git a/core/res/res/color/secondary_text_light_nodisable.xml b/core/res/res/color/secondary_text_light_nodisable.xml
index 2c87a25..cbc7b02 100644
--- a/core/res/res/color/secondary_text_light_nodisable.xml
+++ b/core/res/res/color/secondary_text_light_nodisable.xml
@@ -16,5 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
     <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
 </selector>
diff --git a/core/res/res/color/secondary_text_nodisable_holo_dark.xml b/core/res/res/color/secondary_text_nodisable_holo_dark.xml
new file mode 100644
index 0000000..8c22241
--- /dev/null
+++ b/core/res/res/color/secondary_text_nodisable_holo_dark.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_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/secondary_text_nodisable_holo_light.xml b/core/res/res/color/secondary_text_nodisable_holo_light.xml
new file mode 100644
index 0000000..8c22241
--- /dev/null
+++ b/core/res/res/color/secondary_text_nodisable_holo_light.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_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
+    <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
+</selector>
diff --git a/core/res/res/color/tertiary_text_holo_dark.xml b/core/res/res/color/tertiary_text_holo_dark.xml
new file mode 100644
index 0000000..269ff71
--- /dev/null
+++ b/core/res/res/color/tertiary_text_holo_dark.xml
@@ -0,0 +1,24 @@
+<?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_enabled="false" android:color="#808080"/>
+    <item android:state_window_focused="false" android:color="#808080"/>
+    <item android:state_pressed="true" android:color="#808080"/>
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_light"/>
+    <item android:color="#808080"/> <!-- not selected -->
+</selector>
+
diff --git a/core/res/res/color/tertiary_text_holo_light.xml b/core/res/res/color/tertiary_text_holo_light.xml
new file mode 100644
index 0000000..9e5c2d9
--- /dev/null
+++ b/core/res/res/color/tertiary_text_holo_light.xml
@@ -0,0 +1,24 @@
+<?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_enabled="false" android:color="#808080"/>
+    <item android:state_window_focused="false" android:color="#808080"/>
+    <item android:state_pressed="true" android:color="#808080"/>
+    <item android:state_selected="true" android:color="#808080"/>
+    <item android:color="#808080"/> <!-- not selected -->
+</selector>
+
diff --git a/core/res/res/color/widget_edittext_holo_dark.xml b/core/res/res/color/widget_edittext_holo_dark.xml
new file mode 100644
index 0000000..4f3eb62
--- /dev/null
+++ b/core/res/res/color/widget_edittext_holo_dark.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="false" android:color="@android:color/bright_foreground_light"/> <!-- unfocused -->
+    <item android:color="@android:color/bright_foreground_light"/>
+</selector>
diff --git a/core/res/res/color/widget_edittext_holo_light.xml b/core/res/res/color/widget_edittext_holo_light.xml
new file mode 100644
index 0000000..7b950d4
--- /dev/null
+++ b/core/res/res/color/widget_edittext_holo_light.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="false" android:color="@android:color/bright_foreground_dark"/> <!-- unfocused -->
+    <item android:color="@android:color/bright_foreground_dark"/>
+</selector>
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/btn_check_off.png b/core/res/res/drawable-hdpi/btn_check_off.png
old mode 100755
new mode 100644
index bb62e6f..aad9ef7
--- a/core/res/res/drawable-hdpi/btn_check_off.png
+++ b/core/res/res/drawable-hdpi/btn_check_off.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable.png b/core/res/res/drawable-hdpi/btn_check_off_disable.png
old mode 100755
new mode 100644
index b346381..eaee9e0
--- a/core/res/res/drawable-hdpi/btn_check_off_disable.png
+++ b/core/res/res/drawable-hdpi/btn_check_off_disable.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_focused.png b/core/res/res/drawable-hdpi/btn_check_off_disable_focused.png
old mode 100755
new mode 100644
index 8663369..6d2c293
--- a/core/res/res/drawable-hdpi/btn_check_off_disable_focused.png
+++ b/core/res/res/drawable-hdpi/btn_check_off_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_dark.png
new file mode 100644
index 0000000..d93e580
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_light.png
new file mode 100644
index 0000000..ffbe776
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_disable_holo_dark.png
new file mode 100644
index 0000000..d93e580
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_disable_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_disable_holo_light.png
new file mode 100644
index 0000000..ffbe776
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_disable_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_holo_dark.png
new file mode 100644
index 0000000..4148ac4
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_holo_light.png
new file mode 100644
index 0000000..2cc946f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_pressed.png b/core/res/res/drawable-hdpi/btn_check_off_pressed.png
old mode 100755
new mode 100644
index 67e49df..1c442e9
--- a/core/res/res/drawable-hdpi/btn_check_off_pressed.png
+++ b/core/res/res/drawable-hdpi/btn_check_off_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_dark.png
new file mode 100644
index 0000000..9f9cb01
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_light.png
new file mode 100644
index 0000000..56425d2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_selected.png b/core/res/res/drawable-hdpi/btn_check_off_selected.png
old mode 100755
new mode 100644
index 1791d1f..b852b2c
--- a/core/res/res/drawable-hdpi/btn_check_off_selected.png
+++ b/core/res/res/drawable-hdpi/btn_check_off_selected.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_selected_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_selected_holo_dark.png
new file mode 100644
index 0000000..f819928
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_selected_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_off_selected_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_selected_holo_light.png
new file mode 100644
index 0000000..57cb512
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_off_selected_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on.png b/core/res/res/drawable-hdpi/btn_check_on.png
old mode 100755
new mode 100644
index 15cd25e..cd5c181
--- a/core/res/res/drawable-hdpi/btn_check_on.png
+++ b/core/res/res/drawable-hdpi/btn_check_on.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable.png b/core/res/res/drawable-hdpi/btn_check_on_disable.png
old mode 100755
new mode 100644
index e3fe323..b4fc51a
--- a/core/res/res/drawable-hdpi/btn_check_on_disable.png
+++ b/core/res/res/drawable-hdpi/btn_check_on_disable.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_focused.png b/core/res/res/drawable-hdpi/btn_check_on_disable_focused.png
old mode 100755
new mode 100644
index fa41bb7..bf34647
--- a/core/res/res/drawable-hdpi/btn_check_on_disable_focused.png
+++ b/core/res/res/drawable-hdpi/btn_check_on_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png
new file mode 100644
index 0000000..1f7aeee
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_light.png
new file mode 100644
index 0000000..1f740ad
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_disable_holo_dark.png
new file mode 100644
index 0000000..1f7aeee
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_disable_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_disable_holo_light.png
new file mode 100644
index 0000000..1f740ad
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_disable_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_holo_dark.png
new file mode 100644
index 0000000..d0c4415
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_holo_light.png
new file mode 100644
index 0000000..af84d4b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_pressed.png b/core/res/res/drawable-hdpi/btn_check_on_pressed.png
old mode 100755
new mode 100644
index 906e283..fa5c7a2
--- a/core/res/res/drawable-hdpi/btn_check_on_pressed.png
+++ b/core/res/res/drawable-hdpi/btn_check_on_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_dark.png
new file mode 100644
index 0000000..91e5f14
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_light.png
new file mode 100644
index 0000000..0cf7ed2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_selected.png b/core/res/res/drawable-hdpi/btn_check_on_selected.png
old mode 100755
new mode 100644
index eb496a8..a6a21ad
--- a/core/res/res/drawable-hdpi/btn_check_on_selected.png
+++ b/core/res/res/drawable-hdpi/btn_check_on_selected.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_selected_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_selected_holo_dark.png
new file mode 100644
index 0000000..e758aab
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_selected_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_check_on_selected_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_selected_holo_light.png
new file mode 100644
index 0000000..2edf656
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_check_on_selected_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_circle_disable.png b/core/res/res/drawable-hdpi/btn_circle_disable.png
index 39652a8..d829716 100644
--- a/core/res/res/drawable-hdpi/btn_circle_disable.png
+++ b/core/res/res/drawable-hdpi/btn_circle_disable.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_circle_disable_focused.png b/core/res/res/drawable-hdpi/btn_circle_disable_focused.png
index 1aa7ffe..c1b5b6e 100644
--- a/core/res/res/drawable-hdpi/btn_circle_disable_focused.png
+++ b/core/res/res/drawable-hdpi/btn_circle_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_circle_normal.png b/core/res/res/drawable-hdpi/btn_circle_normal.png
old mode 100755
new mode 100644
index 6011219..bf3fb5a
--- a/core/res/res/drawable-hdpi/btn_circle_normal.png
+++ b/core/res/res/drawable-hdpi/btn_circle_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_circle_pressed.png b/core/res/res/drawable-hdpi/btn_circle_pressed.png
old mode 100755
new mode 100644
index 4942e50..50e22e6
--- a/core/res/res/drawable-hdpi/btn_circle_pressed.png
+++ b/core/res/res/drawable-hdpi/btn_circle_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_circle_selected.png b/core/res/res/drawable-hdpi/btn_circle_selected.png
old mode 100755
new mode 100644
index fe49a40..cfc68fb
--- a/core/res/res/drawable-hdpi/btn_circle_selected.png
+++ b/core/res/res/drawable-hdpi/btn_circle_selected.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_close_normal.png b/core/res/res/drawable-hdpi/btn_close_normal.png
old mode 100755
new mode 100644
index 47f11e5..38b49f1
--- a/core/res/res/drawable-hdpi/btn_close_normal.png
+++ b/core/res/res/drawable-hdpi/btn_close_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_close_pressed.png b/core/res/res/drawable-hdpi/btn_close_pressed.png
old mode 100755
new mode 100644
index 5b96b4e..aa9ea49f0
--- a/core/res/res/drawable-hdpi/btn_close_pressed.png
+++ b/core/res/res/drawable-hdpi/btn_close_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_close_selected.png b/core/res/res/drawable-hdpi/btn_close_selected.png
old mode 100755
new mode 100644
index e27d684..870c670
--- a/core/res/res/drawable-hdpi/btn_close_selected.png
+++ b/core/res/res/drawable-hdpi/btn_close_selected.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_default.png b/core/res/res/drawable-hdpi/btn_code_lock_default.png
index 4469ce0..df3137f 100644
--- a/core/res/res/drawable-hdpi/btn_code_lock_default.png
+++ b/core/res/res/drawable-hdpi/btn_code_lock_default.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_touched.png b/core/res/res/drawable-hdpi/btn_code_lock_touched.png
index b90508c..bf9e46a 100644
--- a/core/res/res/drawable-hdpi/btn_code_lock_touched.png
+++ b/core/res/res/drawable-hdpi/btn_code_lock_touched.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png
new file mode 100644
index 0000000..3deb385
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png
new file mode 100644
index 0000000..de378a5
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png
new file mode 100644
index 0000000..35f8b3d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png
new file mode 100644
index 0000000..3f45375
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png
new file mode 100644
index 0000000..ea58bf7
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png
new file mode 100644
index 0000000..b225aaff
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_normal.9.png b/core/res/res/drawable-hdpi/btn_default_normal.9.png
old mode 100755
new mode 100644
index 803651b..329ce6e
--- a/core/res/res/drawable-hdpi/btn_default_normal.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_normal_disable.9.png b/core/res/res/drawable-hdpi/btn_default_normal_disable.9.png
old mode 100755
new mode 100644
index f4f01c7..a518c6b
--- a/core/res/res/drawable-hdpi/btn_default_normal_disable.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_normal_disable.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_normal_disable_focused.9.png b/core/res/res/drawable-hdpi/btn_default_normal_disable_focused.9.png
old mode 100755
new mode 100644
index 5376db2..71a05b7
--- a/core/res/res/drawable-hdpi/btn_default_normal_disable_focused.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_normal_disable_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png
new file mode 100644
index 0000000..b5b1533
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png
new file mode 100644
index 0000000..81b8a75
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_pressed.9.png b/core/res/res/drawable-hdpi/btn_default_pressed.9.png
old mode 100755
new mode 100644
index 4312c27..d9d02bf
--- a/core/res/res/drawable-hdpi/btn_default_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png
new file mode 100644
index 0000000..eb8d85a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png
new file mode 100644
index 0000000..6777ebf
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_selected.9.png b/core/res/res/drawable-hdpi/btn_default_selected.9.png
old mode 100755
new mode 100644
index 06b7790..ab7c612
--- a/core/res/res/drawable-hdpi/btn_default_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_small_normal.9.png b/core/res/res/drawable-hdpi/btn_default_small_normal.9.png
old mode 100755
new mode 100644
index 11a97d0..baafed6
--- a/core/res/res/drawable-hdpi/btn_default_small_normal.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_small_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_small_normal_disable.9.png b/core/res/res/drawable-hdpi/btn_default_small_normal_disable.9.png
old mode 100755
new mode 100644
index 8f86e3d..175197b
--- a/core/res/res/drawable-hdpi/btn_default_small_normal_disable.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_small_normal_disable.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_small_normal_disable_focused.9.png b/core/res/res/drawable-hdpi/btn_default_small_normal_disable_focused.9.png
old mode 100755
new mode 100644
index 7c8082f..ec1feff
--- a/core/res/res/drawable-hdpi/btn_default_small_normal_disable_focused.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_small_normal_disable_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_small_pressed.9.png b/core/res/res/drawable-hdpi/btn_default_small_pressed.9.png
old mode 100755
new mode 100644
index cf03f7e..c1f9a0f
--- a/core/res/res/drawable-hdpi/btn_default_small_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_small_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_small_selected.9.png b/core/res/res/drawable-hdpi/btn_default_small_selected.9.png
old mode 100755
new mode 100644
index 477a666..0ea3f40
--- a/core/res/res/drawable-hdpi/btn_default_small_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_small_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dialog_disable.png b/core/res/res/drawable-hdpi/btn_dialog_disable.png
old mode 100755
new mode 100644
index 4ff634b..2fc5d1a
--- a/core/res/res/drawable-hdpi/btn_dialog_disable.png
+++ b/core/res/res/drawable-hdpi/btn_dialog_disable.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dialog_normal.png b/core/res/res/drawable-hdpi/btn_dialog_normal.png
old mode 100755
new mode 100644
index e0cc339..c4a1026
--- a/core/res/res/drawable-hdpi/btn_dialog_normal.png
+++ b/core/res/res/drawable-hdpi/btn_dialog_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dialog_pressed.png b/core/res/res/drawable-hdpi/btn_dialog_pressed.png
old mode 100755
new mode 100644
index ed8e008..846f8bf
--- a/core/res/res/drawable-hdpi/btn_dialog_pressed.png
+++ b/core/res/res/drawable-hdpi/btn_dialog_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dialog_selected.png b/core/res/res/drawable-hdpi/btn_dialog_selected.png
old mode 100755
new mode 100644
index 9b1a100..659c289
--- a/core/res/res/drawable-hdpi/btn_dialog_selected.png
+++ b/core/res/res/drawable-hdpi/btn_dialog_selected.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png b/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png
index 0d25b6e..c6503c7 100644
--- a/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png
+++ b/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png b/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png
index e21fd75..152de8b 100644
--- a/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png
+++ b/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dropdown_normal.9.png b/core/res/res/drawable-hdpi/btn_dropdown_normal.9.png
old mode 100755
new mode 100644
index f10402f..9392495
--- a/core/res/res/drawable-hdpi/btn_dropdown_normal.9.png
+++ b/core/res/res/drawable-hdpi/btn_dropdown_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dropdown_pressed.9.png b/core/res/res/drawable-hdpi/btn_dropdown_pressed.9.png
old mode 100755
new mode 100644
index 366c6e0..beaba45
--- a/core/res/res/drawable-hdpi/btn_dropdown_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_dropdown_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dropdown_selected.9.png b/core/res/res/drawable-hdpi/btn_dropdown_selected.9.png
old mode 100755
new mode 100644
index f063c8d..ec51fc9
--- a/core/res/res/drawable-hdpi/btn_dropdown_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_dropdown_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_dark.png
new file mode 100644
index 0000000..2a7505b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_light.png
new file mode 100644
index 0000000..bbb01f0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_dark.png
new file mode 100644
index 0000000..b617a2a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_light.png
new file mode 100644
index 0000000..fd59f4a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_dark.png
new file mode 100644
index 0000000..5d17cde
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_light.png
new file mode 100644
index 0000000..b6b4bf1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_dark.png
new file mode 100644
index 0000000..eab5039
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_light.png
new file mode 100644
index 0000000..b6b4bf1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_dark.png
new file mode 100644
index 0000000..edf2296
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_light.png
new file mode 100644
index 0000000..68afa4c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_dark.png
new file mode 100644
index 0000000..c7df168
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_light.png
new file mode 100644
index 0000000..5a9087b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_off.png b/core/res/res/drawable-hdpi/btn_radio_off.png
old mode 100755
new mode 100644
index 48ee2ba..c0b14aa
--- a/core/res/res/drawable-hdpi/btn_radio_off.png
+++ b/core/res/res/drawable-hdpi/btn_radio_off.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_off_holo_dark.png
new file mode 100644
index 0000000..dd18b7a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_off_holo_light.png
new file mode 100644
index 0000000..66d538f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_off_pressed.png b/core/res/res/drawable-hdpi/btn_radio_off_pressed.png
old mode 100755
new mode 100644
index 5a4ad89..3189581
--- a/core/res/res/drawable-hdpi/btn_radio_off_pressed.png
+++ b/core/res/res/drawable-hdpi/btn_radio_off_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_dark.png
new file mode 100644
index 0000000..4e777f8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_light.png
new file mode 100644
index 0000000..6062033
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_off_selected.png b/core/res/res/drawable-hdpi/btn_radio_off_selected.png
old mode 100755
new mode 100644
index 7d5c676..f337703
--- a/core/res/res/drawable-hdpi/btn_radio_off_selected.png
+++ b/core/res/res/drawable-hdpi/btn_radio_off_selected.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_dark.png
new file mode 100644
index 0000000..683a883
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_light.png
new file mode 100644
index 0000000..19524ff
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_on.png b/core/res/res/drawable-hdpi/btn_radio_on.png
old mode 100755
new mode 100644
index 2472c20..c90d2eb
--- a/core/res/res/drawable-hdpi/btn_radio_on.png
+++ b/core/res/res/drawable-hdpi/btn_radio_on.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_on_holo_dark.png
new file mode 100644
index 0000000..2e1111b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_on_holo_light.png
new file mode 100644
index 0000000..90639ec
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_on_pressed.png b/core/res/res/drawable-hdpi/btn_radio_on_pressed.png
old mode 100755
new mode 100644
index 98d74ce..d79450b8
--- a/core/res/res/drawable-hdpi/btn_radio_on_pressed.png
+++ b/core/res/res/drawable-hdpi/btn_radio_on_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_dark.png
new file mode 100644
index 0000000..1907215
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_light.png
new file mode 100644
index 0000000..b51c7ad
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_on_selected.png b/core/res/res/drawable-hdpi/btn_radio_on_selected.png
old mode 100755
new mode 100644
index b6ab46c..db50c43
--- a/core/res/res/drawable-hdpi/btn_radio_on_selected.png
+++ b/core/res/res/drawable-hdpi/btn_radio_on_selected.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_dark.png
new file mode 100644
index 0000000..06d39cc
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_light.png
new file mode 100644
index 0000000..06a4314
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_dark.png
new file mode 100644
index 0000000..500490d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_light.png
new file mode 100644
index 0000000..f6690c6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_dark.png
new file mode 100644
index 0000000..933d2fe
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_light.png
new file mode 100644
index 0000000..c07445a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_square_overlay_disabled.png b/core/res/res/drawable-hdpi/btn_square_overlay_disabled.png
old mode 100755
new mode 100644
index ff06697..71a037e
--- a/core/res/res/drawable-hdpi/btn_square_overlay_disabled.png
+++ b/core/res/res/drawable-hdpi/btn_square_overlay_disabled.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_square_overlay_normal.png b/core/res/res/drawable-hdpi/btn_square_overlay_normal.png
old mode 100755
new mode 100644
index c311566..bf5da22
--- a/core/res/res/drawable-hdpi/btn_square_overlay_normal.png
+++ b/core/res/res/drawable-hdpi/btn_square_overlay_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_square_overlay_pressed.png b/core/res/res/drawable-hdpi/btn_square_overlay_pressed.png
old mode 100755
new mode 100644
index ac7fdef..52a302d
--- a/core/res/res/drawable-hdpi/btn_square_overlay_pressed.png
+++ b/core/res/res/drawable-hdpi/btn_square_overlay_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_square_overlay_selected.png b/core/res/res/drawable-hdpi/btn_square_overlay_selected.png
old mode 100755
new mode 100644
index ee54149..e065682
--- a/core/res/res/drawable-hdpi/btn_square_overlay_selected.png
+++ b/core/res/res/drawable-hdpi/btn_square_overlay_selected.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
new file mode 100755
index 0000000..128a8dd
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png
new file mode 100755
index 0000000..da05c23
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png
new file mode 100755
index 0000000..da66a98
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png
new file mode 100755
index 0000000..3ac8417
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png
new file mode 100755
index 0000000..fc9d493
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png
new file mode 100755
index 0000000..315ae3b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_holo.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_holo.9.png
new file mode 100755
index 0000000..f903bdb
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png
new file mode 100755
index 0000000..ee9590c6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png
new file mode 100755
index 0000000..cbc9da2c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_dark.9.png
new file mode 100755
index 0000000..1792063
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_light.9.png
new file mode 100755
index 0000000..097025b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
new file mode 100755
index 0000000..5b92d7c6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png
new file mode 100755
index 0000000..a244f45
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png
new file mode 100755
index 0000000..9218f91
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png
new file mode 100755
index 0000000..81802f8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png
new file mode 100755
index 0000000..9dea983
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png
new file mode 100755
index 0000000..fc2374b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png
new file mode 100755
index 0000000..4c1d89d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png
new file mode 100755
index 0000000..18bb6bd
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png
new file mode 100755
index 0000000..de78a9f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_dark.9.png
new file mode 100755
index 0000000..2269326
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_light.9.png
new file mode 100755
index 0000000..ead4ccf
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_divider_holo_dark.png b/core/res/res/drawable-hdpi/cab_divider_holo_dark.png
new file mode 100755
index 0000000..e6f61fc
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_divider_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_divider_holo_light.png b/core/res/res/drawable-hdpi/cab_divider_holo_light.png
new file mode 100755
index 0000000..2f97a29
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_divider_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_divider_vertical_dark.png b/core/res/res/drawable-hdpi/cab_divider_vertical_dark.png
new file mode 100755
index 0000000..b1f035c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_divider_vertical_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_divider_vertical_light.png b/core/res/res/drawable-hdpi/cab_divider_vertical_light.png
new file mode 100755
index 0000000..2183b12
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_divider_vertical_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_holo_dark.9.png b/core/res/res/drawable-hdpi/cab_holo_dark.9.png
new file mode 100755
index 0000000..662d63c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_holo_light.9.png b/core/res/res/drawable-hdpi/cab_holo_light.9.png
new file mode 100755
index 0000000..e8cbde1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_ic_close_focused_holo.png b/core/res/res/drawable-hdpi/cab_ic_close_focused_holo.png
new file mode 100755
index 0000000..861e0a1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_ic_close_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_ic_close_normal_holo.png b/core/res/res/drawable-hdpi/cab_ic_close_normal_holo.png
new file mode 100755
index 0000000..036f362
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_ic_close_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_ic_close_pressed_holo.png b/core/res/res/drawable-hdpi/cab_ic_close_pressed_holo.png
new file mode 100755
index 0000000..be8c2ff
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_ic_close_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_dark.png
new file mode 100644
index 0000000..6de74a7
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_light.png b/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_light.png
new file mode 100644
index 0000000..a0e201d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_dark.png
new file mode 100644
index 0000000..9bb69c4
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_light.png b/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_light.png
new file mode 100644
index 0000000..bffc5aa
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_focused_off_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_focused_off_holo_dark.png
new file mode 100644
index 0000000..bbe04b6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_focused_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_focused_off_holo_light.png b/core/res/res/drawable-hdpi/checkbox_focused_off_holo_light.png
new file mode 100644
index 0000000..62f1efa
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_focused_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_focused_on_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_focused_on_holo_dark.png
new file mode 100644
index 0000000..c4026a8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_focused_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_focused_on_holo_light.png b/core/res/res/drawable-hdpi/checkbox_focused_on_holo_light.png
new file mode 100644
index 0000000..409aa3e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_focused_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_normal_off_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_normal_off_holo_dark.png
new file mode 100644
index 0000000..10f1bc4
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_normal_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_normal_off_holo_light.png b/core/res/res/drawable-hdpi/checkbox_normal_off_holo_light.png
new file mode 100644
index 0000000..20a1efa
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_normal_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_normal_on_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_normal_on_holo_dark.png
new file mode 100644
index 0000000..0a10ec8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_normal_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_normal_on_holo_light.png b/core/res/res/drawable-hdpi/checkbox_normal_on_holo_light.png
new file mode 100644
index 0000000..34b53ee
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_normal_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_dark.png
new file mode 100644
index 0000000..7f14620
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_light.png b/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_light.png
new file mode 100644
index 0000000..cabf936
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_dark.png
new file mode 100644
index 0000000..bcddb31
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_light.png b/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_light.png
new file mode 100644
index 0000000..84160e5
--- /dev/null
+++ b/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_light.png
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/dialog_bottom_holo.9.png b/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png
new file mode 100644
index 0000000..3a84de9
--- /dev/null
+++ b/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/dialog_full_holo.9.png b/core/res/res/drawable-hdpi/dialog_full_holo.9.png
new file mode 100644
index 0000000..5d2e4e1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/dialog_full_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/dialog_middle_holo.9.png b/core/res/res/drawable-hdpi/dialog_middle_holo.9.png
new file mode 100644
index 0000000..dc5e79d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/dialog_middle_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/dialog_top_holo.9.png b/core/res/res/drawable-hdpi/dialog_top_holo.9.png
new file mode 100644
index 0000000..0275c18
--- /dev/null
+++ b/core/res/res/drawable-hdpi/dialog_top_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_horizontal_bright.9.png b/core/res/res/drawable-hdpi/divider_horizontal_bright.9.png
old mode 100755
new mode 100644
index bd1cc0e..41b776b
--- a/core/res/res/drawable-hdpi/divider_horizontal_bright.9.png
+++ b/core/res/res/drawable-hdpi/divider_horizontal_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_horizontal_bright_opaque.9.png b/core/res/res/drawable-hdpi/divider_horizontal_bright_opaque.9.png
old mode 100755
new mode 100644
index bd1cc0e..eb75a22
--- a/core/res/res/drawable-hdpi/divider_horizontal_bright_opaque.9.png
+++ b/core/res/res/drawable-hdpi/divider_horizontal_bright_opaque.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_horizontal_dark.9.png b/core/res/res/drawable-hdpi/divider_horizontal_dark.9.png
index cb62721..55a5e53 100644
--- a/core/res/res/drawable-hdpi/divider_horizontal_dark.9.png
+++ b/core/res/res/drawable-hdpi/divider_horizontal_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png b/core/res/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png
old mode 100755
new mode 100644
index bd1cc0e..60e2cb2
--- a/core/res/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png
+++ b/core/res/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_horizontal_dim_dark.9.png b/core/res/res/drawable-hdpi/divider_horizontal_dim_dark.9.png
index 63859f7..cf34613 100644
--- a/core/res/res/drawable-hdpi/divider_horizontal_dim_dark.9.png
+++ b/core/res/res/drawable-hdpi/divider_horizontal_dim_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_horizontal_holo_dark.9.png b/core/res/res/drawable-hdpi/divider_horizontal_holo_dark.9.png
new file mode 100644
index 0000000..e8e1deb
--- /dev/null
+++ b/core/res/res/drawable-hdpi/divider_horizontal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_horizontal_holo_light.9.png b/core/res/res/drawable-hdpi/divider_horizontal_holo_light.9.png
new file mode 100644
index 0000000..9e6cbbe
--- /dev/null
+++ b/core/res/res/drawable-hdpi/divider_horizontal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_vertical_bright.9.png b/core/res/res/drawable-hdpi/divider_vertical_bright.9.png
index 99a67b9..41b776b 100644
--- a/core/res/res/drawable-hdpi/divider_vertical_bright.9.png
+++ b/core/res/res/drawable-hdpi/divider_vertical_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_vertical_bright_opaque.9.png b/core/res/res/drawable-hdpi/divider_vertical_bright_opaque.9.png
index 5c537ee..eb75a22 100644
--- a/core/res/res/drawable-hdpi/divider_vertical_bright_opaque.9.png
+++ b/core/res/res/drawable-hdpi/divider_vertical_bright_opaque.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_vertical_dark.9.png b/core/res/res/drawable-hdpi/divider_vertical_dark.9.png
index a6c9295..55a5e53 100644
--- a/core/res/res/drawable-hdpi/divider_vertical_dark.9.png
+++ b/core/res/res/drawable-hdpi/divider_vertical_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_vertical_dark_opaque.9.png b/core/res/res/drawable-hdpi/divider_vertical_dark_opaque.9.png
index 8f35315..60e2cb2 100644
--- a/core/res/res/drawable-hdpi/divider_vertical_dark_opaque.9.png
+++ b/core/res/res/drawable-hdpi/divider_vertical_dark_opaque.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_vertical_holo_dark.9.png b/core/res/res/drawable-hdpi/divider_vertical_holo_dark.9.png
new file mode 100644
index 0000000..deacb73
--- /dev/null
+++ b/core/res/res/drawable-hdpi/divider_vertical_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/divider_vertical_holo_light.9.png b/core/res/res/drawable-hdpi/divider_vertical_holo_light.9.png
new file mode 100644
index 0000000..cd2c826
--- /dev/null
+++ b/core/res/res/drawable-hdpi/divider_vertical_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/edit_query.png b/core/res/res/drawable-hdpi/edit_query.png
new file mode 100644
index 0000000..d3e64b2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/edit_query.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/edit_query_background_normal.9.png b/core/res/res/drawable-hdpi/edit_query_background_normal.9.png
new file mode 100644
index 0000000..c083129
--- /dev/null
+++ b/core/res/res/drawable-hdpi/edit_query_background_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/edit_query_background_pressed.9.png b/core/res/res/drawable-hdpi/edit_query_background_pressed.9.png
new file mode 100644
index 0000000..41f3970
--- /dev/null
+++ b/core/res/res/drawable-hdpi/edit_query_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/edit_query_background_selected.9.png b/core/res/res/drawable-hdpi/edit_query_background_selected.9.png
new file mode 100644
index 0000000..04a8d12
--- /dev/null
+++ b/core/res/res/drawable-hdpi/edit_query_background_selected.9.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/expander_ic_maximized.9.png b/core/res/res/drawable-hdpi/expander_ic_maximized.9.png
index 2ec27af..04943aa 100644
--- a/core/res/res/drawable-hdpi/expander_ic_maximized.9.png
+++ b/core/res/res/drawable-hdpi/expander_ic_maximized.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/expander_ic_minimized.9.png b/core/res/res/drawable-hdpi/expander_ic_minimized.9.png
index 0c19bb7..7bddbce 100644
--- a/core/res/res/drawable-hdpi/expander_ic_minimized.9.png
+++ b/core/res/res/drawable-hdpi/expander_ic_minimized.9.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_find_next.png b/core/res/res/drawable-hdpi/ic_btn_find_next.png
new file mode 100644
index 0000000..b696a6b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_btn_find_next.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_btn_find_prev.png b/core/res/res/drawable-hdpi/ic_btn_find_prev.png
new file mode 100644
index 0000000..5550c5a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_btn_find_prev.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/ic_btn_round_more_disabled.png b/core/res/res/drawable-hdpi/ic_btn_round_more_disabled.png
old mode 100755
new mode 100644
index 3f1176f..0125944
--- a/core/res/res/drawable-hdpi/ic_btn_round_more_disabled.png
+++ b/core/res/res/drawable-hdpi/ic_btn_round_more_disabled.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_btn_round_more_normal.png b/core/res/res/drawable-hdpi/ic_btn_round_more_normal.png
old mode 100755
new mode 100644
index 8abda4d..33d7f89
--- a/core/res/res/drawable-hdpi/ic_btn_round_more_normal.png
+++ b/core/res/res/drawable-hdpi/ic_btn_round_more_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_dialog_close_normal_holo.png b/core/res/res/drawable-hdpi/ic_dialog_close_normal_holo.png
new file mode 100644
index 0000000..5ee5bb8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_dialog_close_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_dialog_close_pressed_holo.png b/core/res/res/drawable-hdpi/ic_dialog_close_pressed_holo.png
new file mode 100644
index 0000000..792db06
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_dialog_close_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_dialog_focused_holo.png b/core/res/res/drawable-hdpi/ic_dialog_focused_holo.png
new file mode 100644
index 0000000..e208575
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_dialog_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_emergency.png b/core/res/res/drawable-hdpi/ic_emergency.png
index 89c05e3..b4465ff 100644
--- a/core/res/res/drawable-hdpi/ic_emergency.png
+++ b/core/res/res/drawable-hdpi/ic_emergency.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_input_add.png b/core/res/res/drawable-hdpi/ic_input_add.png
old mode 100755
new mode 100644
index f9ce574..d26ebac
--- a/core/res/res/drawable-hdpi/ic_input_add.png
+++ b/core/res/res/drawable-hdpi/ic_input_add.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_account_list.png b/core/res/res/drawable-hdpi/ic_menu_account_list.png
index c62261f..f858d2c 100644
--- a/core/res/res/drawable-hdpi/ic_menu_account_list.png
+++ b/core/res/res/drawable-hdpi/ic_menu_account_list.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_add.png b/core/res/res/drawable-hdpi/ic_menu_add.png
index 7b0dfc5..65cc01e 100644
--- a/core/res/res/drawable-hdpi/ic_menu_add.png
+++ b/core/res/res/drawable-hdpi/ic_menu_add.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_agenda.png b/core/res/res/drawable-hdpi/ic_menu_agenda.png
index 88659ae..6bb5cc8 100644
--- a/core/res/res/drawable-hdpi/ic_menu_agenda.png
+++ b/core/res/res/drawable-hdpi/ic_menu_agenda.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_allfriends.png b/core/res/res/drawable-hdpi/ic_menu_allfriends.png
index 61c5f6c..8d11ca1 100644
--- a/core/res/res/drawable-hdpi/ic_menu_allfriends.png
+++ b/core/res/res/drawable-hdpi/ic_menu_allfriends.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_always_landscape_portrait.png b/core/res/res/drawable-hdpi/ic_menu_always_landscape_portrait.png
index 3acae27..7ae1760 100644
--- a/core/res/res/drawable-hdpi/ic_menu_always_landscape_portrait.png
+++ b/core/res/res/drawable-hdpi/ic_menu_always_landscape_portrait.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_archive.png b/core/res/res/drawable-hdpi/ic_menu_archive.png
index 48fbcc4..9ca5c62 100644
--- a/core/res/res/drawable-hdpi/ic_menu_archive.png
+++ b/core/res/res/drawable-hdpi/ic_menu_archive.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_attachment.png b/core/res/res/drawable-hdpi/ic_menu_attachment.png
index 876377f..8f11153 100644
--- a/core/res/res/drawable-hdpi/ic_menu_attachment.png
+++ b/core/res/res/drawable-hdpi/ic_menu_attachment.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_back.png b/core/res/res/drawable-hdpi/ic_menu_back.png
index 7abf819..a6cd712 100644
--- a/core/res/res/drawable-hdpi/ic_menu_back.png
+++ b/core/res/res/drawable-hdpi/ic_menu_back.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_block.png b/core/res/res/drawable-hdpi/ic_menu_block.png
index 7e716c4..e1f9c2c 100644
--- a/core/res/res/drawable-hdpi/ic_menu_block.png
+++ b/core/res/res/drawable-hdpi/ic_menu_block.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_blocked_user.png b/core/res/res/drawable-hdpi/ic_menu_blocked_user.png
index b679bf3..3dd9a4a 100644
--- a/core/res/res/drawable-hdpi/ic_menu_blocked_user.png
+++ b/core/res/res/drawable-hdpi/ic_menu_blocked_user.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_call.png b/core/res/res/drawable-hdpi/ic_menu_call.png
index 93f97ce..2ccc087 100644
--- a/core/res/res/drawable-hdpi/ic_menu_call.png
+++ b/core/res/res/drawable-hdpi/ic_menu_call.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_camera.png b/core/res/res/drawable-hdpi/ic_menu_camera.png
index 4e10e3e..5a3850f 100644
--- a/core/res/res/drawable-hdpi/ic_menu_camera.png
+++ b/core/res/res/drawable-hdpi/ic_menu_camera.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_cc.png b/core/res/res/drawable-hdpi/ic_menu_cc.png
index 62510d2..47905a5 100644
--- a/core/res/res/drawable-hdpi/ic_menu_cc.png
+++ b/core/res/res/drawable-hdpi/ic_menu_cc.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_chat_dashboard.png b/core/res/res/drawable-hdpi/ic_menu_chat_dashboard.png
index dc22e6a..dde6741 100644
--- a/core/res/res/drawable-hdpi/ic_menu_chat_dashboard.png
+++ b/core/res/res/drawable-hdpi/ic_menu_chat_dashboard.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_clear_playlist.png b/core/res/res/drawable-hdpi/ic_menu_clear_playlist.png
index 45ce7e8..e6be48b 100644
--- a/core/res/res/drawable-hdpi/ic_menu_clear_playlist.png
+++ b/core/res/res/drawable-hdpi/ic_menu_clear_playlist.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_close_clear_cancel.png b/core/res/res/drawable-hdpi/ic_menu_close_clear_cancel.png
index 4683f61..a54ea9d 100644
--- a/core/res/res/drawable-hdpi/ic_menu_close_clear_cancel.png
+++ b/core/res/res/drawable-hdpi/ic_menu_close_clear_cancel.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_compass.png b/core/res/res/drawable-hdpi/ic_menu_compass.png
index 756bd22..104270f 100644
--- a/core/res/res/drawable-hdpi/ic_menu_compass.png
+++ b/core/res/res/drawable-hdpi/ic_menu_compass.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_compose.png b/core/res/res/drawable-hdpi/ic_menu_compose.png
index bc153fa..6ad379e 100644
--- a/core/res/res/drawable-hdpi/ic_menu_compose.png
+++ b/core/res/res/drawable-hdpi/ic_menu_compose.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_copy.png b/core/res/res/drawable-hdpi/ic_menu_copy.png
new file mode 100644
index 0000000..8f11153
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_copy.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_crop.png b/core/res/res/drawable-hdpi/ic_menu_crop.png
index 82970b8..0d4c9fe 100644
--- a/core/res/res/drawable-hdpi/ic_menu_crop.png
+++ b/core/res/res/drawable-hdpi/ic_menu_crop.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_cut.png b/core/res/res/drawable-hdpi/ic_menu_cut.png
new file mode 100644
index 0000000..6ad379e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_cut.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_day.png b/core/res/res/drawable-hdpi/ic_menu_day.png
index 39612e8..84429aa 100644
--- a/core/res/res/drawable-hdpi/ic_menu_day.png
+++ b/core/res/res/drawable-hdpi/ic_menu_day.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_delete.png b/core/res/res/drawable-hdpi/ic_menu_delete.png
index ce5ecc4..2aed26a 100644
--- a/core/res/res/drawable-hdpi/ic_menu_delete.png
+++ b/core/res/res/drawable-hdpi/ic_menu_delete.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_directions.png b/core/res/res/drawable-hdpi/ic_menu_directions.png
index 42423b3..23f6eb3 100644
--- a/core/res/res/drawable-hdpi/ic_menu_directions.png
+++ b/core/res/res/drawable-hdpi/ic_menu_directions.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_edit.png b/core/res/res/drawable-hdpi/ic_menu_edit.png
index 4748cda..602dd10 100644
--- a/core/res/res/drawable-hdpi/ic_menu_edit.png
+++ b/core/res/res/drawable-hdpi/ic_menu_edit.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_emoticons.png b/core/res/res/drawable-hdpi/ic_menu_emoticons.png
index cb520ae..2fab515 100644
--- a/core/res/res/drawable-hdpi/ic_menu_emoticons.png
+++ b/core/res/res/drawable-hdpi/ic_menu_emoticons.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_end_conversation.png b/core/res/res/drawable-hdpi/ic_menu_end_conversation.png
index e71bb36..c05a207 100644
--- a/core/res/res/drawable-hdpi/ic_menu_end_conversation.png
+++ b/core/res/res/drawable-hdpi/ic_menu_end_conversation.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_find.png b/core/res/res/drawable-hdpi/ic_menu_find.png
new file mode 100644
index 0000000..17ac694
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_find.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_forward.png b/core/res/res/drawable-hdpi/ic_menu_forward.png
index 6b1804f..606e6aa 100644
--- a/core/res/res/drawable-hdpi/ic_menu_forward.png
+++ b/core/res/res/drawable-hdpi/ic_menu_forward.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_friendslist.png b/core/res/res/drawable-hdpi/ic_menu_friendslist.png
index ab9c552..a90e573 100644
--- a/core/res/res/drawable-hdpi/ic_menu_friendslist.png
+++ b/core/res/res/drawable-hdpi/ic_menu_friendslist.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_gallery.png b/core/res/res/drawable-hdpi/ic_menu_gallery.png
index ba07941..7c528fa 100644
--- a/core/res/res/drawable-hdpi/ic_menu_gallery.png
+++ b/core/res/res/drawable-hdpi/ic_menu_gallery.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_goto.png b/core/res/res/drawable-hdpi/ic_menu_goto.png
index 49e68ce..a1acecb 100644
--- a/core/res/res/drawable-hdpi/ic_menu_goto.png
+++ b/core/res/res/drawable-hdpi/ic_menu_goto.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_help.png b/core/res/res/drawable-hdpi/ic_menu_help.png
index a5b4a2f..4300e86 100644
--- a/core/res/res/drawable-hdpi/ic_menu_help.png
+++ b/core/res/res/drawable-hdpi/ic_menu_help.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_home.png b/core/res/res/drawable-hdpi/ic_menu_home.png
index 949fd04..35cb52a 100644
--- a/core/res/res/drawable-hdpi/ic_menu_home.png
+++ b/core/res/res/drawable-hdpi/ic_menu_home.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_info_details.png b/core/res/res/drawable-hdpi/ic_menu_info_details.png
index 6b3f7fd..7696ceb 100644
--- a/core/res/res/drawable-hdpi/ic_menu_info_details.png
+++ b/core/res/res/drawable-hdpi/ic_menu_info_details.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_invite.png b/core/res/res/drawable-hdpi/ic_menu_invite.png
index f578523..3cb129f 100644
--- a/core/res/res/drawable-hdpi/ic_menu_invite.png
+++ b/core/res/res/drawable-hdpi/ic_menu_invite.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_login.png b/core/res/res/drawable-hdpi/ic_menu_login.png
index 29e2db6..d23ebf0 100644
--- a/core/res/res/drawable-hdpi/ic_menu_login.png
+++ b/core/res/res/drawable-hdpi/ic_menu_login.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_manage.png b/core/res/res/drawable-hdpi/ic_menu_manage.png
index c08e64f..c7c4cbce 100644
--- a/core/res/res/drawable-hdpi/ic_menu_manage.png
+++ b/core/res/res/drawable-hdpi/ic_menu_manage.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_mapmode.png b/core/res/res/drawable-hdpi/ic_menu_mapmode.png
index 80c2aa9..c895ccb 100644
--- a/core/res/res/drawable-hdpi/ic_menu_mapmode.png
+++ b/core/res/res/drawable-hdpi/ic_menu_mapmode.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_mark.png b/core/res/res/drawable-hdpi/ic_menu_mark.png
index e6126fb..724d787 100644
--- a/core/res/res/drawable-hdpi/ic_menu_mark.png
+++ b/core/res/res/drawable-hdpi/ic_menu_mark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_month.png b/core/res/res/drawable-hdpi/ic_menu_month.png
index 3bf3738..3e55ae6 100644
--- a/core/res/res/drawable-hdpi/ic_menu_month.png
+++ b/core/res/res/drawable-hdpi/ic_menu_month.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_more.png b/core/res/res/drawable-hdpi/ic_menu_more.png
index 052f8f2..ccecc1d 100644
--- a/core/res/res/drawable-hdpi/ic_menu_more.png
+++ b/core/res/res/drawable-hdpi/ic_menu_more.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_my_calendar.png b/core/res/res/drawable-hdpi/ic_menu_my_calendar.png
index afd696d..3d6ea1f3 100644
--- a/core/res/res/drawable-hdpi/ic_menu_my_calendar.png
+++ b/core/res/res/drawable-hdpi/ic_menu_my_calendar.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_mylocation.png b/core/res/res/drawable-hdpi/ic_menu_mylocation.png
index 379f15c..1bcb0cd 100644
--- a/core/res/res/drawable-hdpi/ic_menu_mylocation.png
+++ b/core/res/res/drawable-hdpi/ic_menu_mylocation.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_myplaces.png b/core/res/res/drawable-hdpi/ic_menu_myplaces.png
index 6cb7c8b..5f726d8 100644
--- a/core/res/res/drawable-hdpi/ic_menu_myplaces.png
+++ b/core/res/res/drawable-hdpi/ic_menu_myplaces.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_notifications.png b/core/res/res/drawable-hdpi/ic_menu_notifications.png
index 1b1c5ed..fb63937 100644
--- a/core/res/res/drawable-hdpi/ic_menu_notifications.png
+++ b/core/res/res/drawable-hdpi/ic_menu_notifications.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_paste.png b/core/res/res/drawable-hdpi/ic_menu_paste.png
new file mode 100644
index 0000000..5a3850f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_paste.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_play_clip.png b/core/res/res/drawable-hdpi/ic_menu_play_clip.png
index 439890c..ddde03a 100644
--- a/core/res/res/drawable-hdpi/ic_menu_play_clip.png
+++ b/core/res/res/drawable-hdpi/ic_menu_play_clip.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_preferences.png b/core/res/res/drawable-hdpi/ic_menu_preferences.png
index 039c721..81bca4a 100644
--- a/core/res/res/drawable-hdpi/ic_menu_preferences.png
+++ b/core/res/res/drawable-hdpi/ic_menu_preferences.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_recent_history.png b/core/res/res/drawable-hdpi/ic_menu_recent_history.png
index bbd5de3..0dd1627 100644
--- a/core/res/res/drawable-hdpi/ic_menu_recent_history.png
+++ b/core/res/res/drawable-hdpi/ic_menu_recent_history.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_refresh.png b/core/res/res/drawable-hdpi/ic_menu_refresh.png
index bbb08f1..53cacca 100644
--- a/core/res/res/drawable-hdpi/ic_menu_refresh.png
+++ b/core/res/res/drawable-hdpi/ic_menu_refresh.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_report_image.png b/core/res/res/drawable-hdpi/ic_menu_report_image.png
index 4db2187..b6aa5d6 100644
--- a/core/res/res/drawable-hdpi/ic_menu_report_image.png
+++ b/core/res/res/drawable-hdpi/ic_menu_report_image.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_revert.png b/core/res/res/drawable-hdpi/ic_menu_revert.png
index ffc67d9..11860a4 100644
--- a/core/res/res/drawable-hdpi/ic_menu_revert.png
+++ b/core/res/res/drawable-hdpi/ic_menu_revert.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_rotate.png b/core/res/res/drawable-hdpi/ic_menu_rotate.png
index 835e393..85115af 100644
--- a/core/res/res/drawable-hdpi/ic_menu_rotate.png
+++ b/core/res/res/drawable-hdpi/ic_menu_rotate.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_save.png b/core/res/res/drawable-hdpi/ic_menu_save.png
index 62d0b9a..fc26c5d 100644
--- a/core/res/res/drawable-hdpi/ic_menu_save.png
+++ b/core/res/res/drawable-hdpi/ic_menu_save.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_search.png b/core/res/res/drawable-hdpi/ic_menu_search.png
index 9154d6e..f78234e 100644
--- a/core/res/res/drawable-hdpi/ic_menu_search.png
+++ b/core/res/res/drawable-hdpi/ic_menu_search.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_select_all.png b/core/res/res/drawable-hdpi/ic_menu_select_all.png
new file mode 100644
index 0000000..dde6741
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_select_all.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_send.png b/core/res/res/drawable-hdpi/ic_menu_send.png
index 9597731..2567b58 100644
--- a/core/res/res/drawable-hdpi/ic_menu_send.png
+++ b/core/res/res/drawable-hdpi/ic_menu_send.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_set_as.png b/core/res/res/drawable-hdpi/ic_menu_set_as.png
index 83a87b9..7e79c15 100644
--- a/core/res/res/drawable-hdpi/ic_menu_set_as.png
+++ b/core/res/res/drawable-hdpi/ic_menu_set_as.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_share.png b/core/res/res/drawable-hdpi/ic_menu_share.png
index b49d9ad7..b41b348 100644
--- a/core/res/res/drawable-hdpi/ic_menu_share.png
+++ b/core/res/res/drawable-hdpi/ic_menu_share.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_slideshow.png b/core/res/res/drawable-hdpi/ic_menu_slideshow.png
index 6208585..925f4eb1 100644
--- a/core/res/res/drawable-hdpi/ic_menu_slideshow.png
+++ b/core/res/res/drawable-hdpi/ic_menu_slideshow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_sort_alphabetically.png b/core/res/res/drawable-hdpi/ic_menu_sort_alphabetically.png
index 94ee3be..5d68998 100644
--- a/core/res/res/drawable-hdpi/ic_menu_sort_alphabetically.png
+++ b/core/res/res/drawable-hdpi/ic_menu_sort_alphabetically.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_sort_by_size.png b/core/res/res/drawable-hdpi/ic_menu_sort_by_size.png
index b43bc57..c9388fd 100644
--- a/core/res/res/drawable-hdpi/ic_menu_sort_by_size.png
+++ b/core/res/res/drawable-hdpi/ic_menu_sort_by_size.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_star.png b/core/res/res/drawable-hdpi/ic_menu_star.png
index 30f6314..21a2c4b 100644
--- a/core/res/res/drawable-hdpi/ic_menu_star.png
+++ b/core/res/res/drawable-hdpi/ic_menu_star.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_start_conversation.png b/core/res/res/drawable-hdpi/ic_menu_start_conversation.png
index 0dfc024..d63d3a7 100644
--- a/core/res/res/drawable-hdpi/ic_menu_start_conversation.png
+++ b/core/res/res/drawable-hdpi/ic_menu_start_conversation.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_stop.png b/core/res/res/drawable-hdpi/ic_menu_stop.png
index 310b6ca..7c99ed4 100644
--- a/core/res/res/drawable-hdpi/ic_menu_stop.png
+++ b/core/res/res/drawable-hdpi/ic_menu_stop.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_today.png b/core/res/res/drawable-hdpi/ic_menu_today.png
index 653f70cd..4a9352d 100644
--- a/core/res/res/drawable-hdpi/ic_menu_today.png
+++ b/core/res/res/drawable-hdpi/ic_menu_today.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_upload.png b/core/res/res/drawable-hdpi/ic_menu_upload.png
index 7454ba3..d845112 100644
--- a/core/res/res/drawable-hdpi/ic_menu_upload.png
+++ b/core/res/res/drawable-hdpi/ic_menu_upload.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_upload_you_tube.png b/core/res/res/drawable-hdpi/ic_menu_upload_you_tube.png
index 448f6c4..df5fa7f 100644
--- a/core/res/res/drawable-hdpi/ic_menu_upload_you_tube.png
+++ b/core/res/res/drawable-hdpi/ic_menu_upload_you_tube.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_view.png b/core/res/res/drawable-hdpi/ic_menu_view.png
index 5b9f2b9..75155d4 100644
--- a/core/res/res/drawable-hdpi/ic_menu_view.png
+++ b/core/res/res/drawable-hdpi/ic_menu_view.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_week.png b/core/res/res/drawable-hdpi/ic_menu_week.png
index c184f2e..c216eca 100644
--- a/core/res/res/drawable-hdpi/ic_menu_week.png
+++ b/core/res/res/drawable-hdpi/ic_menu_week.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_zoom.png b/core/res/res/drawable-hdpi/ic_menu_zoom.png
index c1d18d5..9fa4d7e 100644
--- a/core/res/res/drawable-hdpi/ic_menu_zoom.png
+++ b/core/res/res/drawable-hdpi/ic_menu_zoom.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_search_category_default.png b/core/res/res/drawable-hdpi/ic_search_category_default.png
index d368c54..f78234e 100644
--- a/core/res/res/drawable-hdpi/ic_search_category_default.png
+++ b/core/res/res/drawable-hdpi/ic_search_category_default.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_green_up.png b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_green_up.png
index 6f85f38..6560696 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_green_up.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_green_up.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png
index bec07f1..698c3ec 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png
index 467a013..0102a61 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png
index c21b24e..82ad8f7 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png
index 87007a3..f9d0d33 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_confirm_gray.png b/core/res/res/drawable-hdpi/jog_tab_left_confirm_gray.png
old mode 100755
new mode 100644
index 9599fb5..3499208
--- a/core/res/res/drawable-hdpi/jog_tab_left_confirm_gray.png
+++ b/core/res/res/drawable-hdpi/jog_tab_left_confirm_gray.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_confirm_green.png b/core/res/res/drawable-hdpi/jog_tab_left_confirm_green.png
old mode 100755
new mode 100644
index 46d9ab3..91eaec8
--- a/core/res/res/drawable-hdpi/jog_tab_left_confirm_green.png
+++ b/core/res/res/drawable-hdpi/jog_tab_left_confirm_green.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_confirm_red.png b/core/res/res/drawable-hdpi/jog_tab_left_confirm_red.png
index 6c0dc0a..8818b9e 100644
--- a/core/res/res/drawable-hdpi/jog_tab_left_confirm_red.png
+++ b/core/res/res/drawable-hdpi/jog_tab_left_confirm_red.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_confirm_yellow.png b/core/res/res/drawable-hdpi/jog_tab_left_confirm_yellow.png
old mode 100755
new mode 100644
index 3f9fb8f..e5bc5f6
--- a/core/res/res/drawable-hdpi/jog_tab_left_confirm_yellow.png
+++ b/core/res/res/drawable-hdpi/jog_tab_left_confirm_yellow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_normal.png b/core/res/res/drawable-hdpi/jog_tab_left_normal.png
old mode 100755
new mode 100644
index 8b89538..5326c7c
--- a/core/res/res/drawable-hdpi/jog_tab_left_normal.png
+++ b/core/res/res/drawable-hdpi/jog_tab_left_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_pressed.png b/core/res/res/drawable-hdpi/jog_tab_left_pressed.png
old mode 100755
new mode 100644
index ec98790..7b906df
--- a/core/res/res/drawable-hdpi/jog_tab_left_pressed.png
+++ b/core/res/res/drawable-hdpi/jog_tab_left_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_confirm_gray.png b/core/res/res/drawable-hdpi/jog_tab_right_confirm_gray.png
old mode 100755
new mode 100644
index 2861e8d..ea8c315
--- a/core/res/res/drawable-hdpi/jog_tab_right_confirm_gray.png
+++ b/core/res/res/drawable-hdpi/jog_tab_right_confirm_gray.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_confirm_green.png b/core/res/res/drawable-hdpi/jog_tab_right_confirm_green.png
old mode 100755
new mode 100644
index e974bbc..aa0ceb9
--- a/core/res/res/drawable-hdpi/jog_tab_right_confirm_green.png
+++ b/core/res/res/drawable-hdpi/jog_tab_right_confirm_green.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_confirm_red.png b/core/res/res/drawable-hdpi/jog_tab_right_confirm_red.png
index 9647fa6..d772fb6 100644
--- a/core/res/res/drawable-hdpi/jog_tab_right_confirm_red.png
+++ b/core/res/res/drawable-hdpi/jog_tab_right_confirm_red.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_confirm_yellow.png b/core/res/res/drawable-hdpi/jog_tab_right_confirm_yellow.png
old mode 100755
new mode 100644
index ad878e1..3cfeb67
--- a/core/res/res/drawable-hdpi/jog_tab_right_confirm_yellow.png
+++ b/core/res/res/drawable-hdpi/jog_tab_right_confirm_yellow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_normal.png b/core/res/res/drawable-hdpi/jog_tab_right_normal.png
old mode 100755
new mode 100644
index 01bba0b..da7726b
--- a/core/res/res/drawable-hdpi/jog_tab_right_normal.png
+++ b/core/res/res/drawable-hdpi/jog_tab_right_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_pressed.png b/core/res/res/drawable-hdpi/jog_tab_right_pressed.png
old mode 100755
new mode 100644
index 647e802..450a325
--- a/core/res/res/drawable-hdpi/jog_tab_right_pressed.png
+++ b/core/res/res/drawable-hdpi/jog_tab_right_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_default.9.png b/core/res/res/drawable-hdpi/list_selector_background_default.9.png
new file mode 100644
index 0000000..25c6241
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_default_light.9.png b/core/res/res/drawable-hdpi/list_selector_background_default_light.9.png
new file mode 100644
index 0000000..c3e69f0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_default_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_disabled.9.png b/core/res/res/drawable-hdpi/list_selector_background_disabled.9.png
old mode 100755
new mode 100644
index 9e1c42a..ab377d8
--- a/core/res/res/drawable-hdpi/list_selector_background_disabled.9.png
+++ b/core/res/res/drawable-hdpi/list_selector_background_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_disabled_light.9.png b/core/res/res/drawable-hdpi/list_selector_background_disabled_light.9.png
new file mode 100644
index 0000000..8edc9cd
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_disabled_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_focus.9.png b/core/res/res/drawable-hdpi/list_selector_background_focus.9.png
deleted file mode 100755
index 5563c80..0000000
--- a/core/res/res/drawable-hdpi/list_selector_background_focus.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_focused.9.png b/core/res/res/drawable-hdpi/list_selector_background_focused.9.png
new file mode 100644
index 0000000..60bb454
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_focused_light.9.png b/core/res/res/drawable-hdpi/list_selector_background_focused_light.9.png
new file mode 100644
index 0000000..60bb454
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_focused_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_focused_selected.9.png b/core/res/res/drawable-hdpi/list_selector_background_focused_selected.9.png
new file mode 100644
index 0000000..b527da1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_focused_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_longpress.9.png b/core/res/res/drawable-hdpi/list_selector_background_longpress.9.png
old mode 100755
new mode 100644
index 72d3a08..bd74426
--- a/core/res/res/drawable-hdpi/list_selector_background_longpress.9.png
+++ b/core/res/res/drawable-hdpi/list_selector_background_longpress.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_longpress_light.9.png b/core/res/res/drawable-hdpi/list_selector_background_longpress_light.9.png
new file mode 100644
index 0000000..fc2ba2e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_longpress_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_pressed.9.png b/core/res/res/drawable-hdpi/list_selector_background_pressed.9.png
old mode 100755
new mode 100644
index 7568b30..d18d6f7
--- a/core/res/res/drawable-hdpi/list_selector_background_pressed.9.png
+++ b/core/res/res/drawable-hdpi/list_selector_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_pressed_light.9.png b/core/res/res/drawable-hdpi/list_selector_background_pressed_light.9.png
new file mode 100644
index 0000000..00786e5
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_pressed_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_selected.9.png b/core/res/res/drawable-hdpi/list_selector_background_selected.9.png
new file mode 100644
index 0000000..cbf50b3
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selector_background_selected_light.9.png b/core/res/res/drawable-hdpi/list_selector_background_selected_light.9.png
new file mode 100644
index 0000000..34ea86e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selector_background_selected_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/menu_background.9.png b/core/res/res/drawable-hdpi/menu_background.9.png
index 1b43435..60f0731 100644
--- a/core/res/res/drawable-hdpi/menu_background.9.png
+++ b/core/res/res/drawable-hdpi/menu_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png b/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png
index ec974d6..09eac9b 100644
--- a/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png
+++ b/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/menu_submenu_background.9.png b/core/res/res/drawable-hdpi/menu_submenu_background.9.png
index c7056e0..cbd4400 100644
--- a/core/res/res/drawable-hdpi/menu_submenu_background.9.png
+++ b/core/res/res/drawable-hdpi/menu_submenu_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/nav_divider.png b/core/res/res/drawable-hdpi/nav_divider.png
new file mode 100644
index 0000000..7ca3e61
--- /dev/null
+++ b/core/res/res/drawable-hdpi/nav_divider.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/overscroll_edge.png b/core/res/res/drawable-hdpi/overscroll_edge.png
deleted file mode 100644
index e8c1aa3..0000000
--- a/core/res/res/drawable-hdpi/overscroll_edge.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/overscroll_glow.png b/core/res/res/drawable-hdpi/overscroll_glow.png
deleted file mode 100644
index 3418384..0000000
--- a/core/res/res/drawable-hdpi/overscroll_glow.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/picture_emergency.png b/core/res/res/drawable-hdpi/picture_emergency.png
index 64972c2..b0f10f9 100644
--- a/core/res/res/drawable-hdpi/picture_emergency.png
+++ b/core/res/res/drawable-hdpi/picture_emergency.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_bottom_bright.9.png b/core/res/res/drawable-hdpi/popup_bottom_bright.9.png
old mode 100755
new mode 100644
index f4125a8..cca47d3
--- a/core/res/res/drawable-hdpi/popup_bottom_bright.9.png
+++ b/core/res/res/drawable-hdpi/popup_bottom_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_bottom_dark.9.png b/core/res/res/drawable-hdpi/popup_bottom_dark.9.png
old mode 100755
new mode 100644
index 734981a..62a0bd0
--- a/core/res/res/drawable-hdpi/popup_bottom_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_bottom_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_bottom_medium.9.png b/core/res/res/drawable-hdpi/popup_bottom_medium.9.png
old mode 100755
new mode 100644
index 26ede44..6ebb4f7
--- a/core/res/res/drawable-hdpi/popup_bottom_medium.9.png
+++ b/core/res/res/drawable-hdpi/popup_bottom_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_center_bright.9.png b/core/res/res/drawable-hdpi/popup_center_bright.9.png
old mode 100755
new mode 100644
index 102c84b..756e9ed
--- a/core/res/res/drawable-hdpi/popup_center_bright.9.png
+++ b/core/res/res/drawable-hdpi/popup_center_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_center_dark.9.png b/core/res/res/drawable-hdpi/popup_center_dark.9.png
index ac1f92d..77b4524 100644
--- a/core/res/res/drawable-hdpi/popup_center_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_center_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_center_medium.9.png b/core/res/res/drawable-hdpi/popup_center_medium.9.png
old mode 100755
new mode 100644
index 1ce2a6d..de4be2a
--- a/core/res/res/drawable-hdpi/popup_center_medium.9.png
+++ b/core/res/res/drawable-hdpi/popup_center_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_full_bright.9.png b/core/res/res/drawable-hdpi/popup_full_bright.9.png
old mode 100755
new mode 100644
index d98ab0c..6c30bec
--- a/core/res/res/drawable-hdpi/popup_full_bright.9.png
+++ b/core/res/res/drawable-hdpi/popup_full_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_full_dark.9.png b/core/res/res/drawable-hdpi/popup_full_dark.9.png
old mode 100755
new mode 100644
index 502adaf..fc8c00e
--- a/core/res/res/drawable-hdpi/popup_full_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_full_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_top_bright.9.png b/core/res/res/drawable-hdpi/popup_top_bright.9.png
old mode 100755
new mode 100644
index e52a603..ddd30ab
--- a/core/res/res/drawable-hdpi/popup_top_bright.9.png
+++ b/core/res/res/drawable-hdpi/popup_top_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_top_dark.9.png b/core/res/res/drawable-hdpi/popup_top_dark.9.png
old mode 100755
new mode 100644
index 3f7d61e..144d0fc
--- a/core/res/res/drawable-hdpi/popup_top_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_top_dark.9.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/scrollbar_handle_accelerated_anim2.9.png b/core/res/res/drawable-hdpi/scrollbar_handle_accelerated_anim2.9.png
index 0a28223..c916780 100644
--- a/core/res/res/drawable-hdpi/scrollbar_handle_accelerated_anim2.9.png
+++ b/core/res/res/drawable-hdpi/scrollbar_handle_accelerated_anim2.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinner_black_16.png b/core/res/res/drawable-hdpi/spinner_black_16.png
old mode 100755
new mode 100644
index 42eb734f..eb34867
--- a/core/res/res/drawable-hdpi/spinner_black_16.png
+++ b/core/res/res/drawable-hdpi/spinner_black_16.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinner_black_20.png b/core/res/res/drawable-hdpi/spinner_black_20.png
old mode 100755
new mode 100644
index c88fbc9..dac06d7
--- a/core/res/res/drawable-hdpi/spinner_black_20.png
+++ b/core/res/res/drawable-hdpi/spinner_black_20.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinner_black_48.png b/core/res/res/drawable-hdpi/spinner_black_48.png
old mode 100755
new mode 100644
index 7f9c4f3..337f72a
--- a/core/res/res/drawable-hdpi/spinner_black_48.png
+++ b/core/res/res/drawable-hdpi/spinner_black_48.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinner_black_76.png b/core/res/res/drawable-hdpi/spinner_black_76.png
old mode 100755
new mode 100644
index c1c177e..2edc3e7
--- a/core/res/res/drawable-hdpi/spinner_black_76.png
+++ b/core/res/res/drawable-hdpi/spinner_black_76.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinner_white_16.png b/core/res/res/drawable-hdpi/spinner_white_16.png
old mode 100755
new mode 100644
index 4e037ae..7914a68
--- a/core/res/res/drawable-hdpi/spinner_white_16.png
+++ b/core/res/res/drawable-hdpi/spinner_white_16.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinner_white_48.png b/core/res/res/drawable-hdpi/spinner_white_48.png
old mode 100755
new mode 100644
index d8519f2..faee8ca
--- a/core/res/res/drawable-hdpi/spinner_white_48.png
+++ b/core/res/res/drawable-hdpi/spinner_white_48.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinner_white_76.png b/core/res/res/drawable-hdpi/spinner_white_76.png
old mode 100755
new mode 100644
index 6d6aa94..cd26379
--- a/core/res/res/drawable-hdpi/spinner_white_76.png
+++ b/core/res/res/drawable-hdpi/spinner_white_76.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_call_mute.png b/core/res/res/drawable-hdpi/stat_notify_call_mute.png
index 0cf5ef5..b86d2ca 100755
--- a/core/res/res/drawable-hdpi/stat_notify_call_mute.png
+++ b/core/res/res/drawable-hdpi/stat_notify_call_mute.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_car_mode.png b/core/res/res/drawable-hdpi/stat_notify_car_mode.png
index e700d79..03499a4 100644
--- a/core/res/res/drawable-hdpi/stat_notify_car_mode.png
+++ b/core/res/res/drawable-hdpi/stat_notify_car_mode.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_chat.png b/core/res/res/drawable-hdpi/stat_notify_chat.png
old mode 100644
new mode 100755
index 71ea8de..097a979
--- a/core/res/res/drawable-hdpi/stat_notify_chat.png
+++ b/core/res/res/drawable-hdpi/stat_notify_chat.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_disk_full.png b/core/res/res/drawable-hdpi/stat_notify_disk_full.png
index 66e7380..6ceeda8 100755
--- a/core/res/res/drawable-hdpi/stat_notify_disk_full.png
+++ b/core/res/res/drawable-hdpi/stat_notify_disk_full.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_email_generic.png b/core/res/res/drawable-hdpi/stat_notify_email_generic.png
deleted file mode 100644
index bc5fcab..0000000
--- a/core/res/res/drawable-hdpi/stat_notify_email_generic.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_error.png b/core/res/res/drawable-hdpi/stat_notify_error.png
old mode 100755
new mode 100644
index b3a18b3..37c8853
--- a/core/res/res/drawable-hdpi/stat_notify_error.png
+++ b/core/res/res/drawable-hdpi/stat_notify_error.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_gmail.png b/core/res/res/drawable-hdpi/stat_notify_gmail.png
deleted file mode 100644
index ea8beae..0000000
--- a/core/res/res/drawable-hdpi/stat_notify_gmail.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_missed_call.png b/core/res/res/drawable-hdpi/stat_notify_missed_call.png
old mode 100644
new mode 100755
index 3c19c93..192574d
--- a/core/res/res/drawable-hdpi/stat_notify_missed_call.png
+++ b/core/res/res/drawable-hdpi/stat_notify_missed_call.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_more.png b/core/res/res/drawable-hdpi/stat_notify_more.png
index f54b3d4..1c7f9db 100755
--- a/core/res/res/drawable-hdpi/stat_notify_more.png
+++ b/core/res/res/drawable-hdpi/stat_notify_more.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_sdcard.png b/core/res/res/drawable-hdpi/stat_notify_sdcard.png
old mode 100755
new mode 100644
index dd947a5..d3b624b
--- a/core/res/res/drawable-hdpi/stat_notify_sdcard.png
+++ b/core/res/res/drawable-hdpi/stat_notify_sdcard.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_sdcard_prepare.png b/core/res/res/drawable-hdpi/stat_notify_sdcard_prepare.png
old mode 100755
new mode 100644
index 4b9b9ca..a483ba2
--- a/core/res/res/drawable-hdpi/stat_notify_sdcard_prepare.png
+++ b/core/res/res/drawable-hdpi/stat_notify_sdcard_prepare.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_sdcard_usb.png b/core/res/res/drawable-hdpi/stat_notify_sdcard_usb.png
old mode 100755
new mode 100644
index fb2b26a..a5e369e2
--- a/core/res/res/drawable-hdpi/stat_notify_sdcard_usb.png
+++ b/core/res/res/drawable-hdpi/stat_notify_sdcard_usb.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_sim_toolkit.png b/core/res/res/drawable-hdpi/stat_notify_sim_toolkit.png
index 8865bda..3b14c26 100755
--- a/core/res/res/drawable-hdpi/stat_notify_sim_toolkit.png
+++ b/core/res/res/drawable-hdpi/stat_notify_sim_toolkit.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_sync.png b/core/res/res/drawable-hdpi/stat_notify_sync.png
index 004cfab..6f9cf84 100755
--- a/core/res/res/drawable-hdpi/stat_notify_sync.png
+++ b/core/res/res/drawable-hdpi/stat_notify_sync.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_sync_anim0.png b/core/res/res/drawable-hdpi/stat_notify_sync_anim0.png
index 6973fc5..6f9cf84 100755
--- a/core/res/res/drawable-hdpi/stat_notify_sync_anim0.png
+++ b/core/res/res/drawable-hdpi/stat_notify_sync_anim0.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_sync_error.png b/core/res/res/drawable-hdpi/stat_notify_sync_error.png
index 26b2446..6e3b545 100755
--- a/core/res/res/drawable-hdpi/stat_notify_sync_error.png
+++ b/core/res/res/drawable-hdpi/stat_notify_sync_error.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_voicemail.png b/core/res/res/drawable-hdpi/stat_notify_voicemail.png
index 5b77846..70b2411 100755
--- a/core/res/res/drawable-hdpi/stat_notify_voicemail.png
+++ b/core/res/res/drawable-hdpi/stat_notify_voicemail.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_wifi_in_range.png b/core/res/res/drawable-hdpi/stat_notify_wifi_in_range.png
old mode 100644
new mode 100755
index 0458124..e9405dd
--- a/core/res/res/drawable-hdpi/stat_notify_wifi_in_range.png
+++ b/core/res/res/drawable-hdpi/stat_notify_wifi_in_range.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_adb.png b/core/res/res/drawable-hdpi/stat_sys_adb.png
old mode 100755
new mode 100644
index fdf6c6c..aef8650
--- a/core/res/res/drawable-hdpi/stat_sys_adb.png
+++ b/core/res/res/drawable-hdpi/stat_sys_adb.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_10.png b/core/res/res/drawable-hdpi/stat_sys_battery_10.png
old mode 100755
new mode 100644
index 4486553..c81616b
--- a/core/res/res/drawable-hdpi/stat_sys_battery_10.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_10.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_100.png b/core/res/res/drawable-hdpi/stat_sys_battery_100.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_20.png b/core/res/res/drawable-hdpi/stat_sys_battery_20.png
old mode 100755
new mode 100644
index c8f9c92..eb5ef09
--- a/core/res/res/drawable-hdpi/stat_sys_battery_20.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_20.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_40.png b/core/res/res/drawable-hdpi/stat_sys_battery_40.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_60.png b/core/res/res/drawable-hdpi/stat_sys_battery_60.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_80.png b/core/res/res/drawable-hdpi/stat_sys_battery_80.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim0.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim0.png
old mode 100755
new mode 100644
index c7464f7..9a6c683
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim0.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim0.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim1.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim1.png
old mode 100755
new mode 100644
index 997feb3..c40c622
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim1.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_unknown.png b/core/res/res/drawable-hdpi/stat_sys_battery_unknown.png
old mode 100755
new mode 100644
index dadfe8d..1a9abaf
--- a/core/res/res/drawable-hdpi/stat_sys_battery_unknown.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_unknown.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_data_bluetooth.png b/core/res/res/drawable-hdpi/stat_sys_data_bluetooth.png
old mode 100755
new mode 100644
index e8fbc9e..7a8b78f
--- a/core/res/res/drawable-hdpi/stat_sys_data_bluetooth.png
+++ b/core/res/res/drawable-hdpi/stat_sys_data_bluetooth.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_data_usb.png b/core/res/res/drawable-hdpi/stat_sys_data_usb.png
index e916fbb..4c14c07 100755
--- a/core/res/res/drawable-hdpi/stat_sys_data_usb.png
+++ b/core/res/res/drawable-hdpi/stat_sys_data_usb.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim0.png b/core/res/res/drawable-hdpi/stat_sys_download_anim0.png
index c127e6e..d9c9e4c 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim0.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim0.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_gps_on.png b/core/res/res/drawable-hdpi/stat_sys_gps_on.png
index f901ae8..99a8c6c 100644
--- a/core/res/res/drawable-hdpi/stat_sys_gps_on.png
+++ b/core/res/res/drawable-hdpi/stat_sys_gps_on.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_phone_call.png b/core/res/res/drawable-hdpi/stat_sys_phone_call.png
old mode 100755
new mode 100644
index 9b5f075..950713b
--- a/core/res/res/drawable-hdpi/stat_sys_phone_call.png
+++ b/core/res/res/drawable-hdpi/stat_sys_phone_call.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_phone_call_forward.png b/core/res/res/drawable-hdpi/stat_sys_phone_call_forward.png
old mode 100755
new mode 100644
index 032f8f1..07a2e9d
--- a/core/res/res/drawable-hdpi/stat_sys_phone_call_forward.png
+++ b/core/res/res/drawable-hdpi/stat_sys_phone_call_forward.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_phone_call_on_hold.png b/core/res/res/drawable-hdpi/stat_sys_phone_call_on_hold.png
old mode 100755
new mode 100644
index 5b0a68d..033a558
--- a/core/res/res/drawable-hdpi/stat_sys_phone_call_on_hold.png
+++ b/core/res/res/drawable-hdpi/stat_sys_phone_call_on_hold.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_r_signal_3_cdma.png b/core/res/res/drawable-hdpi/stat_sys_r_signal_3_cdma.png
old mode 100755
new mode 100644
index 1d2f966..0a32d2e
--- a/core/res/res/drawable-hdpi/stat_sys_r_signal_3_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_r_signal_3_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_ra_signal_0_cdma.png b/core/res/res/drawable-hdpi/stat_sys_ra_signal_0_cdma.png
old mode 100755
new mode 100644
index 901058a..3333933
--- a/core/res/res/drawable-hdpi/stat_sys_ra_signal_0_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_ra_signal_0_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_ra_signal_1_cdma.png b/core/res/res/drawable-hdpi/stat_sys_ra_signal_1_cdma.png
old mode 100755
new mode 100644
index f5c5f98..6d69ed6
--- a/core/res/res/drawable-hdpi/stat_sys_ra_signal_1_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_ra_signal_1_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_ra_signal_2_cdma.png b/core/res/res/drawable-hdpi/stat_sys_ra_signal_2_cdma.png
old mode 100755
new mode 100644
index 82102b2..a78957d
--- a/core/res/res/drawable-hdpi/stat_sys_ra_signal_2_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_ra_signal_2_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_ra_signal_4_cdma.png b/core/res/res/drawable-hdpi/stat_sys_ra_signal_4_cdma.png
old mode 100755
new mode 100644
index c08cc86..44c1dca
--- a/core/res/res/drawable-hdpi/stat_sys_ra_signal_4_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_ra_signal_4_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_secure.png b/core/res/res/drawable-hdpi/stat_sys_secure.png
old mode 100755
new mode 100644
index 0889e49..4bae258
--- a/core/res/res/drawable-hdpi/stat_sys_secure.png
+++ b/core/res/res/drawable-hdpi/stat_sys_secure.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_0_cdma.png b/core/res/res/drawable-hdpi/stat_sys_signal_0_cdma.png
index af43e00..3c7db08 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_0_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_0_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_1_cdma.png b/core/res/res/drawable-hdpi/stat_sys_signal_1_cdma.png
index 4ffe421..f8f40a8 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_1_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_1_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_2_cdma.png b/core/res/res/drawable-hdpi/stat_sys_signal_2_cdma.png
index 6f27b96..a243195 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_2_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_2_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_3_cdma.png b/core/res/res/drawable-hdpi/stat_sys_signal_3_cdma.png
index ddc46b0..9c369e7 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_3_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_3_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_4_cdma.png b/core/res/res/drawable-hdpi/stat_sys_signal_4_cdma.png
index fb3cfe9..219bbbd 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_4_cdma.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_4_cdma.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_0.png b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_0.png
index b697ca4..ae18ecd 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_0.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_0.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_1.png b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_1.png
index a61de4d..4fed92c 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_1.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_2.png b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_2.png
index 62e0393..96f1248 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_2.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_3.png b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_3.png
index 09eae9d..b0e1328 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_3.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_4.png b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_4.png
index 4012ac5..da15645 100755
--- a/core/res/res/drawable-hdpi/stat_sys_signal_evdo_4.png
+++ b/core/res/res/drawable-hdpi/stat_sys_signal_evdo_4.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_speakerphone.png b/core/res/res/drawable-hdpi/stat_sys_speakerphone.png
old mode 100755
new mode 100644
index 21f96c4..51dea58
--- a/core/res/res/drawable-hdpi/stat_sys_speakerphone.png
+++ b/core/res/res/drawable-hdpi/stat_sys_speakerphone.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_bluetooth.png b/core/res/res/drawable-hdpi/stat_sys_tether_bluetooth.png
index 4ebee58..e43fbae 100644
--- a/core/res/res/drawable-hdpi/stat_sys_tether_bluetooth.png
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_bluetooth.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_general.png b/core/res/res/drawable-hdpi/stat_sys_tether_general.png
index e23f7f9..c42b00c 100644
--- a/core/res/res/drawable-hdpi/stat_sys_tether_general.png
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_general.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_usb.png b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png
index d432f9a..c6c533d 100644
--- a/core/res/res/drawable-hdpi/stat_sys_tether_usb.png
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_wifi.png b/core/res/res/drawable-hdpi/stat_sys_tether_wifi.png
index 74dfbba..9fcadef 100644
--- a/core/res/res/drawable-hdpi/stat_sys_tether_wifi.png
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_wifi.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_throttled.png b/core/res/res/drawable-hdpi/stat_sys_throttled.png
old mode 100755
new mode 100644
index 58eafc0..33c0521
--- a/core/res/res/drawable-hdpi/stat_sys_throttled.png
+++ b/core/res/res/drawable-hdpi/stat_sys_throttled.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_vp_phone_call.png b/core/res/res/drawable-hdpi/stat_sys_vp_phone_call.png
old mode 100755
new mode 100644
index 83e8ead..dfb3424
--- a/core/res/res/drawable-hdpi/stat_sys_vp_phone_call.png
+++ b/core/res/res/drawable-hdpi/stat_sys_vp_phone_call.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_vp_phone_call_on_hold.png b/core/res/res/drawable-hdpi/stat_sys_vp_phone_call_on_hold.png
old mode 100755
new mode 100644
index 9731c46..402295b
--- a/core/res/res/drawable-hdpi/stat_sys_vp_phone_call_on_hold.png
+++ b/core/res/res/drawable-hdpi/stat_sys_vp_phone_call_on_hold.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_warning.png b/core/res/res/drawable-hdpi/stat_sys_warning.png
old mode 100755
new mode 100644
index cb8a3d4..37c8853
--- a/core/res/res/drawable-hdpi/stat_sys_warning.png
+++ b/core/res/res/drawable-hdpi/stat_sys_warning.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/status_bar_header_background.9.png b/core/res/res/drawable-hdpi/status_bar_header_background.9.png
old mode 100755
new mode 100644
index 79d77aa..37b5fef
--- a/core/res/res/drawable-hdpi/status_bar_header_background.9.png
+++ b/core/res/res/drawable-hdpi/status_bar_header_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/status_bar_item_background_focus.9.png b/core/res/res/drawable-hdpi/status_bar_item_background_focus.9.png
index b07c7bc..0876bc6 100644
--- a/core/res/res/drawable-hdpi/status_bar_item_background_focus.9.png
+++ b/core/res/res/drawable-hdpi/status_bar_item_background_focus.9.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 de2f3c3..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/status_bar_item_background_pressed.9.png b/core/res/res/drawable-hdpi/status_bar_item_background_pressed.9.png
index b5eab83..343e4ca 100644
--- a/core/res/res/drawable-hdpi/status_bar_item_background_pressed.9.png
+++ b/core/res/res/drawable-hdpi/status_bar_item_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/statusbar_background.9.png b/core/res/res/drawable-hdpi/statusbar_background.9.png
new file mode 100644
index 0000000..a4be298
--- /dev/null
+++ b/core/res/res/drawable-hdpi/statusbar_background.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-hdpi/tab_arrow_left_holo_dark.png b/core/res/res/drawable-hdpi/tab_arrow_left_holo_dark.png
new file mode 100644
index 0000000..cfd6f78
--- /dev/null
+++ b/core/res/res/drawable-hdpi/tab_arrow_left_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_arrow_left_holo_light.png b/core/res/res/drawable-hdpi/tab_arrow_left_holo_light.png
new file mode 100644
index 0000000..036aa8c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/tab_arrow_left_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_arrow_right_holo_dark.png b/core/res/res/drawable-hdpi/tab_arrow_right_holo_dark.png
new file mode 100644
index 0000000..b226038
--- /dev/null
+++ b/core/res/res/drawable-hdpi/tab_arrow_right_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_arrow_right_holo_light.png b/core/res/res/drawable-hdpi/tab_arrow_right_holo_light.png
new file mode 100644
index 0000000..0e5fbe6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/tab_arrow_right_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_divider_holo_dark.png b/core/res/res/drawable-hdpi/tab_divider_holo_dark.png
new file mode 100644
index 0000000..112cb04
--- /dev/null
+++ b/core/res/res/drawable-hdpi/tab_divider_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_divider_holo_light.png b/core/res/res/drawable-hdpi/tab_divider_holo_light.png
new file mode 100644
index 0000000..1bf4d38
--- /dev/null
+++ b/core/res/res/drawable-hdpi/tab_divider_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_focus.9.png b/core/res/res/drawable-hdpi/tab_focus.9.png
index 7591c07..6e8a71f 100644
--- a/core/res/res/drawable-hdpi/tab_focus.9.png
+++ b/core/res/res/drawable-hdpi/tab_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_focus_bar_left.9.png b/core/res/res/drawable-hdpi/tab_focus_bar_left.9.png
old mode 100755
new mode 100644
index 0ee8347..51194a4
--- a/core/res/res/drawable-hdpi/tab_focus_bar_left.9.png
+++ b/core/res/res/drawable-hdpi/tab_focus_bar_left.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_focus_bar_right.9.png b/core/res/res/drawable-hdpi/tab_focus_bar_right.9.png
old mode 100755
new mode 100644
index 0ee8347..51194a4
--- a/core/res/res/drawable-hdpi/tab_focus_bar_right.9.png
+++ b/core/res/res/drawable-hdpi/tab_focus_bar_right.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_press.9.png b/core/res/res/drawable-hdpi/tab_press.9.png
old mode 100755
new mode 100644
index 01798a3..119b2c6
--- a/core/res/res/drawable-hdpi/tab_press.9.png
+++ b/core/res/res/drawable-hdpi/tab_press.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_press_bar_left.9.png b/core/res/res/drawable-hdpi/tab_press_bar_left.9.png
old mode 100755
new mode 100644
index ee129ba..dc2fbce
--- a/core/res/res/drawable-hdpi/tab_press_bar_left.9.png
+++ b/core/res/res/drawable-hdpi/tab_press_bar_left.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_press_bar_right.9.png b/core/res/res/drawable-hdpi/tab_press_bar_right.9.png
old mode 100755
new mode 100644
index ee129ba..dc2fbce
--- a/core/res/res/drawable-hdpi/tab_press_bar_right.9.png
+++ b/core/res/res/drawable-hdpi/tab_press_bar_right.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_selected.9.png b/core/res/res/drawable-hdpi/tab_selected.9.png
old mode 100755
new mode 100644
index 6ee775f..f036b9a
--- a/core/res/res/drawable-hdpi/tab_selected.9.png
+++ b/core/res/res/drawable-hdpi/tab_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_selected_bar_left.9.png b/core/res/res/drawable-hdpi/tab_selected_bar_left.9.png
old mode 100755
new mode 100644
index 03bcc13..aa935fe
--- a/core/res/res/drawable-hdpi/tab_selected_bar_left.9.png
+++ b/core/res/res/drawable-hdpi/tab_selected_bar_left.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_selected_bar_right.9.png b/core/res/res/drawable-hdpi/tab_selected_bar_right.9.png
old mode 100755
new mode 100644
index f228445..aa935fe
--- a/core/res/res/drawable-hdpi/tab_selected_bar_right.9.png
+++ b/core/res/res/drawable-hdpi/tab_selected_bar_right.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_selector_holo_dark.9.png b/core/res/res/drawable-hdpi/tab_selector_holo_dark.9.png
new file mode 100644
index 0000000..f01b9bc
--- /dev/null
+++ b/core/res/res/drawable-hdpi/tab_selector_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_strip_holo.9.png b/core/res/res/drawable-hdpi/tab_strip_holo.9.png
new file mode 100644
index 0000000..d937f6b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/tab_strip_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/tab_unselected.9.png b/core/res/res/drawable-hdpi/tab_unselected.9.png
old mode 100755
new mode 100644
index 67561e2..c3a1f30
--- a/core/res/res/drawable-hdpi/tab_unselected.9.png
+++ b/core/res/res/drawable-hdpi/tab_unselected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle.png b/core/res/res/drawable-hdpi/text_select_handle.png
index 80d48ab..93a5a15 100644
--- a/core/res/res/drawable-hdpi/text_select_handle.png
+++ b/core/res/res/drawable-hdpi/text_select_handle.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_default.9.png b/core/res/res/drawable-hdpi/textfield_default.9.png
old mode 100755
new mode 100644
index f7b6e99..4c20179
--- a/core/res/res/drawable-hdpi/textfield_default.9.png
+++ b/core/res/res/drawable-hdpi/textfield_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_default_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_default_holo_dark.9.png
new file mode 100644
index 0000000..7ec2192
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_default_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_default_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_default_holo_light.9.png
new file mode 100644
index 0000000..c03e4f6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_default_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_disabled.9.png b/core/res/res/drawable-hdpi/textfield_disabled.9.png
old mode 100755
new mode 100644
index 3011502..81569d1
--- a/core/res/res/drawable-hdpi/textfield_disabled.9.png
+++ b/core/res/res/drawable-hdpi/textfield_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_disabled_holo_dark.9.png
new file mode 100644
index 0000000..6642717
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_disabled_holo_light.9.png
new file mode 100644
index 0000000..9572752
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png b/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png
old mode 100755
new mode 100644
index e0f82eb..2591490
--- a/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png
+++ b/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_dark.9.png
new file mode 100644
index 0000000..0ad248c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_light.9.png
new file mode 100644
index 0000000..b7a07c4
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_pressed.9.png b/core/res/res/drawable-hdpi/textfield_pressed.9.png
old mode 100755
new mode 100644
index 296d3da..a42d87f
--- a/core/res/res/drawable-hdpi/textfield_pressed.9.png
+++ b/core/res/res/drawable-hdpi/textfield_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_pressed_holo_dark.9.png
new file mode 100644
index 0000000..a271ac9b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_pressed_holo_light.9.png
new file mode 100644
index 0000000..521722d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_search_default_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_search_default_holo_dark.9.png
new file mode 100644
index 0000000..5b62564
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_search_default_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_search_default_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_search_default_holo_light.9.png
new file mode 100644
index 0000000..881edeb
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_search_default_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_search_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_search_selected_holo_dark.9.png
new file mode 100644
index 0000000..cb3f35b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_search_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_search_selected_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_search_selected_holo_light.9.png
new file mode 100644
index 0000000..742b137
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_search_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_selected.9.png b/core/res/res/drawable-hdpi/textfield_selected.9.png
old mode 100755
new mode 100644
index cf2cae3..a36ed72
--- a/core/res/res/drawable-hdpi/textfield_selected.9.png
+++ b/core/res/res/drawable-hdpi/textfield_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_selected_holo_dark.9.png
new file mode 100644
index 0000000..a271ac9b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_selected_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_selected_holo_light.9.png
new file mode 100644
index 0000000..521722d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/title_bar_shadow.9.png b/core/res/res/drawable-hdpi/title_bar_shadow.9.png
old mode 100755
new mode 100644
index c3a0a23..e6dab63
--- a/core/res/res/drawable-hdpi/title_bar_shadow.9.png
+++ b/core/res/res/drawable-hdpi/title_bar_shadow.9.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/btn_check_off_disable_focused_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_dark.png
new file mode 100644
index 0000000..3fac4aa
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_light.png
new file mode 100644
index 0000000..3da9a46
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_disable_holo_dark.png
new file mode 100644
index 0000000..3fac4aa
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_disable_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_disable_holo_light.png
new file mode 100644
index 0000000..3da9a46
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_disable_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_holo_dark.png
new file mode 100644
index 0000000..b03f356
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_off_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_holo_light.png
new file mode 100644
index 0000000..9dbbd49
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_dark.png
new file mode 100644
index 0000000..0dcb9de
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_light.png
new file mode 100644
index 0000000..b25fdc4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_off_selected_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_selected_holo_dark.png
new file mode 100644
index 0000000..89ea3a8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_selected_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_off_selected_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_selected_holo_light.png
new file mode 100644
index 0000000..3fa45d9
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_off_selected_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png
new file mode 100644
index 0000000..48cc017
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_light.png
new file mode 100644
index 0000000..c9ebbca
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_disable_holo_dark.png
new file mode 100644
index 0000000..48cc017
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_disable_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_disable_holo_light.png
new file mode 100644
index 0000000..c9ebbca
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_disable_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_holo_dark.png
new file mode 100644
index 0000000..ca4d509
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_holo_light.png
new file mode 100644
index 0000000..158f3a1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_dark.png
new file mode 100644
index 0000000..0722cda
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_light.png
new file mode 100644
index 0000000..4de166e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_selected_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_selected_holo_dark.png
new file mode 100644
index 0000000..5b93f8b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_selected_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_selected_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_selected_holo_light.png
new file mode 100644
index 0000000..74ab250
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_check_on_selected_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png
new file mode 100644
index 0000000..9bc1ee8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png
new file mode 100644
index 0000000..cc643ea
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png
new file mode 100644
index 0000000..0586d52
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png
new file mode 100644
index 0000000..dd6c1a0
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png
new file mode 100644
index 0000000..e8f07cb
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png
new file mode 100644
index 0000000..0685f1e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png
new file mode 100644
index 0000000..6b33fa4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png
new file mode 100644
index 0000000..eb728ed
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png
new file mode 100644
index 0000000..4a15d9d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png
new file mode 100644
index 0000000..6718ff7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_dropdown_normal.9.png b/core/res/res/drawable-mdpi/btn_dropdown_normal.9.png
index 8540501..f6e9a19 100644
--- a/core/res/res/drawable-mdpi/btn_dropdown_normal.9.png
+++ b/core/res/res/drawable-mdpi/btn_dropdown_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_dropdown_pressed.9.png b/core/res/res/drawable-mdpi/btn_dropdown_pressed.9.png
index 9a50396..3bdf52d 100644
--- a/core/res/res/drawable-mdpi/btn_dropdown_pressed.9.png
+++ b/core/res/res/drawable-mdpi/btn_dropdown_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_dropdown_selected.9.png b/core/res/res/drawable-mdpi/btn_dropdown_selected.9.png
index a0a3fef..087956e 100644
--- a/core/res/res/drawable-mdpi/btn_dropdown_selected.9.png
+++ b/core/res/res/drawable-mdpi/btn_dropdown_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_dark.png
new file mode 100644
index 0000000..f21142e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_light.png
new file mode 100644
index 0000000..a1031fc
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_dark.png
new file mode 100644
index 0000000..61243c5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_light.png
new file mode 100644
index 0000000..faa55e0
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_dark.png
new file mode 100644
index 0000000..0c645da
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_light.png
new file mode 100644
index 0000000..5efc321
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_dark.png
new file mode 100644
index 0000000..96bcdc5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_light.png
new file mode 100644
index 0000000..5efc321
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_dark.png
new file mode 100644
index 0000000..96413ef
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_light.png
new file mode 100644
index 0000000..1cb5432
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_dark.png
new file mode 100644
index 0000000..2e8404a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_light.png
new file mode 100644
index 0000000..b3e14b1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_off_holo_dark.png
new file mode 100644
index 0000000..a3cef04
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_off_holo_light.png
new file mode 100644
index 0000000..e8def55
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_dark.png
new file mode 100644
index 0000000..1a9310b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_light.png
new file mode 100644
index 0000000..bc28b5b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_dark.png
new file mode 100644
index 0000000..2b0ddd1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_light.png
new file mode 100644
index 0000000..745cf19
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_on_holo_dark.png
new file mode 100644
index 0000000..9954500
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_on_holo_light.png
new file mode 100644
index 0000000..fa67a43
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_dark.png
new file mode 100644
index 0000000..c15c310
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_light.png
new file mode 100644
index 0000000..aa07c5a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_dark.png
new file mode 100644
index 0000000..e96a74f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_light.png
new file mode 100644
index 0000000..c51c96c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_dark.png
new file mode 100644
index 0000000..5f74c70
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_light.png
new file mode 100644
index 0000000..408e50e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_dark.png
new file mode 100644
index 0000000..ff60bc2
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_light.png
new file mode 100644
index 0000000..2125c24
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
new file mode 100755
index 0000000..7d1e16d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png
new file mode 100755
index 0000000..92e86cd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png
new file mode 100755
index 0000000..1cf473b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png
new file mode 100755
index 0000000..d6f2125
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png
new file mode 100755
index 0000000..31f7f8c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png
new file mode 100755
index 0000000..82425d5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_holo.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_holo.9.png
new file mode 100755
index 0000000..0ca659e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png
new file mode 100755
index 0000000..16f19fc
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png
new file mode 100755
index 0000000..e2c7702
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_dark.9.png
new file mode 100755
index 0000000..d61470c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_light.9.png
new file mode 100755
index 0000000..4019fee
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
new file mode 100755
index 0000000..ba354e3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png
new file mode 100755
index 0000000..9391b2e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png
new file mode 100755
index 0000000..601ff2c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png
new file mode 100755
index 0000000..90c259a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png
new file mode 100755
index 0000000..857c757
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png
new file mode 100755
index 0000000..ef16a48
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png
new file mode 100755
index 0000000..66cbe48
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png
new file mode 100755
index 0000000..d47ec8f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png
new file mode 100755
index 0000000..2951caf
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_dark.9.png
new file mode 100755
index 0000000..141b4dd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_light.9.png
new file mode 100755
index 0000000..913aed5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_divider_holo_dark.png b/core/res/res/drawable-mdpi/cab_divider_holo_dark.png
new file mode 100755
index 0000000..57cc8a4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_divider_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_divider_holo_light.png b/core/res/res/drawable-mdpi/cab_divider_holo_light.png
new file mode 100755
index 0000000..ec85701
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_divider_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_divider_vertical_dark.png b/core/res/res/drawable-mdpi/cab_divider_vertical_dark.png
new file mode 100755
index 0000000..f7ed6df
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_divider_vertical_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_divider_vertical_light.png b/core/res/res/drawable-mdpi/cab_divider_vertical_light.png
new file mode 100755
index 0000000..73ac0d9
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_divider_vertical_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_holo_dark.9.png b/core/res/res/drawable-mdpi/cab_holo_dark.9.png
new file mode 100755
index 0000000..6c85300
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_holo_light.9.png b/core/res/res/drawable-mdpi/cab_holo_light.9.png
new file mode 100755
index 0000000..c82352a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_ic_close_focused_holo.png b/core/res/res/drawable-mdpi/cab_ic_close_focused_holo.png
new file mode 100755
index 0000000..df170c4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_ic_close_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_ic_close_normal_holo.png b/core/res/res/drawable-mdpi/cab_ic_close_normal_holo.png
new file mode 100755
index 0000000..9482ce7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_ic_close_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_ic_close_pressed_holo.png b/core/res/res/drawable-mdpi/cab_ic_close_pressed_holo.png
new file mode 100755
index 0000000..d115d20
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_ic_close_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_dark.png
new file mode 100644
index 0000000..e1094be
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_light.png b/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_light.png
new file mode 100644
index 0000000..c17377c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_dark.png
new file mode 100644
index 0000000..f2c5290
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_light.png b/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_light.png
new file mode 100644
index 0000000..06bd903
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_focused_off_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_focused_off_holo_dark.png
new file mode 100644
index 0000000..be624c2
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_focused_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_focused_off_holo_light.png b/core/res/res/drawable-mdpi/checkbox_focused_off_holo_light.png
new file mode 100644
index 0000000..2493ce2
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_focused_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_focused_on_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_focused_on_holo_dark.png
new file mode 100644
index 0000000..7cdc1df
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_focused_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_focused_on_holo_light.png b/core/res/res/drawable-mdpi/checkbox_focused_on_holo_light.png
new file mode 100644
index 0000000..f977e72
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_focused_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_normal_off_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_normal_off_holo_dark.png
new file mode 100644
index 0000000..f824f76
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_normal_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_normal_off_holo_light.png b/core/res/res/drawable-mdpi/checkbox_normal_off_holo_light.png
new file mode 100644
index 0000000..a76f68c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_normal_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_normal_on_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_normal_on_holo_dark.png
new file mode 100644
index 0000000..e4fd418
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_normal_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_normal_on_holo_light.png b/core/res/res/drawable-mdpi/checkbox_normal_on_holo_light.png
new file mode 100644
index 0000000..d572ef5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_normal_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_dark.png
new file mode 100644
index 0000000..686707e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_light.png b/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_light.png
new file mode 100644
index 0000000..17dd1da
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_dark.png
new file mode 100644
index 0000000..8cf2b1b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_light.png b/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_light.png
new file mode 100644
index 0000000..c2df6da
--- /dev/null
+++ b/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/combobox_disabled.png b/core/res/res/drawable-mdpi/combobox_disabled.png
new file mode 100644
index 0000000..c32db7e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/combobox_disabled.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/combobox_nohighlight.png b/core/res/res/drawable-mdpi/combobox_nohighlight.png
new file mode 100644
index 0000000..1963316
--- /dev/null
+++ b/core/res/res/drawable-mdpi/combobox_nohighlight.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png b/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png
new file mode 100644
index 0000000..cb3d0f2
--- /dev/null
+++ b/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/dialog_full_holo.9.png b/core/res/res/drawable-mdpi/dialog_full_holo.9.png
new file mode 100644
index 0000000..0ec9421
--- /dev/null
+++ b/core/res/res/drawable-mdpi/dialog_full_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/dialog_middle_holo.9.png b/core/res/res/drawable-mdpi/dialog_middle_holo.9.png
new file mode 100644
index 0000000..36da5ca
--- /dev/null
+++ b/core/res/res/drawable-mdpi/dialog_middle_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/dialog_top_holo.9.png b/core/res/res/drawable-mdpi/dialog_top_holo.9.png
new file mode 100644
index 0000000..1ed519b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/dialog_top_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_horizontal_bright.9.png b/core/res/res/drawable-mdpi/divider_horizontal_bright.9.png
index 395227a..41b776b 100644
--- a/core/res/res/drawable-mdpi/divider_horizontal_bright.9.png
+++ b/core/res/res/drawable-mdpi/divider_horizontal_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_horizontal_bright_opaque.9.png b/core/res/res/drawable-mdpi/divider_horizontal_bright_opaque.9.png
index 5c537ee..eb75a22 100644
--- a/core/res/res/drawable-mdpi/divider_horizontal_bright_opaque.9.png
+++ b/core/res/res/drawable-mdpi/divider_horizontal_bright_opaque.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_horizontal_dark.9.png b/core/res/res/drawable-mdpi/divider_horizontal_dark.9.png
index bf45e38..55a5e53 100644
--- a/core/res/res/drawable-mdpi/divider_horizontal_dark.9.png
+++ b/core/res/res/drawable-mdpi/divider_horizontal_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png b/core/res/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png
index 9444f0d..60e2cb2 100644
--- a/core/res/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png
+++ b/core/res/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_horizontal_dim_dark.9.png b/core/res/res/drawable-mdpi/divider_horizontal_dim_dark.9.png
index 08838ca..cf34613 100644
--- a/core/res/res/drawable-mdpi/divider_horizontal_dim_dark.9.png
+++ b/core/res/res/drawable-mdpi/divider_horizontal_dim_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_horizontal_holo_dark.9.png b/core/res/res/drawable-mdpi/divider_horizontal_holo_dark.9.png
new file mode 100644
index 0000000..d6548c6
--- /dev/null
+++ b/core/res/res/drawable-mdpi/divider_horizontal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_horizontal_holo_light.9.png b/core/res/res/drawable-mdpi/divider_horizontal_holo_light.9.png
new file mode 100644
index 0000000..9a42dd2
--- /dev/null
+++ b/core/res/res/drawable-mdpi/divider_horizontal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_vertical_bright.9.png b/core/res/res/drawable-mdpi/divider_vertical_bright.9.png
index 395227a..41b776b 100644
--- a/core/res/res/drawable-mdpi/divider_vertical_bright.9.png
+++ b/core/res/res/drawable-mdpi/divider_vertical_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_vertical_bright_opaque.9.png b/core/res/res/drawable-mdpi/divider_vertical_bright_opaque.9.png
index 5c537ee..eb75a22 100644
--- a/core/res/res/drawable-mdpi/divider_vertical_bright_opaque.9.png
+++ b/core/res/res/drawable-mdpi/divider_vertical_bright_opaque.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_vertical_dark.9.png b/core/res/res/drawable-mdpi/divider_vertical_dark.9.png
index 702b878..55a5e53 100644
--- a/core/res/res/drawable-mdpi/divider_vertical_dark.9.png
+++ b/core/res/res/drawable-mdpi/divider_vertical_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_vertical_dark_opaque.9.png b/core/res/res/drawable-mdpi/divider_vertical_dark_opaque.9.png
index 8f35315..60e2cb2 100644
--- a/core/res/res/drawable-mdpi/divider_vertical_dark_opaque.9.png
+++ b/core/res/res/drawable-mdpi/divider_vertical_dark_opaque.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_vertical_holo_dark.9.png b/core/res/res/drawable-mdpi/divider_vertical_holo_dark.9.png
new file mode 100644
index 0000000..4c6968c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/divider_vertical_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/divider_vertical_holo_light.9.png b/core/res/res/drawable-mdpi/divider_vertical_holo_light.9.png
new file mode 100644
index 0000000..7ddf1b6
--- /dev/null
+++ b/core/res/res/drawable-mdpi/divider_vertical_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/edit_query.png b/core/res/res/drawable-mdpi/edit_query.png
new file mode 100644
index 0000000..22322cb
--- /dev/null
+++ b/core/res/res/drawable-mdpi/edit_query.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/edit_query_background_normal.9.png b/core/res/res/drawable-mdpi/edit_query_background_normal.9.png
new file mode 100644
index 0000000..8f957b8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/edit_query_background_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/edit_query_background_pressed.9.png b/core/res/res/drawable-mdpi/edit_query_background_pressed.9.png
new file mode 100644
index 0000000..c88d4d5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/edit_query_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/edit_query_background_selected.9.png b/core/res/res/drawable-mdpi/edit_query_background_selected.9.png
new file mode 100644
index 0000000..ffe5791
--- /dev/null
+++ b/core/res/res/drawable-mdpi/edit_query_background_selected.9.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/expander_ic_maximized.9.png b/core/res/res/drawable-mdpi/expander_ic_maximized.9.png
index d5c3276..465cabd 100644
--- a/core/res/res/drawable-mdpi/expander_ic_maximized.9.png
+++ b/core/res/res/drawable-mdpi/expander_ic_maximized.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/expander_ic_minimized.9.png b/core/res/res/drawable-mdpi/expander_ic_minimized.9.png
index 4515b42..9967ecb 100644
--- a/core/res/res/drawable-mdpi/expander_ic_minimized.9.png
+++ b/core/res/res/drawable-mdpi/expander_ic_minimized.9.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_find_next.png b/core/res/res/drawable-mdpi/ic_btn_find_next.png
new file mode 100644
index 0000000..abdc247
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_btn_find_next.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_btn_find_prev.png b/core/res/res/drawable-mdpi/ic_btn_find_prev.png
new file mode 100644
index 0000000..4e3da1d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_btn_find_prev.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/ic_dialog_close_normal_holo.png b/core/res/res/drawable-mdpi/ic_dialog_close_normal_holo.png
new file mode 100644
index 0000000..7f2a029
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_dialog_close_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_dialog_close_pressed_holo.png b/core/res/res/drawable-mdpi/ic_dialog_close_pressed_holo.png
new file mode 100644
index 0000000..daa8e18
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_dialog_close_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_dialog_focused_holo.png b/core/res/res/drawable-mdpi/ic_dialog_focused_holo.png
new file mode 100644
index 0000000..179f2de
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_dialog_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_account_list.png b/core/res/res/drawable-mdpi/ic_menu_account_list.png
index d3af01b..f0945b2 100644
--- a/core/res/res/drawable-mdpi/ic_menu_account_list.png
+++ b/core/res/res/drawable-mdpi/ic_menu_account_list.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_add.png b/core/res/res/drawable-mdpi/ic_menu_add.png
old mode 100644
new mode 100755
index 57a4099..6752bfd
--- a/core/res/res/drawable-mdpi/ic_menu_add.png
+++ b/core/res/res/drawable-mdpi/ic_menu_add.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_agenda.png b/core/res/res/drawable-mdpi/ic_menu_agenda.png
old mode 100644
new mode 100755
index 3fb0031..9f2c1dc
--- a/core/res/res/drawable-mdpi/ic_menu_agenda.png
+++ b/core/res/res/drawable-mdpi/ic_menu_agenda.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_allfriends.png b/core/res/res/drawable-mdpi/ic_menu_allfriends.png
old mode 100644
new mode 100755
index e4e2727..a5bd331
--- a/core/res/res/drawable-mdpi/ic_menu_allfriends.png
+++ b/core/res/res/drawable-mdpi/ic_menu_allfriends.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_always_landscape_portrait.png b/core/res/res/drawable-mdpi/ic_menu_always_landscape_portrait.png
index 451ccc9..68911c4 100644
--- a/core/res/res/drawable-mdpi/ic_menu_always_landscape_portrait.png
+++ b/core/res/res/drawable-mdpi/ic_menu_always_landscape_portrait.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_archive.png b/core/res/res/drawable-mdpi/ic_menu_archive.png
index 7231fbb5..a4599e3 100644
--- a/core/res/res/drawable-mdpi/ic_menu_archive.png
+++ b/core/res/res/drawable-mdpi/ic_menu_archive.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_attachment.png b/core/res/res/drawable-mdpi/ic_menu_attachment.png
index 83e0687..89d626f 100644
--- a/core/res/res/drawable-mdpi/ic_menu_attachment.png
+++ b/core/res/res/drawable-mdpi/ic_menu_attachment.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_back.png b/core/res/res/drawable-mdpi/ic_menu_back.png
index 91fa3e7..5ce50eb 100644
--- a/core/res/res/drawable-mdpi/ic_menu_back.png
+++ b/core/res/res/drawable-mdpi/ic_menu_back.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_block.png b/core/res/res/drawable-mdpi/ic_menu_block.png
index fce9297..422eeb1 100644
--- a/core/res/res/drawable-mdpi/ic_menu_block.png
+++ b/core/res/res/drawable-mdpi/ic_menu_block.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_blocked_user.png b/core/res/res/drawable-mdpi/ic_menu_blocked_user.png
index 5532245..5a5619b 100644
--- a/core/res/res/drawable-mdpi/ic_menu_blocked_user.png
+++ b/core/res/res/drawable-mdpi/ic_menu_blocked_user.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_call.png b/core/res/res/drawable-mdpi/ic_menu_call.png
index eb05949..a63f86b 100644
--- a/core/res/res/drawable-mdpi/ic_menu_call.png
+++ b/core/res/res/drawable-mdpi/ic_menu_call.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_camera.png b/core/res/res/drawable-mdpi/ic_menu_camera.png
old mode 100644
new mode 100755
index 7fddf7c..cdf7ca3
--- a/core/res/res/drawable-mdpi/ic_menu_camera.png
+++ b/core/res/res/drawable-mdpi/ic_menu_camera.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_cc.png b/core/res/res/drawable-mdpi/ic_menu_cc.png
index 97fb663..4876021 100644
--- a/core/res/res/drawable-mdpi/ic_menu_cc.png
+++ b/core/res/res/drawable-mdpi/ic_menu_cc.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_chat_dashboard.png b/core/res/res/drawable-mdpi/ic_menu_chat_dashboard.png
index 54e9023..37fd3cbd 100644
--- a/core/res/res/drawable-mdpi/ic_menu_chat_dashboard.png
+++ b/core/res/res/drawable-mdpi/ic_menu_chat_dashboard.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_clear_playlist.png b/core/res/res/drawable-mdpi/ic_menu_clear_playlist.png
index 52791a0..750db62 100644
--- a/core/res/res/drawable-mdpi/ic_menu_clear_playlist.png
+++ b/core/res/res/drawable-mdpi/ic_menu_clear_playlist.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_close_clear_cancel.png b/core/res/res/drawable-mdpi/ic_menu_close_clear_cancel.png
index 04a76b5..78222ea 100644
--- a/core/res/res/drawable-mdpi/ic_menu_close_clear_cancel.png
+++ b/core/res/res/drawable-mdpi/ic_menu_close_clear_cancel.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_compass.png b/core/res/res/drawable-mdpi/ic_menu_compass.png
index b5f885e..7717dde 100644
--- a/core/res/res/drawable-mdpi/ic_menu_compass.png
+++ b/core/res/res/drawable-mdpi/ic_menu_compass.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_compose.png b/core/res/res/drawable-mdpi/ic_menu_compose.png
index 121228c..1b4733e 100644
--- a/core/res/res/drawable-mdpi/ic_menu_compose.png
+++ b/core/res/res/drawable-mdpi/ic_menu_compose.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_copy.png b/core/res/res/drawable-mdpi/ic_menu_copy.png
new file mode 100644
index 0000000..89d626f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_copy.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_crop.png b/core/res/res/drawable-mdpi/ic_menu_crop.png
old mode 100644
new mode 100755
index 4892ca0..c0df996
--- a/core/res/res/drawable-mdpi/ic_menu_crop.png
+++ b/core/res/res/drawable-mdpi/ic_menu_crop.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_cut.png b/core/res/res/drawable-mdpi/ic_menu_cut.png
new file mode 100644
index 0000000..1b4733e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_cut.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_day.png b/core/res/res/drawable-mdpi/ic_menu_day.png
old mode 100644
new mode 100755
index f366c1b..db5d3a4
--- a/core/res/res/drawable-mdpi/ic_menu_day.png
+++ b/core/res/res/drawable-mdpi/ic_menu_day.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_delete.png b/core/res/res/drawable-mdpi/ic_menu_delete.png
old mode 100644
new mode 100755
index bb533f7..7d95494
--- a/core/res/res/drawable-mdpi/ic_menu_delete.png
+++ b/core/res/res/drawable-mdpi/ic_menu_delete.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_directions.png b/core/res/res/drawable-mdpi/ic_menu_directions.png
old mode 100644
new mode 100755
index fbac73d..00a288f
--- a/core/res/res/drawable-mdpi/ic_menu_directions.png
+++ b/core/res/res/drawable-mdpi/ic_menu_directions.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_edit.png b/core/res/res/drawable-mdpi/ic_menu_edit.png
old mode 100644
new mode 100755
index 1de85ca..41a9c2e
--- a/core/res/res/drawable-mdpi/ic_menu_edit.png
+++ b/core/res/res/drawable-mdpi/ic_menu_edit.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_emoticons.png b/core/res/res/drawable-mdpi/ic_menu_emoticons.png
index 95fd566..e8c4e47 100644
--- a/core/res/res/drawable-mdpi/ic_menu_emoticons.png
+++ b/core/res/res/drawable-mdpi/ic_menu_emoticons.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_end_conversation.png b/core/res/res/drawable-mdpi/ic_menu_end_conversation.png
index 9ddb03e..0ea0fcb 100644
--- a/core/res/res/drawable-mdpi/ic_menu_end_conversation.png
+++ b/core/res/res/drawable-mdpi/ic_menu_end_conversation.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_find.png b/core/res/res/drawable-mdpi/ic_menu_find.png
new file mode 100644
index 0000000..4d96348
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_find.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_forward.png b/core/res/res/drawable-mdpi/ic_menu_forward.png
index 632e4f6..0936fac 100644
--- a/core/res/res/drawable-mdpi/ic_menu_forward.png
+++ b/core/res/res/drawable-mdpi/ic_menu_forward.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_friendslist.png b/core/res/res/drawable-mdpi/ic_menu_friendslist.png
index 45baa48..8ec6b1a 100644
--- a/core/res/res/drawable-mdpi/ic_menu_friendslist.png
+++ b/core/res/res/drawable-mdpi/ic_menu_friendslist.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_gallery.png b/core/res/res/drawable-mdpi/ic_menu_gallery.png
old mode 100644
new mode 100755
index 2d74993..a3deef1
--- a/core/res/res/drawable-mdpi/ic_menu_gallery.png
+++ b/core/res/res/drawable-mdpi/ic_menu_gallery.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_goto.png b/core/res/res/drawable-mdpi/ic_menu_goto.png
index 3810d54..40183eb 100644
--- a/core/res/res/drawable-mdpi/ic_menu_goto.png
+++ b/core/res/res/drawable-mdpi/ic_menu_goto.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_help.png b/core/res/res/drawable-mdpi/ic_menu_help.png
index 4f65a61..7c55dfd 100644
--- a/core/res/res/drawable-mdpi/ic_menu_help.png
+++ b/core/res/res/drawable-mdpi/ic_menu_help.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_home.png b/core/res/res/drawable-mdpi/ic_menu_home.png
index 23dcecd..34943f6 100644
--- a/core/res/res/drawable-mdpi/ic_menu_home.png
+++ b/core/res/res/drawable-mdpi/ic_menu_home.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_info_details.png b/core/res/res/drawable-mdpi/ic_menu_info_details.png
old mode 100644
new mode 100755
index 1f8a7cd..1786d1e
--- a/core/res/res/drawable-mdpi/ic_menu_info_details.png
+++ b/core/res/res/drawable-mdpi/ic_menu_info_details.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_invite.png b/core/res/res/drawable-mdpi/ic_menu_invite.png
index ccbf317..7577e6d 100644
--- a/core/res/res/drawable-mdpi/ic_menu_invite.png
+++ b/core/res/res/drawable-mdpi/ic_menu_invite.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_login.png b/core/res/res/drawable-mdpi/ic_menu_login.png
index d489787..2b856bc 100644
--- a/core/res/res/drawable-mdpi/ic_menu_login.png
+++ b/core/res/res/drawable-mdpi/ic_menu_login.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_manage.png b/core/res/res/drawable-mdpi/ic_menu_manage.png
old mode 100644
new mode 100755
index a96bb0c..f155bbc
--- a/core/res/res/drawable-mdpi/ic_menu_manage.png
+++ b/core/res/res/drawable-mdpi/ic_menu_manage.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_mapmode.png b/core/res/res/drawable-mdpi/ic_menu_mapmode.png
index ef453e1..d85cab5 100644
--- a/core/res/res/drawable-mdpi/ic_menu_mapmode.png
+++ b/core/res/res/drawable-mdpi/ic_menu_mapmode.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_mark.png b/core/res/res/drawable-mdpi/ic_menu_mark.png
index d2a0116..5e95da7 100644
--- a/core/res/res/drawable-mdpi/ic_menu_mark.png
+++ b/core/res/res/drawable-mdpi/ic_menu_mark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_month.png b/core/res/res/drawable-mdpi/ic_menu_month.png
old mode 100644
new mode 100755
index d5285ab..bf6cb89
--- a/core/res/res/drawable-mdpi/ic_menu_month.png
+++ b/core/res/res/drawable-mdpi/ic_menu_month.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_more.png b/core/res/res/drawable-mdpi/ic_menu_more.png
index 168dcd3..b9fc5fa 100644
--- a/core/res/res/drawable-mdpi/ic_menu_more.png
+++ b/core/res/res/drawable-mdpi/ic_menu_more.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_my_calendar.png b/core/res/res/drawable-mdpi/ic_menu_my_calendar.png
old mode 100644
new mode 100755
index c1c9c20..0c88fd3
--- a/core/res/res/drawable-mdpi/ic_menu_my_calendar.png
+++ b/core/res/res/drawable-mdpi/ic_menu_my_calendar.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_mylocation.png b/core/res/res/drawable-mdpi/ic_menu_mylocation.png
old mode 100644
new mode 100755
index 9ee5024..fdbd5ca
--- a/core/res/res/drawable-mdpi/ic_menu_mylocation.png
+++ b/core/res/res/drawable-mdpi/ic_menu_mylocation.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_myplaces.png b/core/res/res/drawable-mdpi/ic_menu_myplaces.png
index 040f9b1..06f11ba 100644
--- a/core/res/res/drawable-mdpi/ic_menu_myplaces.png
+++ b/core/res/res/drawable-mdpi/ic_menu_myplaces.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_notifications.png b/core/res/res/drawable-mdpi/ic_menu_notifications.png
index f361e82..866d4e0 100644
--- a/core/res/res/drawable-mdpi/ic_menu_notifications.png
+++ b/core/res/res/drawable-mdpi/ic_menu_notifications.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_paste.png b/core/res/res/drawable-mdpi/ic_menu_paste.png
new file mode 100755
index 0000000..cdf7ca3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_paste.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_play_clip.png b/core/res/res/drawable-mdpi/ic_menu_play_clip.png
index 8a01f70..4669947 100644
--- a/core/res/res/drawable-mdpi/ic_menu_play_clip.png
+++ b/core/res/res/drawable-mdpi/ic_menu_play_clip.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_preferences.png b/core/res/res/drawable-mdpi/ic_menu_preferences.png
index 2f34c76..60dbff6 100644
--- a/core/res/res/drawable-mdpi/ic_menu_preferences.png
+++ b/core/res/res/drawable-mdpi/ic_menu_preferences.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_recent_history.png b/core/res/res/drawable-mdpi/ic_menu_recent_history.png
index 4722cbb..4ccae5d 100644
--- a/core/res/res/drawable-mdpi/ic_menu_recent_history.png
+++ b/core/res/res/drawable-mdpi/ic_menu_recent_history.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_refresh.png b/core/res/res/drawable-mdpi/ic_menu_refresh.png
index c3551bec..77d70dd 100644
--- a/core/res/res/drawable-mdpi/ic_menu_refresh.png
+++ b/core/res/res/drawable-mdpi/ic_menu_refresh.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_report_image.png b/core/res/res/drawable-mdpi/ic_menu_report_image.png
index 4f128d7..393d727 100644
--- a/core/res/res/drawable-mdpi/ic_menu_report_image.png
+++ b/core/res/res/drawable-mdpi/ic_menu_report_image.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_revert.png b/core/res/res/drawable-mdpi/ic_menu_revert.png
index 2afc8e8..e7e04f5 100644
--- a/core/res/res/drawable-mdpi/ic_menu_revert.png
+++ b/core/res/res/drawable-mdpi/ic_menu_revert.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_rotate.png b/core/res/res/drawable-mdpi/ic_menu_rotate.png
old mode 100644
new mode 100755
index 547d4ca..27368b2
--- a/core/res/res/drawable-mdpi/ic_menu_rotate.png
+++ b/core/res/res/drawable-mdpi/ic_menu_rotate.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_save.png b/core/res/res/drawable-mdpi/ic_menu_save.png
index ffd7fb7..36d50b3 100644
--- a/core/res/res/drawable-mdpi/ic_menu_save.png
+++ b/core/res/res/drawable-mdpi/ic_menu_save.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_search.png b/core/res/res/drawable-mdpi/ic_menu_search.png
old mode 100644
new mode 100755
index ef949d5..94446db
--- a/core/res/res/drawable-mdpi/ic_menu_search.png
+++ b/core/res/res/drawable-mdpi/ic_menu_search.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_select_all.png b/core/res/res/drawable-mdpi/ic_menu_select_all.png
new file mode 100644
index 0000000..37fd3cbd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_select_all.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_send.png b/core/res/res/drawable-mdpi/ic_menu_send.png
old mode 100644
new mode 100755
index 672ab71..74c096d
--- a/core/res/res/drawable-mdpi/ic_menu_send.png
+++ b/core/res/res/drawable-mdpi/ic_menu_send.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_set_as.png b/core/res/res/drawable-mdpi/ic_menu_set_as.png
old mode 100644
new mode 100755
index e2c92ef..cb9dc49
--- a/core/res/res/drawable-mdpi/ic_menu_set_as.png
+++ b/core/res/res/drawable-mdpi/ic_menu_set_as.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_share.png b/core/res/res/drawable-mdpi/ic_menu_share.png
old mode 100644
new mode 100755
index b7c1c25..44db9b1
--- a/core/res/res/drawable-mdpi/ic_menu_share.png
+++ b/core/res/res/drawable-mdpi/ic_menu_share.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_slideshow.png b/core/res/res/drawable-mdpi/ic_menu_slideshow.png
index 08a40f2..04fda7f 100644
--- a/core/res/res/drawable-mdpi/ic_menu_slideshow.png
+++ b/core/res/res/drawable-mdpi/ic_menu_slideshow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_sort_alphabetically.png b/core/res/res/drawable-mdpi/ic_menu_sort_alphabetically.png
old mode 100644
new mode 100755
index 06d5c74..2583eb8
--- a/core/res/res/drawable-mdpi/ic_menu_sort_alphabetically.png
+++ b/core/res/res/drawable-mdpi/ic_menu_sort_alphabetically.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_sort_by_size.png b/core/res/res/drawable-mdpi/ic_menu_sort_by_size.png
old mode 100644
new mode 100755
index 3c39953..65e2786
--- a/core/res/res/drawable-mdpi/ic_menu_sort_by_size.png
+++ b/core/res/res/drawable-mdpi/ic_menu_sort_by_size.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_star.png b/core/res/res/drawable-mdpi/ic_menu_star.png
old mode 100644
new mode 100755
index cf50cf9..527d74a
--- a/core/res/res/drawable-mdpi/ic_menu_star.png
+++ b/core/res/res/drawable-mdpi/ic_menu_star.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_start_conversation.png b/core/res/res/drawable-mdpi/ic_menu_start_conversation.png
index 10ef649..aadcc2f 100644
--- a/core/res/res/drawable-mdpi/ic_menu_start_conversation.png
+++ b/core/res/res/drawable-mdpi/ic_menu_start_conversation.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_stop.png b/core/res/res/drawable-mdpi/ic_menu_stop.png
index 51efa91..4fc825c 100644
--- a/core/res/res/drawable-mdpi/ic_menu_stop.png
+++ b/core/res/res/drawable-mdpi/ic_menu_stop.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_today.png b/core/res/res/drawable-mdpi/ic_menu_today.png
old mode 100644
new mode 100755
index 2b15f29..c63b6af
--- a/core/res/res/drawable-mdpi/ic_menu_today.png
+++ b/core/res/res/drawable-mdpi/ic_menu_today.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_upload.png b/core/res/res/drawable-mdpi/ic_menu_upload.png
old mode 100644
new mode 100755
index 04f3a2c8..1c0dd3f
--- a/core/res/res/drawable-mdpi/ic_menu_upload.png
+++ b/core/res/res/drawable-mdpi/ic_menu_upload.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_upload_you_tube.png b/core/res/res/drawable-mdpi/ic_menu_upload_you_tube.png
old mode 100644
new mode 100755
index add453b..0095564
--- a/core/res/res/drawable-mdpi/ic_menu_upload_you_tube.png
+++ b/core/res/res/drawable-mdpi/ic_menu_upload_you_tube.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_view.png b/core/res/res/drawable-mdpi/ic_menu_view.png
old mode 100644
new mode 100755
index 6aeac7f..69828a9
--- a/core/res/res/drawable-mdpi/ic_menu_view.png
+++ b/core/res/res/drawable-mdpi/ic_menu_view.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_week.png b/core/res/res/drawable-mdpi/ic_menu_week.png
old mode 100644
new mode 100755
index cba437c..62cd65e
--- a/core/res/res/drawable-mdpi/ic_menu_week.png
+++ b/core/res/res/drawable-mdpi/ic_menu_week.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_zoom.png b/core/res/res/drawable-mdpi/ic_menu_zoom.png
index 9695c69..0b8c4e8 100644
--- a/core/res/res/drawable-mdpi/ic_menu_zoom.png
+++ b/core/res/res/drawable-mdpi/ic_menu_zoom.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_search_category_default.png b/core/res/res/drawable-mdpi/ic_search_category_default.png
old mode 100644
new mode 100755
index 9b55383..94446db
--- a/core/res/res/drawable-mdpi/ic_search_category_default.png
+++ b/core/res/res/drawable-mdpi/ic_search_category_default.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png
index 8546c5f..4e88b37 100755
--- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png
+++ b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_gray.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_gray.9.png
index 4440abc..adbb146 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_gray.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_gray.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_green.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_green.9.png
index 26c07e6..e8be7bf 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_green.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_red.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_red.9.png
index 684f8ac..120a9d8 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_red.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_red.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_yellow.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_yellow.9.png
index 56cfce33a..60ec146 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_yellow.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_confirm_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_normal.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_normal.9.png
index d4e1929..7477453 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_normal.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_pressed.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_pressed.9.png
index 39cd1e5..c79a35c 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_left_end_pressed.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_left_end_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_gray.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_gray.9.png
index a6ee329..4ce09fa 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_gray.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_gray.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_green.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_green.9.png
index 386ed9d..9d7565f 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_green.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_red.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_red.9.png
index 0242a42..d5f9bd8 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_red.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_red.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_yellow.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_yellow.9.png
index b8c2e18..5b9c5b4 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_yellow.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_confirm_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_normal.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_normal.9.png
index 4c608ee..2e6ca2e 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_normal.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_pressed.9.png b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_pressed.9.png
index 8bdfd84..f41750d 100644
--- a/core/res/res/drawable-mdpi/jog_tab_bar_right_end_pressed.9.png
+++ b/core/res/res/drawable-mdpi/jog_tab_bar_right_end_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_left_confirm_gray.png b/core/res/res/drawable-mdpi/jog_tab_left_confirm_gray.png
old mode 100755
new mode 100644
index 3dce451..e8544ff
--- a/core/res/res/drawable-mdpi/jog_tab_left_confirm_gray.png
+++ b/core/res/res/drawable-mdpi/jog_tab_left_confirm_gray.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_left_confirm_green.png b/core/res/res/drawable-mdpi/jog_tab_left_confirm_green.png
old mode 100755
new mode 100644
index 829b146..d0ba8f8
--- a/core/res/res/drawable-mdpi/jog_tab_left_confirm_green.png
+++ b/core/res/res/drawable-mdpi/jog_tab_left_confirm_green.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_left_confirm_red.png b/core/res/res/drawable-mdpi/jog_tab_left_confirm_red.png
index f2ceb2e..5188c86 100644
--- a/core/res/res/drawable-mdpi/jog_tab_left_confirm_red.png
+++ b/core/res/res/drawable-mdpi/jog_tab_left_confirm_red.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_left_confirm_yellow.png b/core/res/res/drawable-mdpi/jog_tab_left_confirm_yellow.png
old mode 100755
new mode 100644
index 5a29262..861e17a
--- a/core/res/res/drawable-mdpi/jog_tab_left_confirm_yellow.png
+++ b/core/res/res/drawable-mdpi/jog_tab_left_confirm_yellow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_left_normal.png b/core/res/res/drawable-mdpi/jog_tab_left_normal.png
old mode 100755
new mode 100644
index eb91e97..7af1b85
--- a/core/res/res/drawable-mdpi/jog_tab_left_normal.png
+++ b/core/res/res/drawable-mdpi/jog_tab_left_normal.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_left_pressed.png b/core/res/res/drawable-mdpi/jog_tab_left_pressed.png
old mode 100755
new mode 100644
index 9951992..b76e83e
--- a/core/res/res/drawable-mdpi/jog_tab_left_pressed.png
+++ b/core/res/res/drawable-mdpi/jog_tab_left_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_right_confirm_gray.png b/core/res/res/drawable-mdpi/jog_tab_right_confirm_gray.png
old mode 100755
new mode 100644
index d446480..814a50d
--- a/core/res/res/drawable-mdpi/jog_tab_right_confirm_gray.png
+++ b/core/res/res/drawable-mdpi/jog_tab_right_confirm_gray.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_right_confirm_green.png b/core/res/res/drawable-mdpi/jog_tab_right_confirm_green.png
old mode 100755
new mode 100644
index 96d7acb..cf157fc
--- a/core/res/res/drawable-mdpi/jog_tab_right_confirm_green.png
+++ b/core/res/res/drawable-mdpi/jog_tab_right_confirm_green.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_right_confirm_red.png b/core/res/res/drawable-mdpi/jog_tab_right_confirm_red.png
index 2e1e105..74f2935 100644
--- a/core/res/res/drawable-mdpi/jog_tab_right_confirm_red.png
+++ b/core/res/res/drawable-mdpi/jog_tab_right_confirm_red.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_right_confirm_yellow.png b/core/res/res/drawable-mdpi/jog_tab_right_confirm_yellow.png
old mode 100755
new mode 100644
index 8224c38..6655731
--- a/core/res/res/drawable-mdpi/jog_tab_right_confirm_yellow.png
+++ b/core/res/res/drawable-mdpi/jog_tab_right_confirm_yellow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_right_normal.png b/core/res/res/drawable-mdpi/jog_tab_right_normal.png
old mode 100755
new mode 100644
index f2113f2..479c9a5
--- a/core/res/res/drawable-mdpi/jog_tab_right_normal.png
+++ b/core/res/res/drawable-mdpi/jog_tab_right_normal.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/jog_tab_right_pressed.png b/core/res/res/drawable-mdpi/jog_tab_right_pressed.png
old mode 100755
new mode 100644
index 65cd51e..454aaf2
--- a/core/res/res/drawable-mdpi/jog_tab_right_pressed.png
+++ b/core/res/res/drawable-mdpi/jog_tab_right_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_default.9.png b/core/res/res/drawable-mdpi/list_selector_background_default.9.png
new file mode 100644
index 0000000..cac71b0
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_default_light.9.png b/core/res/res/drawable-mdpi/list_selector_background_default_light.9.png
new file mode 100644
index 0000000..bdedffd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_default_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_disabled.9.png b/core/res/res/drawable-mdpi/list_selector_background_disabled.9.png
index bf970b0..60f19fe 100644
--- a/core/res/res/drawable-mdpi/list_selector_background_disabled.9.png
+++ b/core/res/res/drawable-mdpi/list_selector_background_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_disabled_light.9.png b/core/res/res/drawable-mdpi/list_selector_background_disabled_light.9.png
new file mode 100644
index 0000000..bc992612
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_disabled_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_focus.9.png b/core/res/res/drawable-mdpi/list_selector_background_focus.9.png
deleted file mode 100644
index c3e24158..0000000
--- a/core/res/res/drawable-mdpi/list_selector_background_focus.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_focused.9.png b/core/res/res/drawable-mdpi/list_selector_background_focused.9.png
new file mode 100644
index 0000000..5b1f195
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_focused_light.9.png b/core/res/res/drawable-mdpi/list_selector_background_focused_light.9.png
new file mode 100644
index 0000000..5b1f195
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_focused_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_focused_selected.9.png b/core/res/res/drawable-mdpi/list_selector_background_focused_selected.9.png
new file mode 100644
index 0000000..e5b0c1d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_focused_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_longpress.9.png b/core/res/res/drawable-mdpi/list_selector_background_longpress.9.png
index 5cbb251..7817667 100644
--- a/core/res/res/drawable-mdpi/list_selector_background_longpress.9.png
+++ b/core/res/res/drawable-mdpi/list_selector_background_longpress.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_longpress_light.9.png b/core/res/res/drawable-mdpi/list_selector_background_longpress_light.9.png
new file mode 100644
index 0000000..646fc69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_longpress_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_pressed.9.png b/core/res/res/drawable-mdpi/list_selector_background_pressed.9.png
index 02b4e9a..1531d9d 100644
--- a/core/res/res/drawable-mdpi/list_selector_background_pressed.9.png
+++ b/core/res/res/drawable-mdpi/list_selector_background_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_pressed_light.9.png b/core/res/res/drawable-mdpi/list_selector_background_pressed_light.9.png
new file mode 100644
index 0000000..fdf6f49
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_pressed_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_selected.9.png b/core/res/res/drawable-mdpi/list_selector_background_selected.9.png
new file mode 100644
index 0000000..a4ac1e3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selector_background_selected_light.9.png b/core/res/res/drawable-mdpi/list_selector_background_selected_light.9.png
new file mode 100644
index 0000000..d086194
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selector_background_selected_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/menu_background.9.png b/core/res/res/drawable-mdpi/menu_background.9.png
index ee99583..9f16df9 100644
--- a/core/res/res/drawable-mdpi/menu_background.9.png
+++ b/core/res/res/drawable-mdpi/menu_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png b/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png
index d368983..da3011b 100644
--- a/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png
+++ b/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_focus.9.png b/core/res/res/drawable-mdpi/minitab_lt_focus.9.png
new file mode 100644
index 0000000..415c571
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_press.9.png b/core/res/res/drawable-mdpi/minitab_lt_press.9.png
new file mode 100644
index 0000000..4166543
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_press.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_selected.9.png b/core/res/res/drawable-mdpi/minitab_lt_selected.9.png
new file mode 100644
index 0000000..fefa27e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_unselected.9.png b/core/res/res/drawable-mdpi/minitab_lt_unselected.9.png
new file mode 100644
index 0000000..0051cd5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_unselected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_unselected_press.9.png b/core/res/res/drawable-mdpi/minitab_lt_unselected_press.9.png
new file mode 100644
index 0000000..69444dd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_unselected_press.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/nav_divider.png b/core/res/res/drawable-mdpi/nav_divider.png
new file mode 100644
index 0000000..c9413d7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/nav_divider.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/status_bar_item_app_background_normal.9.png b/core/res/res/drawable-mdpi/status_bar_item_app_background_normal.9.png
index bdcb378..c079615 100644
--- a/core/res/res/drawable-mdpi/status_bar_item_app_background_normal.9.png
+++ b/core/res/res/drawable-mdpi/status_bar_item_app_background_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/statusbar_background.9.png b/core/res/res/drawable-mdpi/statusbar_background.9.png
new file mode 100644
index 0000000..eb7c1a4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/statusbar_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/tab_arrow_left_holo_dark.png b/core/res/res/drawable-mdpi/tab_arrow_left_holo_dark.png
new file mode 100644
index 0000000..4f8bafe
--- /dev/null
+++ b/core/res/res/drawable-mdpi/tab_arrow_left_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/tab_arrow_left_holo_light.png b/core/res/res/drawable-mdpi/tab_arrow_left_holo_light.png
new file mode 100644
index 0000000..8e225fc
--- /dev/null
+++ b/core/res/res/drawable-mdpi/tab_arrow_left_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/tab_arrow_right_holo_dark.png b/core/res/res/drawable-mdpi/tab_arrow_right_holo_dark.png
new file mode 100644
index 0000000..0a8006d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/tab_arrow_right_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/tab_arrow_right_holo_light.png b/core/res/res/drawable-mdpi/tab_arrow_right_holo_light.png
new file mode 100644
index 0000000..85aae47
--- /dev/null
+++ b/core/res/res/drawable-mdpi/tab_arrow_right_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/tab_divider_holo_dark.png b/core/res/res/drawable-mdpi/tab_divider_holo_dark.png
new file mode 100644
index 0000000..89d7b8b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/tab_divider_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/tab_divider_holo_light.png b/core/res/res/drawable-mdpi/tab_divider_holo_light.png
new file mode 100644
index 0000000..878b2b4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/tab_divider_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/tab_selector_holo_dark.9.png b/core/res/res/drawable-mdpi/tab_selector_holo_dark.9.png
new file mode 100644
index 0000000..30d30df
--- /dev/null
+++ b/core/res/res/drawable-mdpi/tab_selector_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_default_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_default_holo_dark.9.png
new file mode 100644
index 0000000..3a5f36d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_default_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_default_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_default_holo_light.9.png
new file mode 100644
index 0000000..b8cc76f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_default_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_disabled_holo_dark.9.png
new file mode 100644
index 0000000..a1f0c71
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_disabled_holo_light.9.png
new file mode 100644
index 0000000..71e3103
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_dark.9.png
new file mode 100644
index 0000000..ac6d406
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_light.9.png
new file mode 100644
index 0000000..bb6e953
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_pressed_holo_dark.9.png
new file mode 100644
index 0000000..7667d95
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_pressed_holo_light.9.png
new file mode 100644
index 0000000..269affd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_search_default_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_search_default_holo_dark.9.png
new file mode 100644
index 0000000..3549948
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_search_default_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_search_default_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_search_default_holo_light.9.png
new file mode 100644
index 0000000..6db03cb
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_search_default_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_search_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_search_selected_holo_dark.9.png
new file mode 100644
index 0000000..670439a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_search_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_search_selected_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_search_selected_holo_light.9.png
new file mode 100644
index 0000000..90e26e2
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_search_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_selected_holo_dark.9.png
new file mode 100644
index 0000000..7667d95
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_selected_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_selected_holo_light.9.png
new file mode 100644
index 0000000..269affd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-nodpi/platlogo.jpg b/core/res/res/drawable-nodpi/platlogo.jpg
deleted file mode 100644
index 0e7780c..0000000
--- a/core/res/res/drawable-nodpi/platlogo.jpg
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-nodpi/platlogo.png b/core/res/res/drawable-nodpi/platlogo.png
new file mode 100644
index 0000000..e5af356
--- /dev/null
+++ b/core/res/res/drawable-nodpi/platlogo.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..5acad94
--- /dev/null
+++ b/core/res/res/drawable-xlarge/default_wallpaper.jpg
Binary files differ
diff --git a/core/res/res/drawable-xlarge/ic_lock_idle_alarm.png b/core/res/res/drawable-xlarge/ic_lock_idle_alarm.png
new file mode 100644
index 0000000..336a820
--- /dev/null
+++ b/core/res/res/drawable-xlarge/ic_lock_idle_alarm.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/ic_lock_idle_charging.png b/core/res/res/drawable-xlarge/ic_lock_idle_charging.png
new file mode 100644
index 0000000..ebef531
--- /dev/null
+++ b/core/res/res/drawable-xlarge/ic_lock_idle_charging.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/ic_lock_idle_lock.png b/core/res/res/drawable-xlarge/ic_lock_idle_lock.png
new file mode 100644
index 0000000..405e218
--- /dev/null
+++ b/core/res/res/drawable-xlarge/ic_lock_idle_lock.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/ic_lock_idle_low_battery.png b/core/res/res/drawable-xlarge/ic_lock_idle_low_battery.png
new file mode 100644
index 0000000..f349b63
--- /dev/null
+++ b/core/res/res/drawable-xlarge/ic_lock_idle_low_battery.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/unlock_default.png b/core/res/res/drawable-xlarge/unlock_default.png
new file mode 100644
index 0000000..0a441c0
--- /dev/null
+++ b/core/res/res/drawable-xlarge/unlock_default.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/unlock_halo.png b/core/res/res/drawable-xlarge/unlock_halo.png
new file mode 100644
index 0000000..09b0526
--- /dev/null
+++ b/core/res/res/drawable-xlarge/unlock_halo.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/unlock_ring.png b/core/res/res/drawable-xlarge/unlock_ring.png
new file mode 100644
index 0000000..1ac6d54
--- /dev/null
+++ b/core/res/res/drawable-xlarge/unlock_ring.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/unlock_wave.png b/core/res/res/drawable-xlarge/unlock_wave.png
new file mode 100644
index 0000000..21bfa24
--- /dev/null
+++ b/core/res/res/drawable-xlarge/unlock_wave.png
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/activated_background.xml b/core/res/res/drawable/activated_background.xml
new file mode 100644
index 0000000..d92fba1
--- /dev/null
+++ b/core/res/res/drawable/activated_background.xml
@@ -0,0 +1,20 @@
+<?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_activated="true" android:drawable="@android:drawable/list_selector_background_selected" />
+    <item android:drawable="@color/transparent" />
+</selector>
diff --git a/core/res/res/drawable/activated_background_light.xml b/core/res/res/drawable/activated_background_light.xml
new file mode 100644
index 0000000..5d5681d
--- /dev/null
+++ b/core/res/res/drawable/activated_background_light.xml
@@ -0,0 +1,20 @@
+<?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_activated="true" android:drawable="@drawable/list_selector_background_selected_light" />
+    <item android:drawable="@color/transparent" />
+</selector>
diff --git a/core/res/res/drawable/btn_check_holo_dark.xml b/core/res/res/drawable/btn_check_holo_dark.xml
new file mode 100644
index 0000000..fd85d72
--- /dev/null
+++ b/core/res/res/drawable/btn_check_holo_dark.xml
@@ -0,0 +1,65 @@
+<?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">
+
+    <!-- Enabled states -->
+        
+    <item android:state_checked="true" android:state_window_focused="false"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_holo_dark" />
+    <item android:state_checked="false" android:state_window_focused="false"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_holo_dark" />
+
+    <item android:state_checked="true" android:state_pressed="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_pressed_holo_dark" />
+    <item android:state_checked="false" android:state_pressed="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_pressed_holo_dark" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_selected_holo_dark" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_selected_holo_dark" />
+
+    <item android:state_checked="false"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_holo_dark" />
+    <item android:state_checked="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_holo_dark" />
+
+
+    <!-- Disabled states -->
+
+    <item android:state_checked="true" android:state_window_focused="false"
+          android:drawable="@drawable/btn_check_on_disable_holo_dark" />
+    <item android:state_checked="false" android:state_window_focused="false"
+          android:drawable="@drawable/btn_check_off_disable_holo_dark" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/btn_check_on_disable_focused_holo_dark" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:drawable="@drawable/btn_check_off_disable_focused_holo_dark" />
+
+    <item android:state_checked="false" android:drawable="@drawable/btn_check_off_disable_holo_dark" />
+    <item android:state_checked="true" android:drawable="@drawable/btn_check_on_disable_holo_dark" />
+
+</selector>
diff --git a/core/res/res/drawable/btn_check_holo_light.xml b/core/res/res/drawable/btn_check_holo_light.xml
new file mode 100644
index 0000000..4fb16fa
--- /dev/null
+++ b/core/res/res/drawable/btn_check_holo_light.xml
@@ -0,0 +1,65 @@
+<?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">
+
+    <!-- Enabled states -->
+        
+    <item android:state_checked="true" android:state_window_focused="false"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_holo_light" />
+    <item android:state_checked="false" android:state_window_focused="false"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_holo_light" />
+
+    <item android:state_checked="true" android:state_pressed="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_pressed_holo_light" />
+    <item android:state_checked="false" android:state_pressed="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_pressed_holo_light" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_selected_holo_light" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_selected_holo_light" />
+
+    <item android:state_checked="false"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_off_holo_light" />
+    <item android:state_checked="true"
+          android:state_enabled="true"
+          android:drawable="@drawable/btn_check_on_holo_light" />
+
+
+    <!-- Disabled states -->
+
+    <item android:state_checked="true" android:state_window_focused="false"
+          android:drawable="@drawable/btn_check_on_disable_holo_light" />
+    <item android:state_checked="false" android:state_window_focused="false"
+          android:drawable="@drawable/btn_check_off_disable_holo_light" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/btn_check_on_disable_focused_holo_light" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:drawable="@drawable/btn_check_off_disable_focused_holo_light" />
+
+    <item android:state_checked="false" android:drawable="@drawable/btn_check_off_disable_holo_light" />
+    <item android:state_checked="true" android:drawable="@drawable/btn_check_on_disable_holo_light" />
+
+</selector>
diff --git a/core/res/res/drawable/btn_default_holo_dark.xml b/core/res/res/drawable/btn_default_holo_dark.xml
new file mode 100644
index 0000000..c250070
--- /dev/null
+++ b/core/res/res/drawable/btn_default_holo_dark.xml
@@ -0,0 +1,32 @@
+<?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_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_default_normal_holo_dark" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_default_disabled_holo_dark" />
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/btn_default_pressed_holo_dark" />
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_default_focused_holo_dark" />
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_default_normal_holo_dark" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_default_disabled_focused_holo_dark" />
+    <item
+         android:drawable="@drawable/btn_default_disabled_holo_dark" />
+</selector>
diff --git a/core/res/res/drawable/btn_radio_holo_dark.xml b/core/res/res/drawable/btn_radio_holo_dark.xml
new file mode 100644
index 0000000..8984f6d
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_holo_dark.xml
@@ -0,0 +1,35 @@
+<?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_checked="true" android:state_window_focused="false"
+          android:drawable="@drawable/btn_radio_on_holo_dark" />
+    <item android:state_checked="false" android:state_window_focused="false"
+          android:drawable="@drawable/btn_radio_off_holo_dark" />
+          
+    <item android:state_checked="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_radio_on_pressed_holo_dark" />
+    <item android:state_checked="false" android:state_pressed="true"
+          android:drawable="@drawable/btn_radio_off_pressed_holo_dark" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/btn_radio_on_selected_holo_dark" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:drawable="@drawable/btn_radio_off_selected_holo_dark" />
+
+    <item android:state_checked="false" android:drawable="@drawable/btn_radio_off_holo_dark" />
+    <item android:state_checked="true" android:drawable="@drawable/btn_radio_on_holo_dark" />
+</selector>
diff --git a/core/res/res/drawable/btn_radio_holo_light.xml b/core/res/res/drawable/btn_radio_holo_light.xml
new file mode 100644
index 0000000..001508c
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_holo_light.xml
@@ -0,0 +1,35 @@
+<?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_checked="true" android:state_window_focused="false"
+          android:drawable="@drawable/btn_radio_on_holo_light" />
+    <item android:state_checked="false" android:state_window_focused="false"
+          android:drawable="@drawable/btn_radio_off_holo_light" />
+          
+    <item android:state_checked="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_radio_on_pressed_holo_light" />
+    <item android:state_checked="false" android:state_pressed="true"
+          android:drawable="@drawable/btn_radio_off_pressed_holo_light" />
+
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/btn_radio_on_selected_holo_light" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:drawable="@drawable/btn_radio_off_selected_holo_light" />
+
+    <item android:state_checked="false" android:drawable="@drawable/btn_radio_off_holo_light" />
+    <item android:state_checked="true" android:drawable="@drawable/btn_radio_on_holo_light" />
+</selector>
diff --git a/core/res/res/drawable/btn_toggle_holo_dark.xml b/core/res/res/drawable/btn_toggle_holo_dark.xml
new file mode 100644
index 0000000..658ca9e
--- /dev/null
+++ b/core/res/res/drawable/btn_toggle_holo_dark.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true"
+        android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_on_normal_holo_dark" />
+    <item android:state_checked="true"
+        android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_toggle_on_disabled_holo_dark" />
+    <item android:state_checked="true" android:state_pressed="true"
+        android:drawable="@drawable/btn_toggle_on_pressed_holo_dark" />
+    <item android:state_checked="true"
+        android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_on_focused_holo_dark" />
+    <item android:state_checked="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_on_normal_holo_dark" />
+    <item android:state_checked="true"  android:state_focused="true"
+        android:drawable="@drawable/btn_toggle_on_disabled_focused_holo_dark" />
+    <item android:state_checked="true"
+         android:drawable="@drawable/btn_toggle_on_disabled_holo_dark" />
+
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_off_normal_holo_dark" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_toggle_off_disabled_holo_dark" />
+    <item android:state_pressed="true"
+        android:drawable="@drawable/btn_toggle_off_pressed_holo_dark" />
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_off_focused_holo_dark" />
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_off_normal_holo_dark" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_toggle_off_disabled_focused_holo_dark" />
+    <item
+         android:drawable="@drawable/btn_toggle_off_disabled_holo_dark" />
+</selector>
diff --git a/core/res/res/drawable/btn_toggle_holo_light.xml b/core/res/res/drawable/btn_toggle_holo_light.xml
new file mode 100644
index 0000000..d3f1012
--- /dev/null
+++ b/core/res/res/drawable/btn_toggle_holo_light.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true"
+        android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_on_normal_holo_light" />
+    <item android:state_checked="true"
+        android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_toggle_on_disabled_holo_light" />
+    <item android:state_checked="true" android:state_pressed="true"
+        android:drawable="@drawable/btn_toggle_on_pressed_holo_light" />
+    <item android:state_checked="true"
+        android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_on_focused_holo_light" />
+    <item android:state_checked="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_on_normal_holo_light" />
+    <item android:state_checked="true"  android:state_focused="true"
+        android:drawable="@drawable/btn_toggle_on_disabled_focused_holo_light" />
+    <item android:state_checked="true"
+         android:drawable="@drawable/btn_toggle_on_disabled_holo_light" />
+
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_off_normal_holo_light" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_toggle_off_disabled_holo_light" />
+    <item android:state_pressed="true"
+        android:drawable="@drawable/btn_toggle_off_pressed_holo_light" />
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_off_focused_holo_light" />
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_toggle_off_normal_holo_light" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_toggle_off_disabled_focused_holo_light" />
+    <item
+         android:drawable="@drawable/btn_toggle_off_disabled_holo_light" />
+</selector>
diff --git a/core/res/res/drawable/cab_ic_close_holo.xml b/core/res/res/drawable/cab_ic_close_holo.xml
new file mode 100644
index 0000000..1baf7cc
--- /dev/null
+++ b/core/res/res/drawable/cab_ic_close_holo.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+          android:drawable="@drawable/cab_ic_close_pressed_holo" />
+    <item android:state_focused="true"
+          android:drawable="@drawable/cab_ic_close_focused_holo" />
+    <item android:drawable="@drawable/cab_ic_close_normal_holo" />
+</selector>
diff --git a/core/res/res/drawable/edit_query_background.xml b/core/res/res/drawable/edit_query_background.xml
new file mode 100644
index 0000000..1446f8c
--- /dev/null
+++ b/core/res/res/drawable/edit_query_background.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_window_focused="false"
+        android:drawable="@drawable/edit_query_background_normal" />
+
+    <item android:state_focused="true" android:state_pressed="true"
+        android:drawable="@drawable/edit_query_background_pressed" />
+
+    <item android:state_focused="false" android:state_pressed="true"
+          android:drawable="@drawable/edit_query_background_pressed" />
+
+    <item android:state_focused="true"
+        android:drawable="@drawable/edit_query_background_selected" />
+
+    <item android:drawable="@drawable/edit_query_background_normal" />
+
+</selector>
diff --git a/core/res/res/drawable/edit_text_holo_dark.xml b/core/res/res/drawable/edit_text_holo_dark.xml
new file mode 100644
index 0000000..b7d24ff
--- /dev/null
+++ b/core/res/res/drawable/edit_text_holo_dark.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/textfield_default_holo_dark" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/textfield_disabled_holo_dark" />
+    <item android:state_pressed="true" android:drawable="@drawable/textfield_pressed_holo_dark" />
+    <item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/textfield_selected_holo_dark" />
+    <item android:state_enabled="true" android:drawable="@drawable/textfield_default_holo_dark" />
+    <item android:state_focused="true" android:drawable="@drawable/textfield_disabled_selected_holo_dark" />
+    <item android:drawable="@drawable/textfield_disabled_holo_dark" />
+</selector>
+
diff --git a/core/res/res/drawable/edit_text_holo_light.xml b/core/res/res/drawable/edit_text_holo_light.xml
new file mode 100644
index 0000000..dae39e3
--- /dev/null
+++ b/core/res/res/drawable/edit_text_holo_light.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/textfield_default_holo_light" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/textfield_disabled_holo_light" />
+    <item android:state_pressed="true" android:drawable="@drawable/textfield_pressed_holo_light" />
+    <item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/textfield_selected_holo_light" />
+    <item android:state_enabled="true" android:drawable="@drawable/textfield_default_holo_light" />
+    <item android:state_focused="true" android:drawable="@drawable/textfield_disabled_selected_holo_light" />
+    <item android:drawable="@drawable/textfield_disabled_holo_light" />
+</selector>
+
diff --git a/core/res/res/drawable/list_selected_background.xml b/core/res/res/drawable/list_selected_background.xml
new file mode 100644
index 0000000..2bb6594
--- /dev/null
+++ b/core/res/res/drawable/list_selected_background.xml
@@ -0,0 +1,22 @@
+<?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_focused="true"   android:state_checked="true" android:drawable="@drawable/list_selector_background_focused_selected" />
+    <item android:state_focused="false"  android:state_checked="true" android:drawable="@drawable/list_selector_background_selected" />
+
+</selector>
diff --git a/core/res/res/drawable/list_selected_background_light.xml b/core/res/res/drawable/list_selected_background_light.xml
new file mode 100644
index 0000000..15fc1cb
--- /dev/null
+++ b/core/res/res/drawable/list_selected_background_light.xml
@@ -0,0 +1,22 @@
+<?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_focused="true"   android:state_checked="true" android:drawable="@drawable/list_selector_background_focused_selected" />
+    <item android:state_focused="false"  android:state_checked="true" android:drawable="@drawable/list_selector_background_selected_light" />
+
+</selector>
diff --git a/core/res/res/drawable/list_selector_background.xml b/core/res/res/drawable/list_selector_background.xml
index bca996c..0a51152 100644
--- a/core/res/res/drawable/list_selector_background.xml
+++ b/core/res/res/drawable/list_selector_background.xml
@@ -16,22 +16,14 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <item android:state_window_focused="false"
-        android:drawable="@color/transparent" />
+    <item android:state_window_focused="false" android:drawable="@color/transparent" />
 
     <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
-    <item android:state_focused="true" android:state_enabled="false"
-        android:state_pressed="true"
-        android:drawable="@drawable/list_selector_background_disabled" />
-    <item android:state_focused="true" android:state_enabled="false"
-        android:drawable="@drawable/list_selector_background_disabled" />
-
-    <item android:state_focused="true" android:state_pressed="true"
-        android:drawable="@drawable/list_selector_background_transition" />
-    <item android:state_focused="false" android:state_pressed="true"
-        android:drawable="@drawable/list_selector_background_transition" />
-
-    <item android:state_focused="true"
-        android:drawable="@drawable/list_selector_background_focus" />
-
+    <item android:state_focused="true"  android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/list_selector_background_disabled" />
+    <item android:state_focused="true"  android:state_enabled="false"                              android:drawable="@drawable/list_selector_background_disabled" />
+    <item android:state_focused="true"                                android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" />
+    <item android:state_focused="false"                               android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" />
+    <item android:state_focused="true"                                                             android:drawable="@drawable/list_selector_background_focused" />
+    <item                                                                                          android:drawable="@drawable/list_selector_background_default" />
+    
 </selector>
diff --git a/core/res/res/drawable/list_selector_background_light.xml b/core/res/res/drawable/list_selector_background_light.xml
new file mode 100644
index 0000000..9b7980c
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background_light.xml
@@ -0,0 +1,29 @@
+<?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_window_focused="false" android:drawable="@color/transparent" />
+
+    <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
+    <item android:state_focused="true"  android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/list_selector_background_disabled_light" />
+    <item android:state_focused="true"  android:state_enabled="false"                              android:drawable="@drawable/list_selector_background_disabled_light" />
+    <item android:state_focused="true"                                android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_light" />
+    <item android:state_focused="false"                               android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_light" />
+    <item android:state_focused="true"                                                             android:drawable="@drawable/list_selector_background_focused_light" />
+    <item                                                                                          android:drawable="@drawable/list_selector_background_default_light" />
+
+</selector>
diff --git a/core/res/res/drawable/list_selector_background_transition_light.xml b/core/res/res/drawable/list_selector_background_transition_light.xml
new file mode 100644
index 0000000..634f895
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background_transition_light.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:drawable/list_selector_background_pressed_light"  />
+    <item android:drawable="@android:drawable/list_selector_background_longpress_light"  />
+</transition>
diff --git a/core/res/res/drawable/minitab_lt.xml b/core/res/res/drawable/minitab_lt.xml
new file mode 100644
index 0000000..aeea97c
--- /dev/null
+++ b/core/res/res/drawable/minitab_lt.xml
@@ -0,0 +1,25 @@
+<?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:state_selected="true"
+          android:drawable="@drawable/minitab_lt_press" />
+    <item android:state_selected="true"
+          android:drawable="@drawable/minitab_lt_selected" />
+    <item android:state_pressed="true"
+          android:drawable="@drawable/minitab_lt_unselected_press" />
+    <item android:drawable="@drawable/minitab_lt_unselected" />
+</selector>
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-port/preference_header_item.xml b/core/res/res/layout-port/preference_header_item.xml
new file mode 100644
index 0000000..cc76c8e
--- /dev/null
+++ b/core/res/res/layout-port/preference_header_item.xml
@@ -0,0 +1,62 @@
+<?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 of a header item in PreferenceActivity. -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="96dp"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:paddingRight="?android:attr/scrollbarSize">
+    
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_gravity="center"
+        android:paddingLeft="4dip"
+        android:paddingRight="4dip"
+        android:paddingTop="4dip"
+        android:paddingBottom="4dip">
+    
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginBottom="2dip" />
+    
+        <TextView android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginBottom="2dip"
+            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_gravity="center"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:ellipsize="end"
+            android:maxLines="2" />
+    
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout-xlarge/keyguard.xml b/core/res/res/layout-xlarge/keyguard.xml
new file mode 100644
index 0000000..ca629f8
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/keyguard.xml
+**
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:paddingLeft="20dip"
+    android:paddingTop="20dip"
+    android:paddingRight="20dip"
+    android:paddingBottom="20dip"
+    android:orientation="vertical" 
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="#ff000000">
+
+    <TextView
+        android:id="@+id/label"
+        android:textSize="16sp" 
+        android:textStyle="bold" 
+        android:textColor="#FFFFFFFF" 
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" 
+        android:text="@string/keyguard_label_text" />
+</LinearLayout>
+
diff --git a/core/res/res/layout-xlarge/keyguard_screen_glogin_unlock.xml b/core/res/res/layout-xlarge/keyguard_screen_glogin_unlock.xml
new file mode 100644
index 0000000..8a46546
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_glogin_unlock.xml
@@ -0,0 +1,126 @@
+<?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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/background_dark"
+        >
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0px"
+        android:layout_weight="1"
+        android:layout_above="@+id/emergencyCall">
+        <RelativeLayout 
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+                >
+        
+            <TextView 
+                android:id="@+id/topHeader"
+                android:layout_width="match_parent"
+                android:layout_height="64dip"
+                android:layout_alignParentTop="true"
+                android:layout_marginLeft="4dip"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:gravity="center_vertical"
+                android:drawableLeft="@drawable/ic_lock_idle_lock"
+                android:drawablePadding="5dip"
+                />
+        
+            <!-- spacer below header -->
+            <View
+                android:id="@+id/spacerTop"
+                android:layout_width="match_parent"
+                android:layout_height="1dip"
+                android:layout_below="@id/topHeader"
+                android:background="@drawable/divider_horizontal_dark"/>
+        
+            <TextView
+                android:id="@+id/instructions"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/spacerTop"
+                android:layout_marginTop="8dip"
+                android:layout_marginLeft="9dip"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:text="@android:string/lockscreen_glogin_instructions"
+                />
+        
+            <EditText
+                android:id="@+id/login"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/instructions"
+                android:layout_marginTop="8dip"
+                android:layout_marginLeft="7dip"
+                android:layout_marginRight="7dip"
+                android:hint="@android:string/lockscreen_glogin_username_hint"
+                android:inputType="textEmailAddress"
+                />
+        
+            <EditText
+                android:id="@+id/password"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/login"
+                android:layout_marginTop="15dip"
+                android:layout_marginLeft="7dip"
+                android:layout_marginRight="7dip"
+                android:inputType="textPassword"
+                android:hint="@android:string/lockscreen_glogin_password_hint"
+                android:nextFocusRight="@+id/ok"
+                android:nextFocusDown="@+id/ok"
+                />
+        
+            <!-- ok below password, aligned to right of screen -->
+            <Button
+                android:id="@+id/ok"
+                android:layout_width="85dip"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/password"
+                android:layout_marginTop="7dip"
+                android:layout_marginRight="7dip"
+                android:layout_alignParentRight="true"
+                android:text="@android:string/lockscreen_glogin_submit_button"
+                />
+        
+        </RelativeLayout>
+    </ScrollView>
+    
+    <!-- spacer above emergency call -->
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:layout_marginBottom="4dip"
+
+        android:background="@drawable/divider_horizontal_dark"/>
+
+    <!-- emergency call button at bottom center -->
+    <Button
+        android:id="@+id/emergencyCall"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:drawableLeft="@drawable/ic_emergency"
+        android:drawablePadding="8dip"
+        android:text="@android:string/lockscreen_emergency_call"
+        />
+
+</LinearLayout>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_lock.xml b/core/res/res/layout-xlarge/keyguard_screen_lock.xml
new file mode 100644
index 0000000..733a350
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_lock.xml
@@ -0,0 +1,220 @@
+<?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.
+*/
+-->
+        
+<!-- This is the general lock screen which shows information about the
+  state of the device, as well as instructions on how to get past it
+  depending on the state of the device.  It is the same for landscape
+  and portrait.-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:gravity="bottom"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ff800000"
+        >
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginBottom="15dip"
+        android:layout_marginLeft="15dip"
+        android:layout_marginRight="15dip"
+        android:paddingTop="20dip"
+        android:paddingBottom="20dip"
+        android:background="@android:drawable/popup_full_dark"
+        >
+
+        <!-- when sim is present -->
+        <TextView android:id="@+id/headerSimOk1"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:textSize="34sp"/>
+        <TextView android:id="@+id/headerSimOk2"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:textSize="34sp"/>
+
+        <!-- when sim is missing / locked -->
+        <TextView android:id="@+id/headerSimBad1"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:text="@android:string/lockscreen_missing_sim_message"
+                  android:textAppearance="?android:attr/textAppearanceLarge"/>
+        <TextView android:id="@+id/headerSimBad2"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:layout_marginTop="7dip"
+                  android:layout_marginBottom="7dip"
+                  android:gravity="center"
+                  android:text="@android:string/lockscreen_missing_sim_instructions"
+                  android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+        <!-- spacer after carrier info / sim messages -->
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dip"
+            android:layout_marginTop="8dip"
+            android:background="@android:drawable/divider_horizontal_dark"/>
+
+        <!-- time and date -->
+        <TextView android:id="@+id/time"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:textSize="34sp"/>
+
+        <TextView android:id="@+id/date"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center"
+                  android:textSize="18sp"/>
+
+        <!-- spacer after time and date -->
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dip"
+            android:layout_marginBottom="8dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+                />
+
+        <!-- battery info -->
+        <LinearLayout android:id="@+id/batteryInfo"
+                android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+              >
+
+            <ImageView android:id="@+id/batteryInfoIcon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="6dip"
+                android:baselineAligned="true"
+                android:gravity="center"
+            />
+
+            <TextView android:id="@+id/batteryInfoText"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textSize="18sp"
+                      android:gravity="center"
+            />
+
+        </LinearLayout>
+
+        <!-- spacer after battery info -->
+        <View android:id="@+id/batteryInfoSpacer"
+            android:layout_width="match_parent"
+            android:layout_height="1dip"
+            android:layout_marginTop="8dip"
+            android:layout_marginBottom="8dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+                />
+
+        <!-- next alarm info -->
+
+        <LinearLayout android:id="@+id/nextAlarmInfo"
+                android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+              >
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="6dip"
+                android:baselineAligned="true"
+                android:src="@android:drawable/ic_lock_idle_alarm"
+                android:gravity="center"
+            />
+
+            <TextView android:id="@+id/nextAlarmText"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textSize="18sp"
+                      android:gravity="center"
+            />
+        </LinearLayout>
+
+        <!-- spacer after alarm info -->
+        <View android:id="@+id/nextAlarmSpacer"
+            android:layout_width="match_parent"
+            android:layout_height="1dip"
+            android:layout_marginTop="8dip"
+            android:layout_marginBottom="8dip"
+            android:background="@android:drawable/divider_horizontal_dark"/>
+
+        <!-- lock icon with 'screen locked' message
+             (shown when SIM card is present) -->
+        <LinearLayout android:id="@+id/screenLockedInfo"
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            >
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="6dip"
+                android:baselineAligned="true"
+                android:src="@android:drawable/ic_lock_idle_lock"
+                android:gravity="center"
+            />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"
+                android:text="@android:string/lockscreen_screen_locked"
+                android:gravity="center"
+                    />
+        </LinearLayout>
+
+        <!-- message about how to unlock
+             (shown when SIM card is present) -->
+        <TextView android:id="@+id/lockInstructions"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:layout_marginBottom="5dip"
+                  android:gravity="center"
+                  android:textSize="14sp"/>
+
+
+        <!-- emergency call button shown when sim is missing or PUKd -->
+        <Button
+            android:id="@+id/emergencyCallButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="5dip"
+            android:layout_marginTop="5dip"
+            android:layout_gravity="center_horizontal"
+            android:drawableLeft="@drawable/ic_emergency"
+            android:drawablePadding="8dip"
+            android:text="@android:string/lockscreen_emergency_call"
+           />
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_password_landscape.xml b/core/res/res/layout-xlarge/keyguard_screen_password_landscape.xml
new file mode 100644
index 0000000..c2d87a2
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_password_landscape.xml
@@ -0,0 +1,90 @@
+<?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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+        >
+    
+    <!-- left side: status and emergency call button -->
+    <LinearLayout
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_width="0dip"
+            android:orientation="vertical"
+            android:gravity="center_vertical"
+                >
+        <include layout="@layout/keyguard_screen_status_land" />
+    </LinearLayout>
+    
+    <!-- right side: password -->
+    <LinearLayout
+        android:layout_width="0dip"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:layout_weight="1"
+        android:gravity="center">
+
+        <!-- Password entry field -->
+        <EditText android:id="@+id/passwordEntry"
+            android:layout_height="wrap_content"
+            android:layout_width="330dip"
+            android:singleLine="true"
+            android:textStyle="normal"
+            android:inputType="textPassword"
+            android:gravity="center"
+            android:layout_gravity="center"
+            android:textSize="24sp"
+            android:layout_marginTop="120dip"
+            android:layout_marginBottom="5dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:background="@drawable/password_field_default"
+            android:textColor="#ffffffff"
+            />
+
+        <!-- Numeric keyboard -->
+        <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+            android:layout_width="330dip"
+            android:layout_height="260dip"
+            android:background="#00000000"
+            android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+        />
+        <!-- Alphanumeric keyboard -->
+        <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboardAlpha"
+            android:layout_width="450dip"
+            android:layout_height="230dip"
+            android:background="#00000000"
+            android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+        />
+
+        <!-- emergency call button -->
+        <Button
+            android:id="@+id/emergencyCall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableLeft="@drawable/ic_emergency"
+            android:drawablePadding="8dip"
+            android:text="@string/lockscreen_emergency_call"
+            android:visibility="gone"
+            style="@style/Widget.Button.Transparent"
+        />
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_password_portrait.xml b/core/res/res/layout-xlarge/keyguard_screen_password_portrait.xml
new file mode 100644
index 0000000..0927e59
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_password_portrait.xml
@@ -0,0 +1,89 @@
+<?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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+        >
+    
+    <!-- left side: status and emergency call button -->
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:gravity="center_vertical"
+                >
+        <include layout="@layout/keyguard_screen_status_land" />
+    </LinearLayout>
+    
+    <!-- right side: password -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:gravity="center">
+
+        <!-- Password entry field -->
+        <EditText android:id="@+id/passwordEntry"
+            android:layout_height="wrap_content"
+            android:layout_width="330dip"
+            android:singleLine="true"
+            android:textStyle="normal"
+            android:inputType="textPassword"
+            android:gravity="center"
+            android:layout_gravity="center"
+            android:textSize="24sp"
+            android:layout_marginTop="120dip"
+            android:layout_marginBottom="5dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:background="@drawable/password_field_default"
+            android:textColor="#ffffffff"
+            />
+
+        <!-- Numeric keyboard -->
+        <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+            android:layout_width="330dip"
+            android:layout_height="260dip"
+            android:background="#00000000"
+            android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+        />
+        <!-- Alphanumeric keyboard -->
+        <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboardAlpha"
+            android:layout_width="450dip"
+            android:layout_height="230dip"
+            android:background="#00000000"
+            android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+        />
+
+        <!-- emergency call button -->
+        <Button
+            android:id="@+id/emergencyCall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableLeft="@drawable/ic_emergency"
+            android:drawablePadding="8dip"
+            android:text="@string/lockscreen_emergency_call"
+            android:visibility="gone"
+            style="@style/Widget.Button.Transparent"
+        />
+
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout-xlarge/keyguard_screen_sim_pin_landscape.xml b/core/res/res/layout-xlarge/keyguard_screen_sim_pin_landscape.xml
new file mode 100644
index 0000000..b8cbe51
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_sim_pin_landscape.xml
@@ -0,0 +1,122 @@
+<?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.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/background_dark"
+        >
+
+  
+    <!-- right side -->
+    <!-- header text ('Enter Pin Code') -->
+    <TextView android:id="@+id/headerText"
+        android:layout_above="@+id/carrier"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="30dip"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"
+            />
+
+    <!-- Carrier info -->
+    <TextView android:id="@+id/carrier"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_above="@+id/pinDisplayGroup"
+        android:layout_marginTop="9dip"
+        android:gravity="left|bottom"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+    />
+
+    <!-- displays dots as user enters pin -->
+    <LinearLayout android:id="@+id/pinDisplayGroup"
+        android:orientation="horizontal"
+        android:layout_centerInParent="true"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:addStatesFromChildren="true"
+        android:gravity="center_vertical"
+        android:baselineAligned="false"
+        android:paddingRight="0dip"
+        android:layout_marginRight="30dip"
+        android:layout_marginLeft="30dip"
+        android:background="@android:drawable/edit_text"
+    >
+
+        <EditText android:id="@+id/pinDisplay"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="match_parent"
+            android:maxLines="1"
+            android:background="@null"
+            android:textSize="32sp"
+            android:inputType="textPassword"
+            />
+
+        <ImageButton android:id="@+id/backspace"
+             android:src="@android:drawable/ic_input_delete"
+             android:layout_width="wrap_content"
+             android:layout_height="match_parent"
+             android:layout_marginTop="2dip"
+             android:layout_marginRight="2dip"
+             android:layout_marginBottom="2dip"
+             android:gravity="center"
+            />
+
+    </LinearLayout>
+        
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_alignParentBottom="true"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="8dip"
+        android:layout_marginLeft="8dip"
+        android:layout_marginRight="8dip">
+
+        <Button android:id="@+id/ok"
+            android:text="@android:string/ok"
+            android:layout_alignParentBottom="true"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1.0"
+            android:layout_marginBottom="8dip"
+            android:layout_marginRight="8dip"
+            android:textSize="18sp"
+            />
+
+        <Button android:id="@+id/emergencyCall"
+            android:text="@android:string/lockscreen_emergency_call"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1.0"
+            android:layout_marginBottom="8dip"
+            android:layout_marginLeft="8dip"
+            android:textSize="18sp"
+            android:drawableLeft="@drawable/ic_emergency"
+            android:drawablePadding="8dip"
+        />
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout-xlarge/keyguard_screen_sim_pin_portrait.xml
new file mode 100644
index 0000000..009148f
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_sim_pin_portrait.xml
@@ -0,0 +1,119 @@
+<?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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/background_dark"
+    android:gravity="center_horizontal">
+
+    <LinearLayout android:id="@+id/topDisplayGroup"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <!-- header text ('Enter Pin Code') -->
+        <TextView android:id="@+id/headerText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+        <!-- Carrier info -->
+        <TextView android:id="@+id/carrier"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="9dip"
+            android:gravity="center"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+        <!-- password entry -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginRight="6dip"
+            android:layout_marginLeft="6dip"
+            android:gravity="center_vertical"
+            android:background="@android:drawable/edit_text">
+
+            <!-- displays dots as user enters pin -->
+            <TextView android:id="@+id/pinDisplay"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:maxLines="1"
+                android:textAppearance="?android:attr/textAppearanceLargeInverse"
+                android:textStyle="bold"
+                android:inputType="textPassword"
+            />
+
+            <ImageButton android:id="@+id/backspace"
+                android:src="@android:drawable/ic_input_delete"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="-3dip"
+                android:layout_marginBottom="-3dip"
+            />
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <include
+        android:id="@+id/keyPad"
+        layout="@android:layout/twelve_key_entry"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/topDisplayGroup"
+        android:layout_marginTop="10dip"
+    />
+
+    <!-- spacer below keypad -->
+    <View
+        android:id="@+id/spacerBottom"
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:layout_marginTop="6dip"
+        android:layout_above="@id/emergencyCall"
+        android:background="@android:drawable/divider_horizontal_dark"
+    />
+
+    <!-- The emergency button should take the rest of the space and be centered vertically -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <!-- emergency call button -->
+        <Button
+            android:id="@+id/emergencyCall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableLeft="@android:drawable/ic_emergency"
+            android:drawablePadding="8dip"
+            android:text="@android:string/lockscreen_emergency_call"
+        />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_status_land.xml b/core/res/res/layout-xlarge/keyguard_screen_status_land.xml
new file mode 100644
index 0000000..8589862
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_status_land.xml
@@ -0,0 +1,127 @@
+<?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.
+*/
+-->
+
+<!-- Status to show on the left side of lock screen -->
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="140dip"
+        android:layout_marginTop="20dip"
+        android:gravity="left"
+        >
+
+    <TextView
+        android:id="@+id/carrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="17sp"
+        android:drawablePadding="4dip"
+        android:layout_marginTop="32dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        />
+
+    <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:singleLine="true"
+            android:ellipsize="none"
+            android:textSize="120sp"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:shadowColor="#C0000000"
+            android:shadowDx="0"
+            android:shadowDy="0"
+            android:shadowRadius="3.0"
+            android:layout_marginBottom="6dip"
+            />
+
+
+        <TextView android:id="@+id/am_pm"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toRightOf="@id/timeDisplay"
+            android:layout_alignBaseline="@id/timeDisplay"
+            android:singleLine="true"
+            android:ellipsize="none"
+            android:textSize="30sp"
+            android:layout_marginLeft="8dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:shadowColor="#C0000000"
+            android:shadowDx="0"
+            android:shadowDy="0"
+            android:shadowRadius="3.0"
+            />
+
+    </com.android.internal.widget.DigitalClock>
+
+    <TextView
+        android:id="@+id/date"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/time"
+        android:layout_marginTop="5dip"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="32sp"
+        />
+
+    <!-- used for instructions such as "draw pattern to unlock", the next alarm, and charging
+         status.  -->
+    <TextView
+        android:id="@+id/status2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="32sp"
+        android:layout_marginTop="50dip"
+        android:drawablePadding="4dip"
+        />
+    <TextView
+        android:id="@+id/status1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="15dip"
+        android:textSize="32sp"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        />
+        
+    <TextView
+        android:id="@+id/propertyOf"
+        android:lineSpacingExtra="8dip"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="22sp"
+        android:layout_marginTop="50dip"
+        android:singleLine="false"
+        android:visibility="invisible"
+        />
+</LinearLayout>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_status_port.xml b/core/res/res/layout-xlarge/keyguard_screen_status_port.xml
new file mode 100644
index 0000000..8589862
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_status_port.xml
@@ -0,0 +1,127 @@
+<?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.
+*/
+-->
+
+<!-- Status to show on the left side of lock screen -->
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="140dip"
+        android:layout_marginTop="20dip"
+        android:gravity="left"
+        >
+
+    <TextView
+        android:id="@+id/carrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="17sp"
+        android:drawablePadding="4dip"
+        android:layout_marginTop="32dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        />
+
+    <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:singleLine="true"
+            android:ellipsize="none"
+            android:textSize="120sp"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:shadowColor="#C0000000"
+            android:shadowDx="0"
+            android:shadowDy="0"
+            android:shadowRadius="3.0"
+            android:layout_marginBottom="6dip"
+            />
+
+
+        <TextView android:id="@+id/am_pm"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toRightOf="@id/timeDisplay"
+            android:layout_alignBaseline="@id/timeDisplay"
+            android:singleLine="true"
+            android:ellipsize="none"
+            android:textSize="30sp"
+            android:layout_marginLeft="8dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:shadowColor="#C0000000"
+            android:shadowDx="0"
+            android:shadowDy="0"
+            android:shadowRadius="3.0"
+            />
+
+    </com.android.internal.widget.DigitalClock>
+
+    <TextView
+        android:id="@+id/date"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/time"
+        android:layout_marginTop="5dip"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="32sp"
+        />
+
+    <!-- used for instructions such as "draw pattern to unlock", the next alarm, and charging
+         status.  -->
+    <TextView
+        android:id="@+id/status2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="32sp"
+        android:layout_marginTop="50dip"
+        android:drawablePadding="4dip"
+        />
+    <TextView
+        android:id="@+id/status1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="15dip"
+        android:textSize="32sp"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        />
+        
+    <TextView
+        android:id="@+id/propertyOf"
+        android:lineSpacingExtra="8dip"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="22sp"
+        android:layout_marginTop="50dip"
+        android:singleLine="false"
+        android:visibility="invisible"
+        />
+</LinearLayout>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_tab_unlock.xml b/core/res/res/layout-xlarge/keyguard_screen_tab_unlock.xml
new file mode 100644
index 0000000..b3645aa
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_tab_unlock.xml
@@ -0,0 +1,97 @@
+<?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.
+*/
+-->
+
+<!-- This is the general lock screen which shows information about the
+  state of the device, as well as instructions on how to get past it
+  depending on the state of the device.  It is the same for landscape
+  and portrait.-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tabunlock="http://schemas.android.com/apk/res/com.android.tabunlock"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#70000000"
+    android:gravity="center_horizontal"
+    android:id="@+id/root">
+
+    <!-- left side: status and emergency call button -->
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:gravity="center_vertical"
+                >
+        <include layout="@layout/keyguard_screen_status_land" />
+    </LinearLayout>
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:orientation="vertical"
+                >
+        <TextView
+            android:id="@+id/screenLocked"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/status2"
+            android:layout_marginLeft="24dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:layout_marginTop="12dip"
+            android:drawablePadding="4dip"
+            />
+
+        <com.android.internal.widget.WaveView
+            android:id="@+id/wave_view"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            />
+
+        <!-- "emergency calls only" shown when sim is missing or PUKd -->
+        <TextView
+            android:id="@+id/emergencyCallText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/carrier"
+            android:layout_alignParentRight="true"
+            android:layout_marginTop="0dip"
+            android:layout_marginRight="8dip"
+            android:text="@string/emergency_calls_only"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="@color/white"
+           />
+
+        <!-- emergency call button shown when sim is PUKd and tab_selector is
+             hidden -->
+        <Button
+            android:id="@+id/emergencyCallButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableLeft="@drawable/ic_emergency"
+            android:layout_centerInParent="true"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="80dip"
+            style="@style/Widget.Button.Transparent"
+            android:drawablePadding="8dip"
+            android:visibility="gone"
+            />
+    </LinearLayout>
+
+</RelativeLayout>
+
diff --git a/core/res/res/layout-xlarge/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout-xlarge/keyguard_screen_tab_unlock_land.xml
new file mode 100644
index 0000000..6c99ccac
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_tab_unlock_land.xml
@@ -0,0 +1,95 @@
+<?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.
+*/
+-->
+
+<!-- This is the general lock screen which shows information about the
+  state of the device, as well as instructions on how to get past it
+  depending on the state of the device.-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tabunlock="http://schemas.android.com/apk/res/com.android.tabunlock"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:background="#70000000"
+    android:id="@+id/root">
+
+    <!-- left side: status and emergency call button -->
+    <LinearLayout
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_width="0dip"
+            android:orientation="vertical"
+            android:gravity="center_vertical"
+                >
+        <include layout="@layout/keyguard_screen_status_land" />
+    </LinearLayout>
+
+    <!-- right side -->
+    <LinearLayout
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_width="0dip"
+            android:orientation="horizontal"
+            android:gravity="center_horizontal"
+                >
+        <TextView
+                android:id="@+id/screenLocked"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/status2"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:gravity="center"
+                android:layout_marginTop="12dip"
+                android:drawablePadding="4dip"
+                />
+
+        <com.android.internal.widget.WaveView
+            android:id="@+id/wave_view"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_marginRight="0dip"
+            android:layout_weight="1.0"
+            />
+
+        <!-- "emergency calls only" shown when sim is missing or PUKd -->
+        <TextView
+            android:id="@+id/emergencyCallText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_marginTop="20dip"
+            android:text="@string/emergency_calls_only"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="@color/white"
+               />
+
+        <!-- emergency call button shown when sim is PUKd and tab_selector is
+             hidden -->
+        <Button
+            android:id="@+id/emergencyCallButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableLeft="@drawable/ic_emergency"
+            style="@style/Widget.Button.Transparent"
+            android:drawablePadding="8dip"
+            android:layout_marginRight="80dip"
+            android:visibility="gone"
+            />
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_unlock_landscape.xml b/core/res/res/layout-xlarge/keyguard_screen_unlock_landscape.xml
new file mode 100644
index 0000000..42636ad
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_unlock_landscape.xml
@@ -0,0 +1,106 @@
+<?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.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+     the user how to unlock their device, or make an emergency call.  This
+     is the portrait layout.  -->
+
+<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+        >
+
+    <!-- left side: status and emergency call button -->
+    <LinearLayout
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_width="0dip"
+            android:orientation="vertical"
+            android:gravity="center_vertical"
+                >
+        
+        <include layout="@layout/keyguard_screen_status_land" />
+
+        <!-- footer -->
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="140dip"
+            >
+
+            <!-- option 1: a single emergency call button -->
+            <RelativeLayout android:id="@+id/footerNormal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="left"
+                >
+                <Button android:id="@+id/emergencyCallAlone"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/lockscreen_emergency_call"
+                    style="@style/Widget.Button.Transparent"
+                    android:drawableLeft="@drawable/ic_emergency"
+                    android:drawablePadding="8dip"
+                    android:visibility="gone"
+                    />
+            </RelativeLayout>
+
+            <!-- option 2: an emergency call button, and a 'forgot pattern?' button -->
+            <LinearLayout android:id="@+id/footerForgotPattern"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="left"
+                >
+                <Button android:id="@+id/forgotPattern"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    style="@style/Widget.Button.Transparent"
+                    />
+                <Button android:id="@+id/emergencyCallTogether"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/lockscreen_emergency_call"
+                    style="@style/Widget.Button.Transparent"
+                    android:drawableLeft="@drawable/ic_emergency"
+                    android:drawablePadding="8dip"
+                    android:visibility="gone"
+                    />
+            </LinearLayout>
+        </FrameLayout>
+    </LinearLayout>
+
+    <!-- right side: lock pattern -->
+    <LinearLayout
+        android:layout_weight="1"
+        android:layout_width="0dip"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        >
+        <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern"
+            android:layout_width="350dip"
+            android:layout_height="350dip"
+            android:layout_marginTop="90dip"
+            android:layout_marginRight="90dip"
+          />
+    </LinearLayout>
+
+</com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>
diff --git a/core/res/res/layout-xlarge/keyguard_screen_unlock_portrait.xml b/core/res/res/layout-xlarge/keyguard_screen_unlock_portrait.xml
new file mode 100644
index 0000000..aeed79b
--- /dev/null
+++ b/core/res/res/layout-xlarge/keyguard_screen_unlock_portrait.xml
@@ -0,0 +1,102 @@
+<?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.
+*/
+-->
+
+<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+        >
+
+    <!-- left side: status and emergency call button -->
+    <LinearLayout
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:layout_width="match_parent"
+            android:orientation="vertical"
+            android:gravity="center_vertical"
+                >
+        
+        <include layout="@layout/keyguard_screen_status_port" />
+
+        <!-- footer -->
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="140dip"
+            >
+
+            <!-- option 1: a single emergency call button -->
+            <RelativeLayout android:id="@+id/footerNormal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="left"
+                >
+                <Button android:id="@+id/emergencyCallAlone"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/lockscreen_emergency_call"
+                    style="@style/Widget.Button.Transparent"
+                    android:drawableLeft="@drawable/ic_emergency"
+                    android:drawablePadding="8dip"
+                    android:visibility="gone"
+                    />
+            </RelativeLayout>
+
+            <!-- option 2: an emergency call button, and a 'forgot pattern?' button -->
+            <LinearLayout android:id="@+id/footerForgotPattern"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="left"
+                >
+                <Button android:id="@+id/forgotPattern"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    style="@style/Widget.Button.Transparent"
+                    />
+                <Button android:id="@+id/emergencyCallTogether"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/lockscreen_emergency_call"
+                    style="@style/Widget.Button.Transparent"
+                    android:drawableLeft="@drawable/ic_emergency"
+                    android:drawablePadding="8dip"
+                    android:visibility="gone"
+                    />
+            </LinearLayout>
+        </FrameLayout>
+    </LinearLayout>
+
+    <!-- right side: lock pattern -->
+    <LinearLayout
+        android:layout_weight="1"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:gravity="center"
+        >
+        <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern"
+            android:layout_width="350dip"
+            android:layout_height="350dip"
+            android:layout_marginTop="50dip"
+          />
+    </LinearLayout>
+
+</com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>
+
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..c402556
--- /dev/null
+++ b/core/res/res/layout-xlarge/screen_action_bar.xml
@@ -0,0 +1,46 @@
+<?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_no_alpha"
+        android:outAnimation="@anim/push_down_out_no_alpha">
+        <com.android.internal.widget.ActionBarView
+            android:id="@+id/action_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionBarStyle" />
+        <com.android.internal.widget.ActionBarContextView
+            android:id="@+id/action_context_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionModeStyle" />
+    </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/screen_action_bar_overlay.xml b/core/res/res/layout-xlarge/screen_action_bar_overlay.xml
new file mode 100644
index 0000000..af051ae
--- /dev/null
+++ b/core/res/res/layout-xlarge/screen_action_bar_overlay.xml
@@ -0,0 +1,49 @@
+<?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 overlaying application content.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fitsSystemWindows="true">
+    <FrameLayout android:id="@android:id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <ViewAnimator android:id="@+id/action_bar_animator"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="top"
+        android:inAnimation="@anim/push_down_in_no_alpha"
+        android:outAnimation="@anim/push_down_out_no_alpha">
+        <com.android.internal.widget.ActionBarView
+            android:id="@+id/action_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionBarStyle" />
+        <com.android.internal.widget.ActionBarContextView
+            android:id="@+id/action_context_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionModeStyle" />
+    </ViewAnimator>
+    <ImageView android:src="?android:attr/windowContentOverlay"
+               android:scaleType="fitXY"
+               android:layout_width="match_parent"
+               android:layout_height="wrap_content"
+               android:layout_below="@id/action_bar_animator" />
+</RelativeLayout>
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..3864981
--- /dev/null
+++ b/core/res/res/layout/action_bar_title_item.xml
@@ -0,0 +1,31 @@
+<?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" />
+    <TextView android:id="@+id/action_bar_subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="end" />
+</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..dc09fdc
--- /dev/null
+++ b/core/res/res/layout/action_mode_bar.xml
@@ -0,0 +1,24 @@
+<?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"
+     android:visibility="gone"
+     style="?android:attr/actionModeStyle" />
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 25a41f8..b746d28 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -80,8 +80,7 @@
             android:paddingTop="2dip"
             android:paddingBottom="12dip"
             android:paddingLeft="14dip"
-            android:paddingRight="10dip"
-            android:overscrollMode="ifContentScrolls">
+            android:paddingRight="10dip">
             <TextView android:id="@+id/message"
                 style="?android:attr/textAppearanceMedium"
                 android:layout_width="match_parent"
@@ -113,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/fragment_bread_crumb_item.xml b/core/res/res/layout/fragment_bread_crumb_item.xml
new file mode 100644
index 0000000..408f6e8
--- /dev/null
+++ b/core/res/res/layout/fragment_bread_crumb_item.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical"
+        android:textAppearance="?android:attr/textAppearanceMediumInverse"
+        android:drawableLeft="@drawable/nav_divider"
+        android:paddingLeft="12dp"
+        android:drawablePadding="12dp"
+        />
diff --git a/core/res/res/layout/fragment_bread_crumbs.xml b/core/res/res/layout/fragment_bread_crumbs.xml
new file mode 100644
index 0000000..f289e14
--- /dev/null
+++ b/core/res/res/layout/fragment_bread_crumbs.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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent">
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml
index ab675c7..3d52f71 100644
--- a/core/res/res/layout/keyguard_screen_password_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_password_landscape.xml
@@ -28,7 +28,7 @@
         android:layout_height="wrap_content"
         android:orientation="horizontal">
         <!-- "Enter PIN(Password) to unlock" -->
-        <TextView android:id="@+id/enter_password_label"
+        <TextView android:id="@+id/status1"
             android:layout_width="0dip"
             android:layout_height="wrap_content"
             android:layout_weight="1"
diff --git a/core/res/res/layout/keyguard_screen_password_portrait.xml b/core/res/res/layout/keyguard_screen_password_portrait.xml
index 9ee8781..af8a3ef 100644
--- a/core/res/res/layout/keyguard_screen_password_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_password_portrait.xml
@@ -24,7 +24,7 @@
     android:gravity="center_horizontal">
 
     <!-- "Enter PIN(Password) to unlock" -->
-    <TextView android:id="@+id/enter_password_label"
+    <TextView android:id="@+id/status1"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
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/locale_picker_item.xml b/core/res/res/layout/locale_picker_item.xml
new file mode 100644
index 0000000..b63f5ab
--- /dev/null
+++ b/core/res/res/layout/locale_picker_item.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:padding="5dip">
+
+    <TextView android:id="@+id/locale"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+    />
+</LinearLayout >
diff --git a/core/res/res/layout/popup_menu_item_layout.xml b/core/res/res/layout/popup_menu_item_layout.xml
new file mode 100644
index 0000000..c30c4f2
--- /dev/null
+++ b/core/res/res/layout/popup_menu_item_layout.xml
@@ -0,0 +1,59 @@
+<?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.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight">
+    
+    <!-- Icon will be inserted here. -->
+    
+    <!-- The title and summary have some gap between them, and this 'group' should be centered vertically. -->
+    <RelativeLayout
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginLeft="6dip"
+        android:layout_marginRight="6dip"
+        android:duplicateParentState="true">
+        
+        <TextView 
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentLeft="true"
+            android:textAppearance="?android:attr/textAppearanceLargePopupMenu"
+            android:singleLine="true"
+            android:duplicateParentState="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+
+        <TextView
+            android:id="@+id/shortcut"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/title"
+            android:layout_alignParentLeft="true"
+            android:textAppearance="?android:attr/textAppearanceSmallPopupMenu"
+            android:singleLine="true"
+            android:duplicateParentState="true" />
+
+    </RelativeLayout>
+
+    <!-- Checkbox, and/or radio button will be inserted here. -->
+    
+</com.android.internal.view.menu.ListMenuItemView>
diff --git a/core/res/res/layout/preference_dialog_edittext.xml b/core/res/res/layout/preference_dialog_edittext.xml
index b41e774..5be5773 100644
--- a/core/res/res/layout/preference_dialog_edittext.xml
+++ b/core/res/res/layout/preference_dialog_edittext.xml
@@ -17,8 +17,7 @@
 <!-- Layout used as the dialog's content View for EditTextPreference. -->
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:overscrollMode="ifContentScrolls">
+    android:layout_height="match_parent">
 
     <LinearLayout
         android:id="@+android:id/edittext_container"
diff --git a/core/res/res/layout/preference_header_item.xml b/core/res/res/layout/preference_header_item.xml
new file mode 100644
index 0000000..aba7b2ad
--- /dev/null
+++ b/core/res/res/layout/preference_header_item.xml
@@ -0,0 +1,62 @@
+<?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 of a header item in PreferenceActivity. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    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:ellipsize="end"
+            android:maxLines="2" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
index 8f86981..7c0b791 100644
--- a/core/res/res/layout/preference_list_content.xml
+++ b/core/res/res/layout/preference_list_content.xml
@@ -4,22 +4,97 @@
 **
 ** 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">
+
+        <LinearLayout
+            android:id="@+id/headers"
+            android:orientation="vertical"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="10">
+
+            <ListView android:id="@android:id/list"
+                android:layout_width="match_parent"
+                android:layout_height="0px"
+                android:layout_weight="1"
+                android:drawSelectorOnTop="false"
+                android:scrollbarAlwaysDrawVerticalTrack="true" />
+
+            <FrameLayout android:id="@+id/list_footer"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="0" />
+
+        </LinearLayout>
+
+        <FrameLayout android:id="@+id/prefs"
+                android:layout_width="0px"
+                android:layout_height="match_parent"
+                android:layout_weight="33"
+                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"
+        />
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true">
+
+            <Button android:id="@+id/skip_button"
+                android:layout_width="150dip"
+                android:layout_height="wrap_content"
+                android:layout_margin="5dip"
+                android:text="@string/skip_button_label"
+                android:visibility="gone"
+            />
+
+            <Button android:id="@+id/next_button"
+                android:layout_width="150dip"
+                android:layout_height="wrap_content"
+                android:layout_margin="5dip"
+                android:drawableRight="@drawable/ic_btn_next"
+                android:drawablePadding="3dip"
+                android:text="@string/next_button_label"
+            />
+        </LinearLayout>
+    </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..b158414
--- /dev/null
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -0,0 +1,51 @@
+<?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_no_alpha"
+        android:outAnimation="@anim/push_down_out_no_alpha">
+        <com.android.internal.widget.ActionBarView
+            android:id="@+id/action_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionBarStyle" />
+        <com.android.internal.widget.ActionBarContextView
+            android:id="@+id/action_context_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionModeStyle" />
+    </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/actionBarStyle"
+                  android:visibility="gone" />
+</LinearLayout>
diff --git a/core/res/res/layout/screen_action_bar_overlay.xml b/core/res/res/layout/screen_action_bar_overlay.xml
new file mode 100644
index 0000000..92a1ee0
--- /dev/null
+++ b/core/res/res/layout/screen_action_bar_overlay.xml
@@ -0,0 +1,55 @@
+<?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 overlaying application content.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fitsSystemWindows="true">
+    <FrameLayout android:id="@android:id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <ViewAnimator android:id="@+id/action_bar_animator"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="top"
+        android:inAnimation="@anim/push_down_in_no_alpha"
+        android:outAnimation="@anim/push_down_out_no_alpha">
+        <com.android.internal.widget.ActionBarView
+            android:id="@+id/action_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionBarStyle" />
+        <com.android.internal.widget.ActionBarContextView
+            android:id="@+id/action_context_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionModeStyle" />
+    </ViewAnimator>
+    <ImageView android:src="?android:attr/windowContentOverlay"
+               android:scaleType="fitXY"
+               android:layout_width="match_parent"
+               android:layout_height="wrap_content"
+               android:layout_below="@id/action_bar_animator" />
+    <LinearLayout android:id="@+id/lower_action_context_bar"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:layout_gravity="bottom"
+                  style="?android:attr/actionBarStyle"
+                  android:visibility="gone" />
+</RelativeLayout>
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/search_dropdown_item_icons_2line.xml b/core/res/res/layout/search_dropdown_item_icons_2line.xml
index d71b4f7..fcdf91b 100644
--- a/core/res/res/layout/search_dropdown_item_icons_2line.xml
+++ b/core/res/res/layout/search_dropdown_item_icons_2line.xml
@@ -1,31 +1,31 @@
 <?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 
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this 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.
 */
 -->
 
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:paddingLeft="4dip"
     android:paddingRight="2dip"
     android:layout_width="match_parent"
     android:layout_height="?android:attr/searchResultListItemHeight" >
 
     <!-- Icons come first in the layout, since their placement doesn't depend on
-         the placement of the text views. -->    
+         the placement of the text views. -->
     <ImageView android:id="@android:id/icon1"
         android:layout_width="48dip"
         android:layout_height="48dip"
@@ -35,31 +35,44 @@
         android:layout_alignParentBottom="true"
         android:visibility="gone" />
 
-    <ImageView android:id="@android:id/icon2"
+    <ImageView android:id="@+id/edit_query"
         android:layout_width="48dip"
         android:layout_height="48dip"
         android:scaleType="centerInside"
         android:layout_alignParentRight="true"
         android:layout_alignParentTop="true"
         android:layout_alignParentBottom="true"
+        android:src="@android:drawable/edit_query"
+        android:background="@android:drawable/edit_query_background"
         android:visibility="gone" />
 
+    <ImageView android:id="@android:id/icon2"
+        android:layout_width="48dip"
+        android:layout_height="48dip"
+        android:scaleType="centerInside"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_toLeftOf="@id/edit_query"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentBottom="true"
+        android:visibility="gone" />
+
+
     <!-- The subtitle comes before the title, since the height of the title depends on whether the
-         subtitle is visible or gone. --> 
+         subtitle is visible or gone. -->
     <TextView android:id="@android:id/text2"
         style="?android:attr/dropDownItemStyle"
         android:textAppearance="?android:attr/textAppearanceSearchResultSubtitle"
         android:singleLine="true"
         android:layout_width="match_parent"
-        android:layout_height="29dip" 
+        android:layout_height="29dip"
         android:paddingBottom="4dip"
         android:gravity="top"
         android:layout_toRightOf="@android:id/icon1"
         android:layout_toLeftOf="@android:id/icon2"
-        android:layout_alignWithParentIfMissing="true" 
+        android:layout_alignWithParentIfMissing="true"
         android:layout_alignParentBottom="true"
         android:visibility="gone" />
-    
+
     <!-- The title is placed above the subtitle, if there is one. If there is no
          subtitle, it fills the parent. -->
     <TextView android:id="@android:id/text1"
@@ -72,5 +85,5 @@
         android:layout_toRightOf="@android:id/icon1"
         android:layout_toLeftOf="@android:id/icon2"
         android:layout_above="@android:id/text2" />
-    
+
 </RelativeLayout>
diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml
new file mode 100644
index 0000000..c229b59
--- /dev/null
+++ b/core/res/res/layout/search_view.xml
@@ -0,0 +1,119 @@
+<?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:id="@+id/search_bar"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:focusable="true"
+    android:descendantFocusability="afterDescendants">
+
+    <!-- This is actually used for the badge icon *or* the badge label (or neither) -->
+    <TextView
+        android:id="@+id/search_badge"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="2dip"
+        android:drawablePadding="0dip"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorPrimaryInverse"
+        android:visibility="gone"
+    />
+
+    <ImageView
+        android:id="@+id/search_button"
+        android:layout_height="36dip"
+        android:layout_width="36dip"
+        android:layout_marginRight="7dip"
+        android:layout_gravity="center_vertical"
+        android:src="@android:drawable/ic_btn_search"
+    />
+
+    <!-- Inner layout contains the app icon, button(s) and EditText -->
+    <LinearLayout
+        android:id="@+id/search_edit_frame"
+        android:layout_width="300dp"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:orientation="horizontal"
+        android:background="?android:attr/editTextBackground">
+
+        <ImageView
+            android:id="@+id/search_app_icon"
+            android:layout_height="24dip"
+            android:layout_width="24dip"
+            android:layout_marginRight="7dip"
+            android:layout_gravity="bottom"
+            android:src="@android:drawable/ic_btn_search"
+        />
+
+        <AutoCompleteTextView
+            android:id="@+id/search_src_text"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:layout_weight="1"
+            android:paddingLeft="8dip"
+            android:paddingRight="6dip"
+            android:drawablePadding="2dip"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:background="@null"
+            android:inputType="text|textAutoComplete"
+            android:imeOptions="actionSearch"
+            android:dropDownWidth="300dp"
+            android:dropDownHeight="wrap_content"
+            android:dropDownAnchor="@id/search_edit_frame"
+            android:dropDownVerticalOffset="0dip"
+            android:dropDownHorizontalOffset="0dip"
+            android:popupBackground="@android:drawable/search_dropdown_background"
+        />
+
+        <ImageView
+            android:id="@+id/search_close_btn"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_gravity="bottom"
+            android:src="@android:drawable/btn_close"
+        />
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/search_go_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginLeft="4dip"
+        android:layout_marginRight="4dip"
+        android:src="@android:drawable/ic_btn_find_next"
+    />
+
+    <ImageButton
+        android:id="@+id/search_voice_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginLeft="0dip"
+        android:layout_marginTop="-6.5dip"
+        android:layout_marginBottom="-7dip"
+        android:layout_marginRight="-5dip"
+        android:background="@drawable/btn_search_dialog_voice"
+        android:src="@android:drawable/ic_btn_speak_now"
+        android:visibility="gone"
+    />
+</LinearLayout>
diff --git a/core/res/res/layout/select_dialog.xml b/core/res/res/layout/select_dialog.xml
index 6e4e5e1..94dcb6a 100644
--- a/core/res/res/layout/select_dialog.xml
+++ b/core/res/res/layout/select_dialog.xml
@@ -30,6 +30,5 @@
     android:layout_height="match_parent"
     android:layout_marginTop="5px"
     android:cacheColorHint="@null"
-    android:divider="@android:drawable/divider_horizontal_bright"
-    android:scrollbars="vertical"
-    android:overscrollMode="ifContentScrolls" />
+    android:divider="?android:attr/listDividerAlertDialog"
+    android:scrollbars="vertical" />
diff --git a/core/res/res/layout/select_dialog_item.xml b/core/res/res/layout/select_dialog_item.xml
index 30fe02e..96fdcc6 100644
--- a/core/res/res/layout/select_dialog_item.xml
+++ b/core/res/res/layout/select_dialog_item.xml
@@ -29,7 +29,7 @@
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:textAppearance="?android:attr/textAppearanceLarge"
-    android:textColor="@android:color/primary_text_light_disable_only"
+    android:textColor="?android:attr/textColorAlertDialogListItem"
     android:gravity="center_vertical"
     android:paddingLeft="14dip"
     android:paddingRight="15dip"
diff --git a/core/res/res/layout/select_dialog_multichoice.xml b/core/res/res/layout/select_dialog_multichoice.xml
index 5785d3b..a9be014 100644
--- a/core/res/res/layout/select_dialog_multichoice.xml
+++ b/core/res/res/layout/select_dialog_multichoice.xml
@@ -20,11 +20,11 @@
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:textAppearance="?android:attr/textAppearanceLarge"
-    android:textColor="@android:color/primary_text_light_disable_only"
+    android:textColor="?android:attr/textColorAlertDialogListItem"
     android:gravity="center_vertical"
     android:paddingLeft="12dip"
     android:paddingRight="7dip"
-    android:checkMark="@android:drawable/btn_check"
+    android:checkMark="?android:attr/listChoiceIndicatorMultiple"
     android:ellipsize="marquee"
 />
 
diff --git a/core/res/res/layout/select_dialog_singlechoice.xml b/core/res/res/layout/select_dialog_singlechoice.xml
index 3560fee..1b9c973 100644
--- a/core/res/res/layout/select_dialog_singlechoice.xml
+++ b/core/res/res/layout/select_dialog_singlechoice.xml
@@ -20,10 +20,10 @@
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:textAppearance="?android:attr/textAppearanceLarge"
-    android:textColor="@android:color/primary_text_light_disable_only"
+    android:textColor="?android:attr/textColorAlertDialogListItem"
     android:gravity="center_vertical"
     android:paddingLeft="12dip"
     android:paddingRight="7dip"
-    android:checkMark="@android:drawable/btn_radio"
+    android:checkMark="?android:attr/listChoiceIndicatorSingle"
     android:ellipsize="marquee"
 />
diff --git a/core/res/res/layout/simple_list_item_2.xml b/core/res/res/layout/simple_list_item_2.xml
index 5eab54e..c87922c 100644
--- a/core/res/res/layout/simple_list_item_2.xml
+++ b/core/res/res/layout/simple_list_item_2.xml
@@ -15,8 +15,6 @@
 -->
 
 <TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android" 
-	android:paddingTop="2dip"
-	android:paddingBottom="2dip"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
diff --git a/core/res/res/layout/simple_list_item_activated_1.xml b/core/res/res/layout/simple_list_item_activated_1.xml
new file mode 100644
index 0000000..8416df2
--- /dev/null
+++ b/core/res/res/layout/simple_list_item_activated_1.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center_vertical"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+/>
diff --git a/core/res/res/layout/simple_list_item_activated_2.xml b/core/res/res/layout/simple_list_item_activated_2.xml
new file mode 100644
index 0000000..2ffbf02
--- /dev/null
+++ b/core/res/res/layout/simple_list_item_activated_2.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.
+-->
+
+<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:paddingTop="2dip"
+    android:paddingBottom="2dip"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:mode="twoLine"
+>
+
+    <TextView android:id="@android:id/text1"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="6dip"
+        android:layout_marginTop="6dip"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+    />
+
+    <TextView android:id="@android:id/text2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@android:id/text1"
+        android:layout_alignLeft="@android:id/text1"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+
+</TwoLineListItem>
diff --git a/core/res/res/layout/simple_selectable_list_item.xml b/core/res/res/layout/simple_selectable_list_item.xml
new file mode 100644
index 0000000..518bcd0
--- /dev/null
+++ b/core/res/res/layout/simple_selectable_list_item.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:gravity="center_vertical"
+    android:background="?android:attr/listChoiceBackgroundIndicator"
+    android:paddingLeft="6dip"
+    android:paddingRight="9dip"
+/>
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/layout/webview_find.xml b/core/res/res/layout/webview_find.xml
new file mode 100644
index 0000000..f2edb2b
--- /dev/null
+++ b/core/res/res/layout/webview_find.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    >
+    <EditText android:id="@+id/edit"
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:scrollHorizontally="true"
+        android:inputType="text"
+        android:hint="@string/find_on_page"
+        android:imeOptions="actionDone"
+        android:layout_marginRight="10dip"
+        />
+    <TextView android:id="@+id/matches"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondaryInverse"
+        />
+</LinearLayout>
diff --git a/core/res/res/menu/webview_copy.xml b/core/res/res/menu/webview_copy.xml
new file mode 100644
index 0000000..adba563
--- /dev/null
+++ b/core/res/res/menu/webview_copy.xml
@@ -0,0 +1,35 @@
+<?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/copy"
+        android:icon="@drawable/ic_menu_copy"
+        android:showAsAction="always"
+        />
+    <item android:id="@+id/share"
+        android:icon="@drawable/ic_menu_share"
+        android:showAsAction="always"
+        />
+    <item android:id="@+id/select_all"
+        android:icon="@drawable/ic_menu_select_all"
+        android:showAsAction="always"
+        />
+    <item android:id="@+id/find"
+        android:icon="@drawable/ic_menu_find"
+        android:showAsAction="always"
+        />
+</menu>
+
diff --git a/core/res/res/menu/webview_find.xml b/core/res/res/menu/webview_find.xml
new file mode 100644
index 0000000..74a40aa
--- /dev/null
+++ b/core/res/res/menu/webview_find.xml
@@ -0,0 +1,26 @@
+<?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/find_prev"
+        android:icon="@drawable/ic_btn_find_prev"
+        android:showAsAction="always"
+        />
+    <item android:id="@+id/find_next"
+        android:icon="@drawable/ic_btn_find_next"
+        android:showAsAction="always"
+        />
+</menu>
diff --git a/core/res/res/raw-rm/loaderror.html b/core/res/res/raw-rm/loaderror.html
new file mode 100644
index 0000000..8e4a3fe
--- /dev/null
+++ b/core/res/res/raw-rm/loaderror.html
@@ -0,0 +1,18 @@
+<html>
+    <head>
+        <title>Pagina d'internet betg disponibla</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Pagina d'internet betg disponibla</h2>
+            <p>Impussibel da chargiar la pagina d'internet sut <a href="%s">%s</a>. Errur:</p>
+            <!-- The %e is replaced by a localized error string -->
+            <p>%e</p>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/raw-rm/nodomain.html b/core/res/res/raw-rm/nodomain.html
new file mode 100644
index 0000000..1e2833b
--- /dev/null
+++ b/core/res/res/raw-rm/nodomain.html
@@ -0,0 +1,23 @@
+<html>
+    <head>
+        <title>Pagina d'internet betg disponibla</title>
+        <style type="text/css">
+            body { margin-top: 0px; padding-top: 0px; }
+            h2   { margin-top: 5px; padding-top: 0px; }
+        </style>
+
+        <body>
+
+            <img src="file:///android_asset/webkit/android-weberror.png" align="top" />
+            <h2>Pagina d'internet betg disponibla</h2>
+            <p>La pagina d'internet sut <a href="%s">%s</a> &egrave; eventualmain deactivada temporarmain u spustada definitivamain ad ina nova adressa da web.</p>
+
+            <p><b>Qua in p&egrave;r propostas:</b></p>
+            <ul>
+                <li>Controllai che Voss apparat recepescha in signal e dispona d'ina connexiun da datas.</li>
+                <li>Chargiai pli tard danovamain la pagina d'internet.</li>
+                <li>Chargiai ina copia da la pagina da web memorisada en il cache da Google.</li>
+            </ul>
+        </body>
+    </head>
+</html>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 925e26c..e6c0a48 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Nástroje pro vývojáře"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funkce pouze pro vývojáře aplikací"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Úložiště"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Přístup ke kartě SD."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Přístup ke kartě SD."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Umožňuje aplikaci uvolnit paměť telefonu smazáním souborů v adresáři mezipaměti aplikace. Přístup je velmi omezený, většinou pouze pro systémové procesy."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Přesun zdrojů aplikace"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Umožňuje aplikaci přesunout své zdroje z interní paměti na externí médium a opačně."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"čtení systémových souborů protokolu"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Umožňuje aplikaci číst různé systémové soubory protokolů. Toto nastavení aplikaci umožní získat obecné informace o činnostech s telefonem, ale neměly by obsahovat žádné osobní či soukromé informace."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"čtení nebo zápis do prostředků funkce diag"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Umožňuje aplikaci číst libovolné prostředky ve skupině diag, např. soubory ve složce /dev, a zapisovat do nich. Může dojít k ovlivnění stability a bezpečnosti systému. Toto nastavení by měl používat pouze výrobce či operátor pro diagnostiku hardwaru."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"povolení či zakázání komponent aplikací"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Umožňuje aplikaci konfigurovat místní telefon s rozhraním Bluetooth a vyhledávat a párovat vzdálená zařízení."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"vytvoření připojení Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Umožňuje aplikaci zobrazit konfiguraci místního telefonu s rozhraním Bluetooth, vytvářet připojení ke spárovaným zařízením a přijímat tato připojení."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"vypnutí zámku kláves"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Umožňuje aplikaci vypnout zámek kláves a související zabezpečení heslem. Příkladem oprávněného použití této funkce je vypnutí zámku klávesnice při příchozím hovoru a jeho opětovné zapnutí po skončení hovoru."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"čtení nastavení synchronizace"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Umožní aplikaci číst soukromá slova, jména a fráze, která uživatel mohl uložit do svého slovníku."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"zápis do slovníku definovaného uživatelem"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Umožní aplikaci zapisovat nová slova do uživatelského slovníku."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"změna/smazání obsahu karty SD"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"změna/smazání obsahu karty SD"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Umožní aplikaci zápis na kartu SD."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Umožní aplikaci zápis na kartu SD."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"změna/smazání obsahu karty SD"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Umožní aplikaci zápis na kartu SD."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"přistupovat do souborového systému mezipaměti"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Umožňuje aplikaci číst a zapisovat do souborového systému mezipaměti."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Omezení hesla"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Omezuje typ hesel, která lze použít."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Sledování pokusů o přihlášení"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Sleduje nezdařené pokusy o přihlášení do zařízení a umožňuje provedení určité akce."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Obnovení hesla"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Vynutí nastavení hesla na novou hodnotu, kterou vám před přihlášením musí sdělit správce."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Vynucení uzamčení"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Určuje, kdy dojde k uzamčení zařízení a bude požadováno opětovné zadání hesla."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Omezení hesla"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Omezuje typ hesel, která lze použít."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Sledování pokusů o přihlášení"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Sleduje nezdařené pokusy o přihlášení do zařízení a umožňuje provedení určité akce."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Obnovení hesla"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Vynutí nastavení hesla na novou hodnotu, kterou vám před přihlášením musí sdělit správce."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Vynucení uzamčení"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Určuje, kdy dojde k uzamčení zařízení a bude požadováno opětovné zadání hesla."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Vymazání všech dat"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Bez dalšího potvrzení obnoví výchozí nastavení z výroby a smaže všechna data."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Bez dalšího potvrzení obnoví výchozí nastavení z výroby a smaže všechna data."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Nastavte globální server proxy, který je používán, když jsou zásady aktivní. Platný globální server proxy nastavuje pouze první správce zařízení."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Domů"</item>
     <item msgid="869923650527136615">"Mobil"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"pomocí <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> pomocí <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Zadejte kód PIN"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Zadejte heslo pro odblokování"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Zadejte kód PIN pro odblokování"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Nesprávný kód PIN"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Chcete-li telefon odemknout, stiskněte Menu a poté 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Číslo tísňové linky"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Zavolat zpět"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Správně!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Zkuste to prosím znovu"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Zkuste to prosím znovu"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Nabíjení (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Nabito."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Karta SIM je zablokována."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Odblokování karty SIM..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"<xliff:g id="NUMBER_0">%d</xliff:g>krát jste nakreslili nesprávné bezpečnostní gesto. "\n\n"Opakujte prosím akci za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Vícekrát (<xliff:g id="NUMBER_0">%d</xliff:g>) jste nesprávně zadali heslo. "\n\n"Zkuste to znovu za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Vícekrát (<xliff:g id="NUMBER_0">%d</xliff:g>) jste nesprávně zadali kód PIN. "\n\n"Zkuste to znovu za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"<xliff:g id="NUMBER_0">%d</xliff:g>krát jste nesprávně nakreslili své bezpečnostní gesto. Po dalších neúspěšných pokusech (<xliff:g id="NUMBER_1">%d</xliff:g>) budete požádáni o odemčení telefonu pomocí přihlášení do účtu Google."\n\n" Akci prosím opakujte za několik sekund (<xliff:g id="NUMBER_2">%d</xliff:g>)."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Sekundy zbývající do dalšího pokusu: <xliff:g id="NUMBER">%d</xliff:g>."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Zapomněli jste gesto?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Chcete opustit tuto stránku?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Vyberte OK, chcete-li pokračovat, nebo Zrušit, chcete-li na stránce zůstat."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Potvrdit"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Tip: Dvojitým klepnutím můžete zobrazení přiblížit nebo oddálit."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"čtení historie a záložek Prohlížeče"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Umožňuje aplikaci číst všechny navštívené adresy URL a záložky Prohlížeče."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"zápis do historie a záložek Prohlížeče"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Umožní aplikaci změnit historii či záložky prohlížeče uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit data Prohlížeče."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Změnit oprávnění prohlížeče poskytovat informace o zeměpisné poloze"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Umožňuje aplikaci změnit oprávnění prohlížeče poskytovat informace o zeměpisné poloze. Škodlivé aplikace mohou toto nastavení použít k odesílání informací o umístění na libovolné webové stránky."</string>
     <string name="save_password_message" msgid="767344687139195790">"Chcete, aby si prohlížeč zapamatoval toto heslo?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Vybrat vše"</string>
-    <string name="selectText" msgid="4862359311088898878">"Vybrat slovo"</string>
     <string name="cut" msgid="3092569408438626261">"Vyjmout"</string>
     <string name="copy" msgid="2681946229533511987">"Kopírovat"</string>
     <string name="paste" msgid="5629880836805036433">"Vložit"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopírovat adresu URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Vybrat text..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Výběr textu"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metoda zadávání dat"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Přidat slovo „<xliff:g id="WORD">%s</xliff:g>“ do slovníku"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Úpravy textu"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Operace s textem"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Málo paměti"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"V telefonu zbývá málo místa pro ukládání dat."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Zrušit"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Upozornění"</string>
+    <string name="loading" msgid="1760724998928255250">"Načítání..."</string>
     <string name="capital_on" msgid="1544682755514494298">"ZAPNUTO"</string>
     <string name="capital_off" msgid="6815870386972805832">"VYPNOUT"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Dokončit akci pomocí aplikace"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Aplikace přesměrována"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"Je spuštěna aplikace <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"Původně byla spuštěna aplikace <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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é vlastní vynucené zásady StrictMode."</string>
     <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> porušil své vlastní vynucené zásady StrictMode."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"Běží aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Zobrazit vše"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"Úložiště USB"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB připojeno"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Připojili jste svůj telefon k počítači pomocí USB. Chcete-li kopírovat soubory z počítače na kartu SD v zařízení Android či obráceně, vyberte následující tlačítko."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Připojili jste svůj telefon k počítači pomocí USB. Chcete-li kopírovat soubory z počítače na kartu SD v zařízení Android či obráceně, vyberte následující tlačítko."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Připojili jste svůj telefon k počítači pomocí USB. Chcete-li kopírovat soubory z počítače na kartu SD v zařízení Android či obráceně, vyberte následující tlačítko."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Zapnout úložiště USB"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Při používání vaší karty SD jako úložiště USB došlo k problému."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Při používání vaší karty SD jako úložiště USB došlo k problému."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Při používání vaší karty SD jako úložiště USB došlo k problému."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB připojeno"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Vyberte, chcete-li kopírovat soubory do nebo z počítače."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Vypnout úložiště USB"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Vyberte, chcete-li vypnout úložiště USB."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"Úložiště USB je používáno"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Před vypnutím úložiště USB zkontrolujte, zda jste odpojili (vyjmuli) kartu SD zařízení Android z počítače."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Před vypnutím úložiště USB zkontrolujte, zda jste odpojili (vyjmuli) kartu SD zařízení Android z počítače."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Před vypnutím úložiště USB zkontrolujte, zda jste odpojili (vyjmuli) kartu SD zařízení Android z počítače."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Vypnout úložiště USB"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Při vypínání úložiště USB došlo k problémům. Zkontrolujte, zda byl hostitel USB odpojen, a zkuste to znovu."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Zapnout úložiště USB"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Pokud zapnete úložiště USB, dojde k zastavení některých používaných aplikací. Tyto aplikace pravděpodobně nebudou k dispozici až do vypnutí úložiště USB."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"Chyba operace na rozhraní USB"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formátovat kartu SD"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formátovat kartu SD"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Opravdu chcete kartu SD naformátovat? Všechna data na kartě budou ztracena."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Opravdu chcete kartu SD naformátovat? Všechna data na kartě budou ztracena."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formátovat kartu SD"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Opravdu chcete kartu SD naformátovat? Všechna data na kartě budou ztracena."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formátovat"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Ladění přes rozhraní USB připojeno"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Vyberte, chcete-li zakázat ladění USB."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" AÁBCČDĎEÉĚFGHCHIÍJKLMNŇOÓPQRŘSŠTŤUÚVWXYÝZŽ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789AÁBCČDĎEÉĚFGHCHIÍJKLMNŇOÓPQRŘSŠTŤUÚVWXYÝZŽ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"kandidáti"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Příprava karty SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Příprava karty SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Příprava karty SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Kontrola chyb."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Prázdná karta SD"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Prázdná karta SD"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"Karta SD je prázdná nebo používá nepodporovaný systém souborů."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Karta SD je prázdná nebo obsahuje nepodporovaný systém souborů."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Poškozená karta SD"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Poškozená karta SD"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"Karta SD je poškozena. Pravděpodobně ji bude nutné znovu formátovat."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"Karta SD je poškozená. Bude pravděpodobně nutné ji přeformátovat."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Karta SD byla neočekávaně odebrána"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Karta SD byla neočekávaně odebrána"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Chcete-li zabránit ztrátě dat, kartu SD před odebráním odpojte."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Chcete-li zabránit ztrátě dat, kartu SD před odebráním odpojte."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"Kartu SD je možné bezpečně odebrat"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"Kartu SD je možné bezpečně odebrat"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"Kartu SD lze nyní bezpečně vyjmout."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Kartu SD lze bezpečně odebrat."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Karta SD byla odstraněna"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Karta SD byla odstraněna"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"Karta SD byla odebrána. Chcete-li zvětšit úložiště svého zařízení, vložte kartu SD."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"Karta SD byla odebrána. Vložte novou kartu."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Prázdná karta SD"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"Karta SD je prázdná nebo obsahuje nepodporovaný systém souborů."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Poškozená karta SD"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"Karta SD je poškozená. Bude pravděpodobně nutné ji přeformátovat."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Karta SD byla neočekávaně odebrána"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Chcete-li zabránit ztrátě dat, kartu SD před odebráním odpojte."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"Kartu SD je možné bezpečně odebrat"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Kartu SD lze bezpečně odebrat."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Karta SD byla odstraněna"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"Karta SD byla odebrána. Vložte novou kartu."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Nebyly nalezeny žádné odpovídající aktivity."</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"aktualizovat statistiku použití součástí"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Umožňuje změnu shromážděných statistických údajů o použití součástí. Není určeno pro běžné aplikace."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Síť VPN L2TP/IPSec s předsdíleným klíčem"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Síť VPN L2TP/IPSec s certifikátem"</string>
     <string name="upload_file" msgid="2897957172366730416">"Zvolit soubor"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Není vybrán žádný soubor"</string>
     <string name="reset" msgid="2448168080964209908">"Resetovat"</string>
     <string name="submit" msgid="1602335572089911941">"Odeslat"</string>
-    <string name="description_star" msgid="2654319874908576133">"oblíbené"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Aktivován režim V autě"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Vyberte, chcete-li ukončit režim Na cestě."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Je aktivní tethering nebo hotspot"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Dotykem zahájíte konfiguraci"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Zpět"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Další"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Přeskočit"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Vysoké využití mobilních dat"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Dotykem zobrazíte další informace o využití mobilních dat"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Byl překročen limit mobilních dat"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Dotykem zobrazíte další informace o využití mobilních dat"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Žádné shody"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Vyhledat na stránce"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 shoda"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> z <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 5f0c0ee..d7e3625 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Udviklingsværktøjer"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funktioner kun til programudviklere."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Lagring"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Få adgang til SD-kortet."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Få adgang til SD-kortet."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Tillader, at et program frigør plads på telefonen ved at slette filer i programmets cachemappe. Adgang er normalt meget begrænset til systemprocesser."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Flyt programressourcer"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Tillader, at et program flytter programressourcer fra interne til eksterne medier og omvendt."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"læs systemlogfiler"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Tillader, at et program læser fra systemets forskellige logfiler. Dermed kan generelle oplysninger om, hvad du laver med telefonen, registreres, men logfilerne bør ikke indeholde personlige eller private oplysninger."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"læs/skriv til ressourcer ejet af diag"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Tillader, at et program læser og skriver til alle ressourcer, der ejes af diag-gruppen, som f.eks. flier i /dev. Dette kan muligvis påvirke systemets stabilitet og sikkerhed. Dette bør KUN bruges til hardwarespecifikke diagnosticeringer foretaget af producent eller udbyder."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"aktiver eller deaktiver programkomponenter"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Tillader, at et program konfigurerer den lokale Bluetooth-telefon samt opdager og parrer med fjerne enheder."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"opret Bluetooth-forbindelser"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Tillader, at et program viser konfigurationen af den lokale Bluetooth-telefon samt opretter og accepterer forbindelse med parrede enheder."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"deaktiver tastaturlås"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Tillader, at et program deaktiverer tastaturlåsen og al associeret adgangskodesikkerhed. Et legitimt eksempel på dette er, at telefonen deaktiverer tastaturlåsen, når der modtages et indgående telefonopkald, og genaktiverer tastaturlåsen, når opkaldet er afsluttet."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"læs indstillinger for synkronisering"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Tillader, at et program læser alle private ord, navne og sætninger, som brugeren eventuelt har gemt i brugerordbogen."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"skriv til den brugerdefinerede ordbog"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Tillader, at et program skriver nye ord i brugerordbogen."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"ret/slet indholdet på SD-kortet"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"ret/slet indholdet på SD-kortet"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Tillader, at et program skriver til SD-kortet."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Tillader, at et program skriver til SD-kortet."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"ret/slet indholdet på SD-kortet"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Tillader, at et program skriver til SD-kortet."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"få adgang til cache-filsystemet"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Tillader, at et program læser og skriver til cache-filsystemet."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Begræns adgangskode"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Begræns de adgangskodetyper, du har tilladelse til at bruge."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Vis forsøg på at logge ind"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Overvåg mislykkede forsøg på at logge ind på enheden for at foretage en handling."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Nulstil adgangskode"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Tving din adgangskode til en ny værdi. Dette kræver, at administratoren giver den til dig, før du kan kogge ind."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Tvangslås"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Kontrol når enheden låses, så du skal indtaste adgangskoden igen."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Begræns adgangskode"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Begræns de adgangskodetyper, du har tilladelse til at bruge."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Vis forsøg på at logge ind"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Overvåg mislykkede forsøg på at logge ind på enheden for at foretage en handling."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Nulstil adgangskode"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Tving din adgangskode til en ny værdi. Dette kræver, at administratoren giver den til dig, før du kan kogge ind."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Tvangslås"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Kontrol når enheden låses, så du skal indtaste adgangskoden igen."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Slet alle data"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Foretag en fabriksnulstilling, der sletter alle dine data uden bekræftelse."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Foretag en fabriksnulstilling, der sletter alle dine data uden bekræftelse."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Angiv enhedens globale proxy, der skal bruges, mens politikken er aktiveret. Kun den første enhedsadministrator angiver den effektive globale proxy."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Hjem"</item>
     <item msgid="869923650527136615">"Mobil"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Indtast PIN-kode"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Indtast adgangskode for at låse op"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Indtast pinkode for at låse op"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Forkert PIN-kode!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Tryk på Menu og dernæst på 0 for at låse op."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nødnummer"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Tilbage til opkald"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Rigtigt!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Beklager! Prøv igen"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Beklager! Prøv igen"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Oplader (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Opladt."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kortet er låst."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Låser SIM-kortet op ..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Du har tegnet dit oplåsningsmønster forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. "\n\n"Prøv igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har indtastet din adgangskode forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. "\n\n"Prøv igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Du har indtastet din pinkode forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. "\n\n"Prøv igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Du har tegnet dit oplåsningsmønster forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. Efter <xliff:g id="NUMBER_1">%d</xliff:g> forsøg mere vil du blive bedt om at låse din telefon op ved hjælp af dit Google-login"\n\n" Prøv igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Prøv igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Har du glemt mønstret?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Naviger væk fra denne side?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n" Vælg OK for at fortsætte eller Annuller for at blive på den aktuelle side."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Bekræft"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Tip: Dobbeltklik for at zoome ind eller ud."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"læs browserens oversigt og bogmærker"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Tillader, at programmet læser alle de webadresser, browseren har besøgt, og alle browserens bogmærker."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skriv browserens oversigt og bogmærker"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Tillader, at et program ændrer browseroversigten eller bogmærker, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre din browsers data."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Skift browsertilladelser for geografisk placering"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Giver et program tilladelse til at ændre browserens tilladelser for geografisk placering. Skadelige programmer kan bruge dette til at tillade, at placeringsoplysninger sendes til vilkårlige websteder."</string>
     <string name="save_password_message" msgid="767344687139195790">"Ønsker du, at browseren skal huske denne adgangskode?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Vælg alle"</string>
-    <string name="selectText" msgid="4862359311088898878">"Vælg ord"</string>
     <string name="cut" msgid="3092569408438626261">"Klip"</string>
     <string name="copy" msgid="2681946229533511987">"Kopier"</string>
     <string name="paste" msgid="5629880836805036433">"Indsæt"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopier webadresse"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Marker tekst..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Tekstmarkering"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Inputmetode"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Føj \"<xliff:g id="WORD">%s</xliff:g>\" til ordbog"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Rediger tekst"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksthandlinger"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Der er ikke så meget plads tilbage"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Der er næsten ikke mere plads på telefonen."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Annuller"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Bemærk"</string>
+    <string name="loading" msgid="1760724998928255250">"Indlæser..."</string>
     <string name="capital_on" msgid="1544682755514494298">"TIL"</string>
     <string name="capital_off" msgid="6815870386972805832">"FRA"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Fuldfør handling ved hjælp af"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Programmet er omdirigeret"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> kører nu."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> blev oprindeligt åbnet."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Vis alle"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-masselagring"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB er tilsluttet"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Du har forbundet din telefon til din computer via USB. Vælg knappen nedenfor, hvis du ønsker at kopiere filer mellem din computer og din Androids SD-kort."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Du har forbundet din telefon til din computer via USB. Vælg knappen nedenfor, hvis du ønsker at kopiere filer mellem din computer og din Androids SD-kort."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Du har forbundet din telefon til din computer via USB. Vælg knappen nedenfor, hvis du ønsker at kopiere filer mellem din computer og din Androids SD-kort."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Slå USB-lagringen til"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Der opstod et problem med at bruge dit SD-kort til USB-lagring."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Der opstod et problem med at bruge dit SD-kort til USB-lagring."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Der opstod et problem med at bruge dit SD-kort til USB-lagring."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB er tilsluttet"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Vælg for at kopiere filer til/fra din computer."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Slå USB-lagringen fra"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Vælg for at slå USB-lagring fra."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-lager i brug"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Sørg for, at du har demonteret (\"udskubbet\") din Androids SD-kort fra computeren, før du slår USB-lagring fra."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Sørg for, at du har demonteret (\"udskubbet\") din Androids SD-kort fra computeren, før du slår USB-lagring fra."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Sørg for, at du har demonteret (\"udskubbet\") din Androids SD-kort fra computeren, før du slår USB-lagring fra."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Slå USB-lagring fra"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Der opstod et problem med at slå USB-lagringen fra. Sørg for, at du har demonteret USB-værten, og prøv så igen."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Slå USB-lagring til"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Hvis du slår USB-lagring til, vil nogle af de programmer, som du bruger, stoppe, og de kan være utilgængelige, indtil du slår USB-lagring til igen."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB-handlingen mislykkedes"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formater SD-kort"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formater SD-kort"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Er du sikker på, du ønsker at formatere SD-kortet? Alle data på kortet mistes."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Er du sikker på, du ønsker at formatere SD-kortet? Alle data på kortet mistes."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formater SD-kort"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Er du sikker på, du ønsker at formatere SD-kortet? Alle data på kortet mistes."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formater"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-fejlretning er tilsluttet"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Vælg for at deaktivere USB-fejlretning."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"kandidater"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Forbereder SD-kortet"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Forbereder SD-kortet"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Forbereder SD-kortet"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Kontrollerer for fejl."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Tomt SD-kort"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Tomt SD-kort"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"SD-kortet er tomt eller bruger et ikke understøttet filsystem."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SD-kortet er tomt eller har et ikke understøttet filsystem."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Beskadiget SD-kort"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Beskadiget SD-kort"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"SD-kortet er beskadiget. Du skal muligvis omformatere dit kort."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD-kortet er beskadiget. Du bliver muligvis nødt til at formatere det igen."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"SD-kortet blev fjernet uventet"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SD-kortet blev fjernet uventet"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Demonter SD-kortet inden fjernelse for at undgå tab af data."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Demonter SD-kortet inden fjernelse for at undgå tab af data."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"SD-kortet kan fjernes sikkert"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"SD-kortet kan fjernes sikkert"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"SD-kortet kan nu fjernes sikkert."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Du kan nu fjerne SD-kortet."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"SD-kortet er fjernet"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"SD-kortet er fjernet"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"SD er fjernet. Indsæt et nyt SD-kort for at øge din enheds lagerplads."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD-kortet er fjernet. Indsæt et nyt."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Tomt SD-kort"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD-kortet er tomt eller har et ikke understøttet filsystem."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Beskadiget SD-kort"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD-kortet er beskadiget. Du bliver muligvis nødt til at formatere det igen."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD-kortet blev fjernet uventet"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Demonter SD-kortet inden fjernelse for at undgå tab af data."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SD-kortet kan fjernes sikkert"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Du kan nu fjerne SD-kortet."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"SD-kortet er fjernet"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD-kortet er fjernet. Indsæt et nyt."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Der blev ikke fundet nogen matchende aktiviteter"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"opdater brugerstatistikker for komponenter"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Tillader ændring af indsamlede brugerstatistikker for komponenter. Ikke til brug til normale programmer."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN baseret på forhåndsdelt nøglekodning"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatbaseret L2TP/IPSec VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
     <string name="reset" msgid="2448168080964209908">"Nulstil"</string>
     <string name="submit" msgid="1602335572089911941">"Send"</string>
-    <string name="description_star" msgid="2654319874908576133">"favorit"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Biltilstand er aktiveret"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Vælg for at afslutte biltilstand."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Tethering eller hotspot er aktivt"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Tryk for at konfigurere"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Tilbage"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Næste"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Spring over"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Højt mobildataforbrug"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tryk for oplysninger om brug af mobildata"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Grænsen for mobildata er overskredet"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Tryk for oplysninger om brug af mobildata"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Der er ingen matches"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Find på siden"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 match"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> af <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 539bbe8..bbe8de9 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -169,12 +169,11 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Entwickler-Tools"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funktionen nur für Anwendungsentwickler vorgesehen."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Speicher"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Greift auf die SD-Karte zu."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Greift auf die SD-Karte zu."</string>
+    <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">"Ermöglicht der Anwendung, zur Statusleiste zu werden."</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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Ermöglicht einer Anwendung, Telefonspeicher durch das Löschen von Dateien im Cache-Verzeichnis der Anwendung freizugeben. Der Zugriff beschränkt sich in der Regel auf Systemprozesse."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Anwendungsressourcen verschieben"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Ermöglicht einer Anwendung, Anwendungsressourcen von interne auf externe Medien zu verschieben und umgekehrt."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"System-Protokolldateien lesen"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Ermöglicht einer Anwendung, die verschiedenen Protokolldateien des Systems zu lesen. So können allgemeine Informationen zu den auf Ihrem Telefon durchgeführten Aktionen eingesehen werden, diese sollten jedoch keine persönlichen oder geheimen Daten enthalten."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"Lese-/Schreibberechtigung für zu Diagnosegruppe gehörige Elemente"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Ermöglicht einer Anwendung, alle Elemente in der Diagnosegruppe zu lesen und zu bearbeiten, etwa Dateien in \"/dev\". Dies könnte eine potenzielle Gefährdung für die Stabilität und Sicherheit des Systems darstellen und sollte NUR für Hardware-spezifische Diagnosen des Herstellers oder Netzbetreibers verwendet werden."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"Anwendungskomponenten aktivieren oder deaktivieren"</string>
@@ -318,9 +319,9 @@
     <string name="permlab_reboot" msgid="2898560872462638242">"Neustart des Telefons erzwingen"</string>
     <string name="permdesc_reboot" msgid="7914933292815491782">"Ermöglicht der Anwendung, einen Neustart des Telefons zu erzwingen."</string>
     <string name="permlab_mount_unmount_filesystems" msgid="1761023272170956541">"Dateisysteme bereitstellen oder Bereitstellung aufheben"</string>
-    <string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Ermöglicht der Anwendung, Dateisysteme für austauschbare Datenträger bereitzustellen oder die Bereitstellung aufzuheben."</string>
+    <string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Ermöglicht der Anwendung, Dateisysteme für austauschbare Speicherplätze bereitzustellen oder die Bereitstellung aufzuheben."</string>
     <string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"Externen Speicher formatieren"</string>
-    <string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Ermöglicht der Anwendung, austauschbare Datenträger zu formatieren."</string>
+    <string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Erlaubt der Anwendung, austauschbaren Speicher zu formatieren."</string>
     <string name="permlab_asec_access" msgid="1070364079249834666">"Informationen zum sicheren Speicher abrufen"</string>
     <string name="permdesc_asec_access" msgid="7691616292170590244">"Ermöglicht der Anwendung, Informationen zum sicheren Speicher abzurufen."</string>
     <string name="permlab_asec_create" msgid="7312078032326928899">"Sicheren Speicher erstellen"</string>
@@ -336,7 +337,7 @@
     <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">"Ermöglicht der Anwendung den Zugriff auf USB-Geräte."</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>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Ermöglicht einer Anwendung, das lokale Bluetooth-Telefon zu konfigurieren, Remote-Geräte zu erkennen und eine Verbindung zu diesen herzustellen."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"Bluetooth-Verbindungen herstellen"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Ermöglicht einer Anwendung, die Konfiguration des lokalen Bluetooth-Telefons einzusehen und Verbindungen mit Partnergeräten herzustellen und zu akzeptieren."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"Tastensperre deaktivieren"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Ermöglicht einer Anwendung, die Tastensperre sowie den damit verbundenen Passwortschutz zu deaktivieren. So wird die Tastensperre vom Telefon deaktiviert, wenn ein Anruf eingeht, und nach Beendigung des Anrufs wieder aktiviert."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"Synchronisierungseinstellungen lesen"</string>
@@ -434,23 +419,24 @@
     <string name="permlab_readDictionary" msgid="432535716804748781">"nutzerdefiniertes Wörterbuch lesen"</string>
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Erlaubt einer Anwendung, alle privaten Wörter, Namen und Ausdrücke zu lesen, die ein Nutzer in seinem Wörterbuch gespeichert hat."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"in nutzerdefiniertes Wörterbuch schreiben"</string>
-    <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Ermöglicht einer Anwendung, neue Wörter in das Wörterbuch des Nutzers zu schreiben."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"SD-Karten-Inhalt ändern/löschen"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"SD-Karten-Inhalt ändern/löschen"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Ermöglicht einer Anwendung, auf die SD-Karte zu schreiben"</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Ermöglicht einer Anwendung, auf die SD-Karte zu schreiben"</string>
+    <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Erlaubt einer Anwendung, neue Wörter in das Wörterbuch des Nutzers zu schreiben."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"SD-Karten-Inhalt ändern/löschen"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Ermöglicht einer Anwendung, auf die SD-Karte zu schreiben"</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"Zugriff auf das Cache-Dateisystem"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Gewährt einer Anwendung Lese- und Schreibzugriff auf das Cache-Dateisystem."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Passwort beschränken"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Beschränken der erlaubten Passworttypen"</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Anmeldeversuche überwachen"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Fehlgeschlagene Versuche·zum Anmelden/Durchführen einer Aktion überwachen"</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Passwort zurücksetzen"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Festlegen eines neuen Werts für Ihr Passwort, sodass der Administrator es Ihnen vor dem Anmelden übermitteln muss."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Sperren erzwingen"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Steuerung der Gerätesperre; erfordert die erneute Passworteingabe"</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Passwort beschränken"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Beschränken der erlaubten Passworttypen"</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Anmeldeversuche überwachen"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Fehlgeschlagene Versuche·zum Anmelden/Durchführen einer Aktion überwachen"</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Passwort zurücksetzen"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Festlegen eines neuen Werts für Ihr Passwort, sodass der Administrator es Ihnen vor dem Anmelden übermitteln muss."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Sperren erzwingen"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Steuerung der Gerätesperre; erfordert die erneute Passworteingabe"</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Alle Daten löschen"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Zurücksetzen auf die Werkseinstellungen. Dabei werden alle Ihre Daten ohne Nachfrage gelöscht."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Zurücksetzen auf die Werkseinstellungen. Dabei werden alle Ihre Daten ohne Nachfrage gelöscht."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Den globalen Proxy des Geräts zur Verwendung während der Aktivierung der Richtlinie festlegen. Nur der erste Geräteadministrator kann den gültigen globalen Proxy festlegen."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Privat"</item>
     <item msgid="869923650527136615">"Mobil"</item>
@@ -487,7 +473,7 @@
   <string-array name="imProtocols">
     <item msgid="8595261363518459565">"AIM"</item>
     <item msgid="7390473628275490700">"Windows Live"</item>
-    <item msgid="7882877134931458217">"Yahoo!"</item>
+    <item msgid="7882877134931458217">"Yahoo"</item>
     <item msgid="5035376313200585242">"Skype"</item>
     <item msgid="7532363178459444943">"QQ"</item>
     <item msgid="3713441034299660749">"Google Talk"</item>
@@ -534,7 +520,7 @@
     <string name="imProtocolCustom" msgid="6919453836618749992">"Benutzerdefiniert"</string>
     <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string>
     <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string>
-    <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo!"</string>
+    <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string>
     <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string>
     <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string>
     <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"über <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> über <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-Code eingeben"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Passwort zum Entsperren eingeben"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"PIN zum Entsperren eingeben"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Falscher PIN-Code!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Drücken Sie zum Entsperren die Menütaste und dann auf \"0\"."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Notrufnummer"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Zurück zum Anruf"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Korrekt!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Tut uns leid. Versuchen Sie es noch einmal."</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Tut uns leid. Versuchen Sie es noch einmal."</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Wird geladen (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Aufgeladen"</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Bitte PIN-Code eingeben"</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM-Karte wird entsperrt..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Sie haben Ihr Entsperrungsmuster <xliff:g id="NUMBER_0">%d</xliff:g>-mal falsch gezeichnet. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_1">%d</xliff:g> Sekunden erneut."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Sie haben Ihr Passwort <xliff:g id="NUMBER_0">%d</xliff:g> Mal falsch eingegeben. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_1">%d</xliff:g> Sekunden erneut."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Sie haben Ihre PIN <xliff:g id="NUMBER_0">%d</xliff:g> Mal falsch eingegeben. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_1">%d</xliff:g> Sekunden erneut."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Sie haben Ihr Entsperrungsmuster <xliff:g id="NUMBER_0">%d</xliff:g>-mal falsch gezeichnet. Nach <xliff:g id="NUMBER_1">%d</xliff:g> weiteren erfolglosen Versuchen werden Sie aufgefordert, Ihr Telefon mithilfe Ihrer Google-Anmeldeinformationen zu entsperren. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_2">%d</xliff:g> Sekunden erneut."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Versuchen Sie es in <xliff:g id="NUMBER">%d</xliff:g> Sekunden erneut."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Muster vergessen?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Von dieser Seite navigieren?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Wählen Sie \"OK\", um fortzufahren, oder wählen Sie \"Abbrechen\", um auf der aktuellen Seite zu bleiben."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Bestätigen"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Tipp: Zum Heranzoomen und Vergrößern zweimal tippen"</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"Browserverlauf und Lesezeichen lesen"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Ermöglicht der Anwendung, alle URLs, die mit dem Browser besucht wurden, sowie alle Lesezeichen des Browsers zu lesen."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"Browserverlauf und Lesezeichen schreiben"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Ermöglicht einer Anwendung, den auf Ihrem Telefon gespeicherten Browserverlauf und die Lesezeichen zu ändern. Schädliche Anwendungen können so Ihre Browserdaten löschen oder ändern."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Geolokalisierungsberechtigungen des Browsers ändern"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Ermöglicht einer Anwendung, die Geolokalisierungsberechtigungen des Browsers zu ändern. Schädliche Anwendungen können dies nutzen, um das Senden von Standortinformationen an willkürliche Websites zuzulassen."</string>
     <string name="save_password_message" msgid="767344687139195790">"Möchten Sie, dass der Browser dieses Passwort speichert?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Alles auswählen"</string>
-    <string name="selectText" msgid="4862359311088898878">"Wort auswählen"</string>
     <string name="cut" msgid="3092569408438626261">"Ausschneiden"</string>
     <string name="copy" msgid="2681946229533511987">"Kopieren"</string>
     <string name="paste" msgid="5629880836805036433">"Einfügen"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL kopieren"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Text auswählen..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Textauswahl"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Eingabemethode"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"\"<xliff:g id="WORD">%s</xliff:g>\" zum Wörterbuch hinzufügen"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Text bearbeiten"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Textaktionen"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Geringer Speicher"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Kaum noch freier Telefonspeicher verfügbar."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Abbrechen"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Achtung"</string>
+    <string name="loading" msgid="1760724998928255250">"Wird geladen..."</string>
     <string name="capital_on" msgid="1544682755514494298">"EIN"</string>
     <string name="capital_off" msgid="6815870386972805832">"AUS"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Aktion durchführen mit"</string>
@@ -763,19 +750,22 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Anwendung umgeleitet"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> wird jetzt ausgeführt."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> wurde ursprünglich gestartet."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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">"Auswählen 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">"Es läuft gerade eine andere Anwendung, die vor dem Start einer neuen beendet werden muss."</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">"Die neue Anwendung nicht starten."</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">"Anwendung beenden, ohne zu speichern."</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>
@@ -812,58 +802,42 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Alle anzeigen"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-Massenspeicher"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB-Verbindung"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Sie haben Ihr Telefon über USB mit Ihrem Computer verbunden. Wählen Sie die Schaltfläche unten aus, wenn Sie Dateien auf Ihren Computer oder die SD-Karte Ihres Android-Geräts kopieren möchten."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Sie haben Ihr Telefon über USB mit Ihrem Computer verbunden. Wählen Sie die Schaltfläche unten aus, wenn Sie Dateien auf Ihren Computer oder die SD-Karte Ihres Android-Geräts kopieren möchten."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Sie haben Ihr Telefon über USB mit Ihrem Computer verbunden. Wählen Sie die Schaltfläche unten aus, wenn Sie Dateien auf Ihren Computer oder die SD-Karte Ihres Android-Geräts kopieren möchten."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"USB-Speicher aktivieren"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Bei der Verwendung Ihrer SD-Karte als USB-Speicher ist ein Problem aufgetreten."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Bei der Verwendung Ihrer SD-Karte als USB-Speicher ist ein Problem aufgetreten."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Bei der Verwendung Ihrer SD-Karte als USB-Speicher ist ein Problem aufgetreten."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB-Verbindung"</string>
-    <string name="usb_storage_notification_message" msgid="7380082404288219341">"Zum Kopieren von Dateien zum/vom Computer"</string>
+    <string name="usb_storage_notification_message" msgid="7380082404288219341">"Zum Kopieren von Dateien zu/von Ihrem Computer."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USB-Speicher deaktivieren"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Auswählen, um USB-Speicher zu deaktivieren."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-Speicher in Verwendung"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Stellen Sie vor dem Deaktivieren des USB-Speichers sicher, dass Sie Ihre Android-SD-Karte von Ihrem Computer getrennt (\"ausgeworfen\") haben."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Stellen Sie vor dem Deaktivieren des USB-Speichers sicher, dass Sie Ihre Android-SD-Karte von Ihrem Computer getrennt (\"ausgeworfen\") haben."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Stellen Sie vor dem Deaktivieren des USB-Speichers sicher, dass Sie Ihre Android-SD-Karte von Ihrem Computer getrennt (\"ausgeworfen\") haben."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USB-Speicher deaktivieren"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Beim Deaktivieren des USB-Speichers ist ein Problem aufgetreten. Überprüfen Sie, ob Sie den USB-Host getrennt haben, und versuchen Sie es erneut."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USB-Speicher aktivieren"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Wenn Sie den USB-Speicher aktivieren, werden einige von Ihnen verwendete Anwendungen angehalten und sind möglicherweise nicht verfügbar, bis Sie den USB-Speicher wieder deaktivieren."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB-Vorgang fehlgeschlagen"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"SD-Karte formatieren"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"SD-Karte formatieren"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Möchten Sie die SD-Karte wirklich formatieren? Alle Daten auf Ihrer Karte gehen dann verloren."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Möchten Sie die SD-Karte wirklich formatieren? Alle Daten auf Ihrer Karte gehen dann verloren."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"SD-Karte formatieren"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Möchten Sie die SD-Karte wirklich formatieren? Alle Daten auf Ihrer Karte gehen dann verloren."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-Debugging verbunden"</string>
-    <string name="adb_active_notification_message" msgid="8470296818270110396">"Auswählen, um USB-Debugging zu deaktivieren."</string>
+    <string name="adb_active_notification_message" msgid="8470296818270110396">"Zum Deaktivieren des USB-Debugging auswählen"</string>
     <string name="select_input_method" msgid="6865512749462072765">"Eingabemethode auswählen"</string>
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"Kandidaten"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"SD-Karte wird vorbereitet"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"SD-Karte wird vorbereitet"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"SD-Karte wird vorbereitet"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Suche nach Fehlern"</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"SD-Karte leer"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"SD-Karte leer"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"Die SD-Karte ist leer oder verwendet ein Dateisystem, das nicht unterstützt wird."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SD-Karte ist leer oder verfügt über ein nicht unterstütztes Dateisystem."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Beschädigte SD-Karte"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Beschädigte SD-Karte"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"Die SD-Karte ist beschädigt. Sie müssen Ihre Karte eventuell neu formatieren."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"Die SD-Karte ist beschädigt. Sie müssen Ihre Karte eventuell neu formatieren."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"SD-Karte unerwartet entfernt"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SD-Karte unerwartet entfernt"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"SD-Karte vor dem Entnehmen trennen, um Datenverlust zu vermeiden."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"SD-Karte vor dem Entnehmen trennen, um Datenverlust zu vermeiden."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"SD-Karte kann entfernt werden."</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"SD-Karte kann entfernt werden."</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"Die SD-Karte kann jetzt entfernt werden."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Die SD-Karte kann entfernt werden."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"SD-Karte entfernt"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"SD-Karte entfernt"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"Die SD-Karte wurde entfernt. Legen Sie eine neue SD-Karte ein, um den Speicherplatz Ihres Geräts zu erweitern."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD-Karte entfernt. Legen Sie eine neue ein."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"SD-Karte leer"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD-Karte ist leer oder verfügt über ein nicht unterstütztes Dateisystem."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Beschädigte SD-Karte"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"Die SD-Karte ist beschädigt. Sie müssen Ihre Karte eventuell neu formatieren."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD-Karte unerwartet entfernt"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"SD-Karte vor dem Entnehmen trennen, um Datenverlust zu vermeiden."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SD-Karte\nkann entfernt werden."</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Die SD-Karte kann entfernt werden."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"SD-Karte entfernt"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD-Karte entfernt. Legen Sie eine neue ein."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Keine passenden Aktivitäten gefunden"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"Nutzungsstatistik der Komponente aktualisieren"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Ermöglicht die Änderung von gesammelten Nutzungsstatistiken der Komponente. Nicht für normale Anwendungen vorgesehen."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec-VPN mit vorinstalliertem Schlüssel"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Zertifikat mit vorinstalliertem Schlüssel"</string>
     <string name="upload_file" msgid="2897957172366730416">"Datei auswählen"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Keine Datei ausgewählt"</string>
     <string name="reset" msgid="2448168080964209908">"Zurücksetzen"</string>
     <string name="submit" msgid="1602335572089911941">"Senden"</string>
-    <string name="description_star" msgid="2654319874908576133">"Favorit"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Automodus aktiviert"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Zum Beenden des Automodus auswählen"</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Tethering oder Hotspot aktiv"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Zum Konfigurieren berühren"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Zurück"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Weiter"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Überspringen"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Hohe Mobildatennutzung"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Weitere Informationen über die Mobildatennutzung durch Berühren aufrufen"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Mobildatenlimit überschritten"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Weitere Informationen über die Mobildatennutzung durch Berühren aufrufen"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Keine Übereinstimmungen"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Auf Seite suchen"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 Übereinstimmung"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> von <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index ae51c55..e2a01f4 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Εργαλεία ανάπτυξης"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Δυνατότητες που είναι απαραίτητες μόνο σε προγραμματιστές εφαρμογών."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Αποθηκευτικός χώρος"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Πρόσβαση στην κάρτα SD."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Πρόσβαση στην κάρτα SD."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Επιτρέπει σε μια εφαρμογή να αυξήσει τον ελεύθερο χώρο αποθήκευσης του τηλεφώνου διαγράφοντας αρχεία από τον κατάλογο προσωρινής μνήμης της εφαρμογής. Η πρόσβαση είναι συνήθως πολύ περιορισμένη στη διαδικασία συστήματος."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Μετακίνηση πόρων εφαρμογής"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Επιτρέπει σε μια εφαρμογή τη μετακίνηση πόρων εφαρμογής από ένα εσωτερικό σε ένα εξωτερικό μέσο και αντίστροφα."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"ανάγνωση αρχείων καταγραφής συστήματος"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Επιτρέπει σε μια εφαρμογή να αναγνώσει τα αρχεία καταγραφής του συστήματος. Έτσι μπορεί να ανακαλύψει γενικές πληροφορίες σχετικά με τις δραστηριότητές σας στο τηλέφωνο, όμως δεν θα πρέπει να περιέχουν προσωπικές ή ιδιωτικές πληροφορίες."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"ανάγνωση/εγγραφή σε πόρους που ανήκουν στο διαγνωστικό"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Επιτρέπει σε μια εφαρμογή την ανάγνωση και την εγγραφή σε πόρο που ανήκει στην ομάδα διαγνωστικού (π.χ. αρχεία στον κατάλογο /dev). Αυτό ενδέχεται να επηρεάσει την σταθερότητα και την ασφάλεια του συστήματος. Θα πρέπει να χρησιμοποιείται ΜΟΝΟ για διαγνωστικά υλικού του κατασκευαστή ή του χειριστή."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"ενεργοποίηση ή απενεργοποίηση στοιχείων εφαρμογής"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Επιτρέπει σε μια εφαρμογή τη διαμόρφωση του τοπικού τηλεφώνου Bluetooth και την ανακάλυψη και σύζευξη με απομακρυσμένες συσκευές."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"δημιουργία συνδέσεων Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Επιτρέπει σε μια εφαρμογή να προβάλει τη διαμόρφωση του τοπικού τηλεφώνου Bluetooth και επίσης να πραγματοποιεί και να αποδέχεται συνδέσεις με συζευγμένες συσκευές."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"απενεργοποίηση κλειδώματος πληκτρολογίου"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Επιτρέπει σε μια εφαρμογή την απενεργοποίηση του κλειδώματος πληκτρολογίου και άλλης σχετικής ασφάλειας με κωδικό πρόσβασης. Για παράδειγμα, η απενεργοποίηση του κλειδώματος πληκτρολογίου όταν λαμβάνεται εισερχόμενη τηλεφωνική κλήση και η επανενεργοποίηση του κλειδώματος πληκτρολογίου όταν η κλήση τερματιστεί."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"ανάγνωση ρυθμίσεων συγχρονισμού"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Επιτρέπει σε μια εφαρμογή να αναγνώσει ιδιωτικές λέξεις και φράσεις και ιδιωτικά ονόματα, τα οποία ο χρήστης ενδέχεται να έχει αποθηκεύσει στο λεξικό χρήστη."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"εγγραφή σε καθορισμένο από τον χρήστη λεξικό"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Επιτρέπει σε μια εφαρμογή την εγγραφή νέων λέξεων στο λεξικό χρήστη."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"τροποποίηση/διαγραφή περιεχομένων κάρτας SD"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"τροποποίηση/διαγραφή περιεχομένων κάρτας SD"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Επιτρέπει στην εφαρμογή την εγγραφή στην κάρτα SD."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Επιτρέπει στην εφαρμογή την εγγραφή στην κάρτα SD."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"τροποποίηση/διαγραφή περιεχομένων κάρτας SD"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Επιτρέπει στην εφαρμογή την εγγραφή στην κάρτα SD."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"πρόσβαση στο σύστημα αρχείων προσωρινής μνήμης"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Επιτρέπει σε μια εφαρμογή την ανάγνωση και την εγγραφή του συστήματος αρχείων προσωρινής μνήμης."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Περιορισμός επιλογών κωδικού πρόσβασης"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Περιορισμός των τύπων κωδικού πρόσβασης που επιτρέπεται να χρησιμοποιείτε."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Παρακολούθηση προσπαθειών σύνδεσης"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Παρακολούθηση αποτυχημένων προσπαθειών σύνδεσης με τη συσκευή, για την εκτέλεσης κάποιας ενέργειας."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Επαναφορά κωδικού πρόσβασης"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Εφαρμογή του κωδικού πρόσβασής σας σε μια νέα τιμή, με την προϋπόθεση ότι σας παρέχεται από τον διαχειριστή για να μπορείτε να συνδεθείτε."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Εφαρμογή κλειδώματος"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Ελέγχει πότε κλειδώνει η συσκευή, απαιτώντας κωδικό πρόσβασης για επανείσοδο."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Περιορισμός επιλογών κωδικού πρόσβασης"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Περιορισμός των τύπων κωδικού πρόσβασης που επιτρέπεται να χρησιμοποιείτε."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Παρακολούθηση προσπαθειών σύνδεσης"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Παρακολούθηση αποτυχημένων προσπαθειών σύνδεσης με τη συσκευή, για την εκτέλεσης κάποιας ενέργειας."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Επαναφορά κωδικού πρόσβασης"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Εφαρμογή του κωδικού πρόσβασής σας σε μια νέα τιμή, με την προϋπόθεση ότι σας παρέχεται από τον διαχειριστή για να μπορείτε να συνδεθείτε."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Εφαρμογή κλειδώματος"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Ελέγχει πότε κλειδώνει η συσκευή, απαιτώντας κωδικό πρόσβασης για επανείσοδο."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Διαγραφή όλων των δεδομένων"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Πραγματοποιείται επαναφορά εργοστασιακών ρυθμίσεων, με τη διαγραφή όλων των δεδομένων σας χωρίς επιβεβαίωση."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Πραγματοποιείται επαναφορά εργοστασιακών ρυθμίσεων, με τη διαγραφή όλων των δεδομένων σας χωρίς επιβεβαίωση."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Ορίστε τη χρήση του γενικού διακομιστή μεσολάβησης της συσκευής όταν είναι ενεργοποιημένη η πολιτική. Μόνο ο διαχειριστής της πρώτης συσκευής ορίζει τον ισχύοντα γενικό διακομιστή μεσολάβησης."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Οικία"</item>
     <item msgid="869923650527136615">"Κινητό"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"μέσω <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> μέσω <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Πληκτρολογήστε τον κωδικό αριθμό PIN"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Εισαγάγετε τον κωδικό πρόσβασης για ξεκλείδωμα"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Εισαγάγετε το PIN για ξεκλείδωμα"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Εσφαλμένος κωδικός αριθμός PIN!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Για ξεκλείδωμα, πατήστε το πλήκτρο Menu και, στη συνέχεια, το πλήκτρο 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Αριθμός έκτακτης ανάγκης"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Επιστροφή στην κλήση"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Σωστό!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Προσπαθήστε αργότερα"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Προσπαθήστε αργότερα"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Φόρτιση (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Φορτίστηκε."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,7 +568,9 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Η κάρτα SIM είναι κλειδωμένη."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Ξεκλείδωμα κάρτας SIM..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Σχεδιάσατε εσφαλμένα το μοτίβο ξεκλειδώματος<xliff:g id="NUMBER_0">%d</xliff:g> φορές. "\n\n"Προσπαθήστε ξανά σε <xliff:g id="NUMBER_1">%d</xliff:g> δευτερόλεπτα."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Σχεδιάσατε το μοτίβο ξεκλειδώματος εσφαλμένα <xliff:g id="NUMBER_0">%d</xliff:g> φορές. Μετά από <xliff:g id="NUMBER_1">%d</xliff:g> ανεπιτυχείς προσπάθειες ακόμη, θα σας ζητηθεί να ξεκλειδώσετε το τηλέφωνό σας με τη χρήση της σύνδεσής σας Google."\n\n" Προσπαθήστε ξανά σε <xliff:g id="NUMBER_2">%d</xliff:g> δευτερόλεπτα."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Καταχωρίσατε εσφαλμένα τον κωδικό πρόσβασης <xliff:g id="NUMBER_0">%d</xliff:g> φορές. "\n\n"Προσπαθήστε ξανά σε <xliff:g id="NUMBER_1">%d</xliff:g> δευτερόλεπτα."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Καταχωρίσατε εσφαλμένα το PIN σας<xliff:g id="NUMBER_0">%d</xliff:g> φορές. "\n\n"Προσπαθήστε ξανά σε <xliff:g id="NUMBER_1">%d</xliff:g> δευτερόλεπτα."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Σχεδιάσατε το μοτίβο ξεκλειδώματος εσφαλμένα <xliff:g id="NUMBER_0">%d</xliff:g> φορές. Μετά από <xliff:g id="NUMBER_1">%d</xliff:g> ανεπιτυχείς προσπάθειες ακόμη, θα σας ζητηθεί να ξεκλειδώσετε το τηλέφωνό σας με τη χρήση της σύνδεσής σας Google."\n\n" Προσπαθήστε ξανά σε <xliff:g id="NUMBER_2">%d</xliff:g> \nδευτερόλεπτα."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Προσπαθήστε ξανά σε <xliff:g id="NUMBER">%d</xliff:g> δευτερόλεπτα."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Ξεχάσατε το μοτίβο;"</string>
     <string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Ξεκλείδωμα λογαριασμού"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Απομάκρυνση από αυτή τη σελίδα;"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Επιλέξτε OK για συνέχεια, ή Ακύρωση για παραμονή στην τρέχουσα σελίδα."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Επιβεβαίωση"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Συμβουλή: διπλό άγγιγμα για μεγέθυνση και σμίκρυνση."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"ανάγνωση ιστορικού και σελιδοδεικτών προγράμματος περιήγησης"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Επιτρέπει στην εφαρμογή την ανάγνωση όλων των διευθύνσεων URL που το πρόγραμμα περιήγησης έχει επισκεφθεί και όλων των σελιδοδεικτών του προγράμματος περιήγησης."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"εγγραφή ιστορικού και σελιδοδεικτών προγράμματος περιήγησης"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Επιτρέπει σε μια εφαρμογή να τροποποιήσει το ιστορικό ή τους σελιδοδείκτες του προγράμματος περιήγησης που βρίσκονται αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να διαγράψουν ή να τροποποιήσουν τα δεδομένα του προγράμματος περιήγησης."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Τροποποίηση δικαιωμάτων γεωγραφικής θέσης προγράμματος περιήγησης"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Επιτρέπει σε μια εφαρμογή την τροποποίηση των δικαιωμάτων γεωγραφικής θέσης του προγράμματος περιήγησης. Οι κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να επιτρέψουν την αποστολή στοιχείων τοποθεσίας σε αυθαίρετους ιστότοπους."</string>
     <string name="save_password_message" msgid="767344687139195790">"Θέλετε το πρόγραμμα περιήγησης να διατηρήσει αυτόν τον κωδικό πρόσβασης;"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Επιλογή όλων"</string>
-    <string name="selectText" msgid="4862359311088898878">"Επιλογή λέξης"</string>
     <string name="cut" msgid="3092569408438626261">"Αποκοπή"</string>
     <string name="copy" msgid="2681946229533511987">"Αντιγραφή"</string>
     <string name="paste" msgid="5629880836805036433">"Επικόλληση"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Αντιγραφή διεύθυνσης URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Επιλογή κειμένου..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Επιλογή κειμένου"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Μέθοδος εισόδου"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Προσθήκη της λέξης \"<xliff:g id="WORD">%s</xliff:g>\" στο λεξικό"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Επεξεργασία κειμένου"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Ενέργειες κειμένου"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Απομένει λίγος ελεύθερος χώρος"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Έχει απομείνει λίγος ελεύθερος αποθηκευτικός χώρος στο τηλέφωνο."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Ακύρωση"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Προσοχή"</string>
+    <string name="loading" msgid="1760724998928255250">"Φόρτωση..."</string>
     <string name="capital_on" msgid="1544682755514494298">"Ενεργοποιημένο"</string>
     <string name="capital_off" msgid="6815870386972805832">"Απενεργοποίηση"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Ολοκλήρωση ενέργειας με τη χρήση"</string>
@@ -763,9 +750,12 @@
     <string name="force_close" msgid="3653416315450806396">"Αναγκαστικό κλείσιμο"</string>
     <string name="report" msgid="4060218260984795706">"Αναφορά"</string>
     <string name="wait" msgid="7147118217226317732">"Αναμονή"</string>
-    <string name="launch_warning_title" msgid="8323761616052121936">"Ανακατεύθυνση εφαρμογής"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> εκτελείται τώρα."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"Έγινε εκκίνηση πρώτα της εφαρμογής <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Εμφάνιση όλων"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"Μαζική αποθήκευση USB"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"Το USB είναι συνδεδεμένο"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Συνδέσατε το τηλέφωνό σας στον υπολογιστή μέσω USB. Επιλέξτε το παρακάτω κουμπί αν θέλετε να αντιγράψετε αρχεία μεταξύ του υπολογιστή και της κάρτας SD του Android."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Συνδέσατε το τηλέφωνό σας στον υπολογιστή μέσω USB. Επιλέξτε το παρακάτω κουμπί αν θέλετε να αντιγράψετε αρχεία μεταξύ του υπολογιστή και της κάρτας SD του Android."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Συνδέσατε το τηλέφωνό σας στον υπολογιστή μέσω USB. Επιλέξτε το παρακάτω κουμπί αν θέλετε να αντιγράψετε αρχεία μεταξύ του υπολογιστή και της κάρτας SD του Android."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Ενεργοποίηση αποθηκευτικού χώρου USB"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Παρουσιάστηκε ένα πρόβλημα στη χρήση της κάρτας SD ως αποθηκευτικό χώρο USB."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Παρουσιάστηκε ένα πρόβλημα στη χρήση της κάρτας SD ως αποθηκευτικό χώρο USB."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Παρουσιάστηκε ένα πρόβλημα στη χρήση της κάρτας SD ως αποθηκευτικό χώρο USB."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"Το USB είναι συνδεδεμένο"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Επιλέξτε για αντιγραφή προς/από τον υπολογιστή σας."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Απενεργοποίηση αποθηκευτικού χώρου USB"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Επιλογή για απενεργοποίηση αποθηκευτικού χώρου USB."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"Χώρος αποθήκευσης USB σε χρήση"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Προτού απενεργοποιήσετε το χώρο αποθήκευσης USB, βεβαιωθείτε ότι έχετε αποσυνδέσει (“αφαιρέσει”) την κάρτα SD του Android από τον υπολογιστή σας."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Προτού απενεργοποιήσετε το χώρο αποθήκευσης USB, βεβαιωθείτε ότι έχετε αποσυνδέσει (“αφαιρέσει”) την κάρτα SD του Android από τον υπολογιστή σας."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Προτού απενεργοποιήσετε το χώρο αποθήκευσης USB, βεβαιωθείτε ότι έχετε αποσυνδέσει (“αφαιρέσει”) την κάρτα SD του Android από τον υπολογιστή σας."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Απενεργοποίηση χώρου αποθήκευσης USB"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Παρουσιάστηκε πρόβλημα κατά την απενεργοποίηση του αποθηκευτικού χώρου USB. Βεβαιωθείτε ότι έχετε αφαιρέσει την υποδοχή USB και προσπαθήστε ξανά."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Ενεργοποίηση αποθηκευτικού χώρου USB"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Εάν ενεργοποιήσετε τον αποθηκευτικό χώρο USB, ορισμένες από τις εφαρμογές που χρησιμοποιείτε θα σταματήσουν και ενδέχεται να μην είναι διαθέσιμες μέχρι να απενεργοποιήσετε τον αποθηκευτικό χώρο USB."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"Απέτυχε η λειτουργία USB"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"ΟΚ"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Διαμόρφωση κάρτας SD"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Διαμόρφωση κάρτας SD"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Είστε βέβαιοι ότι θέλετε να διαμορφώσετε την κάρτα SD; Όλα τα δεδομένα στην κάρτα σας θα χαθούν."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Είστε βέβαιοι ότι θέλετε να διαμορφώσετε την κάρτα SD; Όλα τα δεδομένα στην κάρτα σας θα χαθούν."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Διαμόρφωση κάρτας SD"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Είστε βέβαιοι ότι θέλετε να διαμορφώσετε την κάρτα SD; Όλα τα δεδομένα στην κάρτα σας θα χαθούν."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Διαμόρφωση"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Συνδέθηκε ο εντοπισμός σφαλμάτων USB"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Επιλογή για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"υποψήφιοι"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Προετοιμασία κάρτας SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Προετοιμασία κάρτας SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Προετοιμασία κάρτας SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Έλεγχος για σφάλματα."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Κενή κάρτα SD"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Κενή κάρτα SD"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"Η κάρτα SD είναι κενή ή χρησιμοποιεί σύστημα αρχείων που δεν υποστηρίζεται."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Η κάρτα SD είναι κενή ή έχει μη υποστηριζόμενο σύστημα αρχείων."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Κατεστραμμένη κάρτα SD"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Κατεστραμμένη κάρτα SD"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"Η κάρτα SD έχει υποστεί βλάβη. Ενδέχεται να χρειαστεί αναδιαμόρφωση της κάρτας σας."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"Η κάρτα SD παρουσιάζει βλάβη. Ενδεχομένως θα πρέπει να προβείτε σε διαμόρφωσή της."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Μη αναμενόμενη αφαίρεση κάρτας SD"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Μη αναμενόμενη αφαίρεση κάρτας SD"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Αποπροσαρτήστε την κάρτα SD πριν την αφαιρέσετε για την αποφυγή απώλειας δεδομένων."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Αποπροσαρτήστε την κάρτα SD πριν την αφαιρέσετε για την αποφυγή απώλειας δεδομένων."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"Η κάρτα SD μπορεί να αφαιρεθεί με ασφάλεια"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"Η κάρτα SD μπορεί να αφαιρεθεί με ασφάλεια"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"Τώρα μπορείτε να αφαιρέσετε με ασφάλεια την κάρτα SD."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Μπορείτε να αφαιρέσετε με ασφάλεια της κάρτα SD."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Η κάρτα SD αφαιρέθηκε"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Η κάρτα SD αφαιρέθηκε"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"Η κάρτα SD αφαιρέθηκε. Εισαγάγετε μια νέα κάρτα SD για να αυξήσετε τον αποθηκευτικό χώρο της συσκευής σας."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"Αφαιρέθηκε η κάρτα SD. Τοποθετήστε μια νέα κάρτα."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Κενή κάρτα SD"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"Η κάρτα SD είναι κενή ή έχει μη υποστηριζόμενο σύστημα αρχείων."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Κατεστραμμένη κάρτα SD"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"Η κάρτα SD παρουσιάζει βλάβη. Ενδεχομένως θα πρέπει να προβείτε σε διαμόρφωσή της."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Μη αναμενόμενη αφαίρεση κάρτας SD"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Αποπροσαρτήστε την κάρτα SD πριν την αφαιρέσετε για την αποφυγή απώλειας δεδομένων."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"Η κάρτα SD μπορεί να αφαιρεθεί με ασφάλεια"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Μπορείτε να αφαιρέσετε με ασφάλεια της κάρτα SD."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Η κάρτα SD αφαιρέθηκε"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"Αφαιρέθηκε η κάρτα SD. Τοποθετήστε μια νέα κάρτα."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Δεν βρέθηκαν δραστηριότητες που να αντιστοιχούν"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"ενημέρωση στατιστικών χρήσης στοιχείου"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Επιτρέπει την τροποποίηση στατιστικών χρήσης στοιχείων που έχουν συλλεχθεί. Δεν πρέπει να χρησιμοποιείται από κανονικές εφαρμογές."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Κλειδί pre-shared βάσει L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Πιστοποιητικό βάσει L2TP/IPSec VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"Επιλογή αρχείου"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Δεν έχει επιλεγεί αρχείο"</string>
     <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="tethered_notification_title" msgid="3146694234398202601">"Σύνδεση μέσω κινητής συσκευής ή σημείου πρόσβασης ενεργή"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Αγγίξτε για να γίνει διαμόρφωση"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Πίσω"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Επόμενο"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Παράβλεψη"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Υψηλή χρήση δεδομένων κινητής τηλεφωνίας"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Αγγίξτε για να μάθετε περισσότερα σχετικά με τη χρήση δεδομένων κινητής τηλεφωνίας"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Ξεπεράστηκε το όριο δεδομένων κινητής τηλεφωνίας"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Αγγίξτε για να μάθετε περισσότερα σχετικά με τη χρήση δεδομένων κινητής τηλεφωνίας"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Δεν υπάρχουν αποτελέσματα"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Εύρεση στη σελίδα"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 αποτέλεσμα"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> από <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index d9d1c9b..fe27b95 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Herramientas de desarrollo"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Las funciones sólo son necesarias para los desarrolladores de aplicaciones."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Espacio de almacenamiento"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Acceder a la tarjeta SD."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Acceder a la tarjeta SD."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Admite una aplicación que libera espacio de almacenamiento en el teléfono al eliminar archivos del directorio de memoria caché de la aplicación. En general, el acceso es muy restringido para el proceso del sistema."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos de la aplicación"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Permite a una aplicación mover recursos de aplicación de medios internos a externos y viceversa."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"leer archivos de registro del sistema"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Admite una aplicación que lee diversos archivos de registro del sistema. Esto le permite descubrir información general sobre lo que haces con el teléfono, pero no debe contener información personal ni privada."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"leer y escribir a recursos dentro del grupo de diagnóstico"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Admite una aplicación que lee y escribe a cualquier recurso dentro del grupo de diagnóstico; por ejemplo, archivos con /dev. Esto puede afectar potencialmente la estabilidad y la seguridad del sistema. Debe utilizarlo SÓLO el fabricante o el operador en los diagnósticos específicos del hardware."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"activar o desactivar componentes de la aplicación"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Admite una aplicación que configura el teléfono Bluetooth local y descubre y se vincula con dispositivos remotos."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"crear conexiones de Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Admite una aplicación que ve la configuración del teléfono Bluetooth local, y realiza y acepta conexiones con dispositivos vinculados."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"desactivar el bloqueo"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Admite una aplicación que desactiva el bloqueo y cualquier seguridad con contraseña relacionada. Un ejemplo legítimo de esto es el bloqueo desactivado por el teléfono cuando recibe una llamada telefónica entrante, y luego la reactivación del bloqueo cuando finaliza la llamada."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"leer la configuración de sincronización"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Admite una aplicación para leer palabras, nombres y frases privadas que posiblemente el usuario haya almacenado en el diccionario del usuario."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"escribir al diccionario definido por el usuario"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Admite una aplicación que escribe palabras nuevas en el diccionario del usuario."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"modificar/suprimir el contenido de la tarjeta SD"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"modificar/suprimir el contenido de la tarjeta SD"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Admite que una aplicación escriba en la tarjeta SD."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Admite que una aplicación escriba en la tarjeta SD."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/suprimir el contenido de la tarjeta SD"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Admite que una aplicación escriba en la tarjeta SD."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"Acceder al sistema de archivos caché"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permite que una aplicación lea y escriba el sistema de archivos caché."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Limitar la contraseña"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Restringe los tipos de contraseñas que puedes utilizar."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Observar los intentos de acceso"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Intentos fallidos del control para acceder al dispositivo para realizar alguna acción."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Restablecer contraseña"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Forzar un nuevo valor para tu contraseña, el administrador deberá enviártelo antes de poder acceder."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Provocar el bloqueo"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Controlar cuando se bloquee el dispositivo, requiere que vuelvas a ingresar tu contraseña."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Limitar la contraseña"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Restringe los tipos de contraseñas que puedes utilizar."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Observar los intentos de acceso"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Intentos fallidos del control para acceder al dispositivo para realizar alguna acción."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Restablecer contraseña"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Forzar un nuevo valor para tu contraseña, el administrador deberá enviártelo antes de poder acceder."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Provocar el bloqueo"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Controlar cuando se bloquee el dispositivo, requiere que vuelvas a ingresar tu contraseña."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Borrar todos los datos"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Realizar un reestablecimiento de fábrica y borrar todos tus datos sin ninguna confirmación."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Realizar un reestablecimiento de fábrica y borrar todos tus datos sin ninguna confirmación."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Configuración del proxy global de dispositivo que se utilizará mientras se habilita la política. Sólo la primera administración de dispositivo configura el proxy global efectivo."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Casa"</item>
     <item msgid="869923650527136615">"Celular"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ingresar el código de PIN"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Ingresar la contraseña para desbloquear"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Ingresa el PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"¡Código de PIN incorrecto!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, presiona el menú y luego 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergencia"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Regresar a llamada"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Lo sentimos, vuelve a intentarlo"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Lo sentimos, vuelve a intentarlo"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Cargada."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"Segmento <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"La tarjeta SIM está bloqueada."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Desbloqueando tarjeta SIM…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Has extraído incorrectamente tu patrón de desbloqueo <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Vuelve a intentarlo en <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Has ingresado tu contraseña de manera incorrecta <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Vuelve a intentarlo en <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Has ingresado tu PIN de manera incorrecta <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Vuelve a intentarlo en <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Has extraído incorrectamente tu patrón de desbloqueo <xliff:g id="NUMBER_0">%d</xliff:g> veces. Luego de <xliff:g id="NUMBER_1">%d</xliff:g> intentos incorrectos, se te solicitará que desbloquees tu teléfono al iniciar sesión en Google. "\n\n" Vuelve a intentarlo en <xliff:g id="NUMBER_2">%d</xliff:g> segundos."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Vuelve a intentarlo en <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"¿Olvidaste el patrón?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"¿Deseas salir de esta página?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Selecciona Aceptar para continuar o Cancelar para permanecer en la página actual."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Sugerencia: presiona dos veces para acercar y alejar"</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"leer historial y marcadores del navegador"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite a la aplicación leer todas las URL que ha visitado el navegador y todos los marcadores del navegador."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"escribir historial y marcadores del navegador"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Permite a una aplicación modificar el historial y los marcadores del navegador almacenados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar tus datos."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modificar los permisos de ubicación geográfica del navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permite que una aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones maliciosas pueden utilizarlos para permitir el envío de información sobre la ubicación a sitos web de forma arbitraria."</string>
     <string name="save_password_message" msgid="767344687139195790">"¿Quieres recordar esta contraseña en el navegador?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Seleccionar todos"</string>
-    <string name="selectText" msgid="4862359311088898878">"Seleccionar palabra"</string>
     <string name="cut" msgid="3092569408438626261">"Cortar"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Pegar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Seleccionar texto..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selección de texto "</string>
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Agregar \"<xliff:g id="WORD">%s</xliff:g>\" al diccionario"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Editar texto"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Acciones de texto"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Poco espacio de almacenamiento"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Hay poco espacio de almacenamiento en el teléfono."</string>
     <string name="ok" msgid="5970060430562524910">"Aceptar"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"Aceptar"</string>
     <string name="no" msgid="5141531044935541497">"Cancelar"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Atención"</string>
+    <string name="loading" msgid="1760724998928255250">"Cargando..."</string>
     <string name="capital_on" msgid="1544682755514494298">"Encendido"</string>
     <string name="capital_off" msgid="6815870386972805832">"APAGADO"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Completar la acción mediante"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Se redirigió la aplicación"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> se está ejecutando ahora."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> se inició originalmente."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Mostrar todos"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"Almacenamiento masivo USB"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"conectado al USB"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Has conectado tu teléfono a tu computadora mediante USB. Selecciona el botón a continuación si deseas copiar los archivos entre tu computadora y la tarjeta SD de Android."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Has conectado tu teléfono a tu computadora mediante USB. Selecciona el botón a continuación si deseas copiar los archivos entre tu computadora y la tarjeta SD de Android."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Has conectado tu teléfono a tu computadora mediante USB. Selecciona el botón a continuación si deseas copiar los archivos entre tu computadora y la tarjeta SD de Android."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Activar el almacenamiento USB"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Hay un problema para utilizar tu tarjeta SD en el almacenamiento USB."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Hay un problema para utilizar tu tarjeta SD en el almacenamiento USB."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Hay un problema para utilizar tu tarjeta SD en el almacenamiento USB."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"conectado al USB"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Seleccionar para copiar archivos desde o hacia tu computadora."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Apagar el almacenamiento USB"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Seleccionar para desactivar el almacenamiento USB."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"Almacenamiento USB en uso"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Antes de desactivar el almacenamiento USB, asegúrate de haber desmontado (\"expulsado\") la tarjeta SD de Android de tu computadora."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Antes de desactivar el almacenamiento USB, asegúrate de haber desmontado (\"expulsado\") la tarjeta SD de Android de tu computadora."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Antes de desactivar el almacenamiento USB, asegúrate de haber desmontado (\"expulsado\") la tarjeta SD de Android de tu computadora."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Desactivar el almacenamiento USB"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Se ha producido un problema al desactivar el almacenamiento USB. Asegúrate de haber desmontado el host USB, luego vuelve a intentarlo."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Activar el almacenamiento USB"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Si activas el almacenamiento USB, algunas aplicaciones que estás usando se detendrán y es posible que no estén disponibles hasta que desactives el almacenamiento USB."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"Error en el funcionamiento del USB"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"Aceptar"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formatear tarjeta SD"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formatear tarjeta SD"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de tu tarjeta."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de tu tarjeta."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formatear tarjeta SD"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de tu tarjeta."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formato"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuración de USB conectada"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Seleccionar para desactivar la depuración de USB."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Preparación de la tarjeta SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Preparación de la tarjeta SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Preparación de la tarjeta SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Verificando errores"</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Tarjeta SD vacía"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Tarjeta SD vacía"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"La tarjeta SD está vacía o utiliza un filesystem no admitido."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Tarjeta SD en blanco o el sistema de archivos no es compatible."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Tarjeta SD dañada"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Tarjeta SD dañada"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"La tarjeta SD está dañada. Es posible que debas reformatear tu tarjeta."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"Tarjeta SD dañada. Es posible que debas reformatearla."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Tarjeta SD extraída de forma imprevista"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Tarjeta SD extraída de forma imprevista"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Desmontar la tarjeta SD antes de extraerla para evitar la pérdida de datos."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Desmontar la tarjeta SD antes de extraerla para evitar la pérdida de datos."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"Tarjeta SD fácil de extraer"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"Tarjeta SD fácil de extraer"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"La tarjeta SD ahora se puede extraer de manera segura."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Puedes eliminar la tarjeta SD sin riesgos."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Tarjeta SD extraída"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Tarjeta SD extraída"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"Se extrajo la SD. Inserta una nva. tarjeta SD para aumentar el espacio de almac. de tu disposit."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"Tarjeta SD eliminada. Inserta una nueva."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Tarjeta SD vacía"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"Tarjeta SD en blanco o el sistema de archivos no es compatible."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Tarjeta SD dañada"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"Tarjeta SD dañada. Es posible que debas reformatearla."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Tarjeta SD extraída de forma imprevista"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Desmontar la tarjeta SD antes de extraerla para evitar la pérdida de datos."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"Tarjeta SD fácil de extraer"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Puedes eliminar la tarjeta SD sin riesgos."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Tarjeta SD extraída"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"Tarjeta SD eliminada. Inserta una nueva."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"No se encontraron actividades coincidentes"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"actualizar la estadística de uso de los componentes"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permite la modificación de estadísticas recopiladas sobre el uso de componentes. Las aplicaciones normales no deben utilizarlo."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Clave previamente compartida según L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificado según L2TP/IPSec VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"Elegir archivo"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"No se seleccionó un archivo."</string>
     <string name="reset" msgid="2448168080964209908">"Restablecer"</string>
     <string name="submit" msgid="1602335572089911941">"Enviar"</string>
-    <string name="description_star" msgid="2654319874908576133">"favorito"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modo auto habilitado"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Seleccionar para salir del modo auto"</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Anclaje a red o zona activa conectados"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Tocar para configurar"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Atrás"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Siguiente"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Omitir"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Amplia utilización de datos móviles"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toca para obtener más información acerca de la utilización de datos móviles."</string>
-    <string name="throttled_notification_title" msgid="6269541897729781332">"Límite de datos móviles excedido"</string>
+    <string name="throttled_notification_title" msgid="6269541897729781332">"Límite de datos móviles excedido "</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Toca para obtener más información acerca de la utilización de datos móviles."</string>
+    <string name="no_matches" msgid="8129421908915840737">"Sin coincidencias"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Buscar en la página"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 coincidencia"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> de <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 0cb4bab..10ecaca 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Herramientas de desarrollo"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funciones necesarias solo para desarrolladores de aplicaciones"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Almacenamiento"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Acceder a la tarjeta SD"</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Acceder a la tarjeta SD"</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permite que una aplicación libere espacio de almacenamiento en el teléfono mediante la eliminación de archivos en el directorio de caché de la aplicación. El acceso al proceso del sistema suele estar muy restringido."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos de aplicaciones"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Permite que una aplicación mueva los recursos de aplicaciones de un medio interno a otro externo y viceversa."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"leer archivos de registro del sistema"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Permite que una aplicación lea los distintos archivos de registro del sistema. Con este permiso, la aplicación puede ver información general sobre las acciones que realiza el usuario con el teléfono, pero los registros no deberían contener información personal o privada."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"leer/escribir en los recursos propiedad del grupo de diagnóstico"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Permite que una aplicación lea y escriba en cualquier recurso propiedad del grupo de diagnóstico como, por ejemplo, archivos in/dev. Este permiso podría afectar a la seguridad y estabilidad del sistema. SÓLO se debe utilizar para diagnósticos específicos de hardware realizados por el fabricante o el operador."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"habilitar o inhabilitar componentes de la aplicación"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Permite que una aplicación configure el teléfono Bluetooth local, y vea dispositivos remotos y sincronice el teléfono con ellos."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"crear conexiones de Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Permite que una aplicación vea la configuración del teléfono Bluetooth local, y cree y acepte conexiones con los dispositivos sincronizados."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"inhabilitar bloqueo del teclado"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Permite que una aplicación inhabilite el bloqueo del teclado y cualquier protección con contraseña asociada. Un ejemplo legítimo de este permiso es la inhabilitación por parte del teléfono del bloqueo del teclado cuando recibe una llamada telefónica entrante y su posterior habilitación cuando finaliza la llamada."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"leer la configuración de sincronización"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Permite a una aplicación leer cualquier frase, palabra o nombre privado que el usuario haya almacenado en su diccionario."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"escribir en el diccionario definido por el usuario"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Permite a una aplicación escribir palabras nuevas en el diccionario de usuario."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"modificar/eliminar contenido de la tarjeta SD"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"modificar/eliminar contenido de la tarjeta SD"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Permite que una aplicación escriba en la tarjeta SD."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Permite que una aplicación escriba en la tarjeta SD."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/eliminar contenido de la tarjeta SD"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Permite que una aplicación escriba en la tarjeta SD."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"acceder al sistema de archivos almacenado en caché"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permite que una aplicación lea y escriba el sistema de archivos almacenado en caché."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Limitar opciones de contraseña"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Permite restringir los tipos de contraseñas que puede utilizar el usuario."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Controlar intentos de acceso"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Se ha producido un error al intentar controlar el acceso al dispositivo para realizar una acción."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Restablecer contraseña"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Permite forzar la contraseña para establecer un valor nuevo que el administrador deberá proporcionar al usuario para poder acceder."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Forzar bloqueo"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Permite controlar el momento de bloqueo del dispositivo solicitando al usuario que vuelva a introducir la contraseña."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Limitar opciones de contraseña"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Permite restringir los tipos de contraseñas que puede utilizar el usuario."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Controlar intentos de acceso"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Se ha producido un error al intentar controlar el acceso al dispositivo para realizar una acción."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Restablecer contraseña"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Permite forzar la contraseña para establecer un valor nuevo que el administrador deberá proporcionar al usuario para poder acceder."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Forzar bloqueo"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Permite controlar el momento de bloqueo del dispositivo solicitando al usuario que vuelva a introducir la contraseña."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Borrar todos los datos"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Permite realizar un restablecimiento de fábrica eliminando todos los datos sin confirmación."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Permite realizar un restablecimiento de fábrica eliminando todos los datos sin confirmación."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Define el servidor proxy global que se debe utilizar mientras la política esté habilitada. Solo el primer administrador de dispositivos define el servidor proxy global efectivo."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Casa"</item>
     <item msgid="869923650527136615">"Móvil"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduce el código PIN"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Introducir contraseña para desbloquear"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Introducir PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"El código PIN es incorrecto."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergencia"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Volver a llamada"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Inténtalo de nuevo"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Inténtalo de nuevo"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Cargado"</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Introduce el código PIN."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Desbloqueando tarjeta SIM..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Has realizado <xliff:g id="NUMBER_0">%d</xliff:g> intentos fallidos de creación de un patrón de desbloqueo. "\n\n"Inténtalo de nuevo dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Has introducido una contraseña incorrecta <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Inténtalo de nuevo dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Has introducido un PIN incorrecto <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Inténtalo de nuevo dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Has realizado <xliff:g id="NUMBER_0">%d</xliff:g> intentos fallidos de creación del patrón de desbloqueo. Si realizas <xliff:g id="NUMBER_1">%d</xliff:g> intentos fallidos más, se te pedirá que desbloquees el teléfono con tus credenciales de acceso de Google."\n\n" Espera <xliff:g id="NUMBER_2">%d</xliff:g> segundos e inténtalo de nuevo."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Espera <xliff:g id="NUMBER">%d</xliff:g> segundos y vuelve a intentarlo."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"¿Has olvidado el patrón?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"¿Quieres salir de esta página?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Selecciona \"Aceptar\" para continuar o \"Cancelar\" para permanecer en la página actual."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Sugerencia: toca dos veces para ampliar o reducir."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"leer información de marcadores y del historial del navegador"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite que la aplicación lea todas las URL que ha visitado el navegador y todos sus marcadores."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"escribir en marcadores y en el historial del navegador"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Permite que una aplicación modifique la información de los marcadores o del historial del navegador almacenada en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar los datos del navegador."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modificar los permisos de ubicación geográfica del navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permite que una aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones malintencionadas pueden utilizar este permiso para permitir el envío de información sobre la ubicación a sitios web arbitrarios."</string>
     <string name="save_password_message" msgid="767344687139195790">"¿Deseas que el navegador recuerde esta contraseña?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Seleccionar todo"</string>
-    <string name="selectText" msgid="4862359311088898878">"Seleccionar palabra"</string>
     <string name="cut" msgid="3092569408438626261">"Cortar"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Pegar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Seleccionar texto..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selección de texto"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Método de introducción de texto"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Añadir \"<xliff:g id="WORD">%s</xliff:g>\" al diccionario"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Editar texto"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Acciones de texto"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Poco espacio"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Se está agotando el espacio de almacenamiento del teléfono."</string>
     <string name="ok" msgid="5970060430562524910">"Aceptar"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"Aceptar"</string>
     <string name="no" msgid="5141531044935541497">"Cancelar"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Atención"</string>
+    <string name="loading" msgid="1760724998928255250">"Cargando…"</string>
     <string name="capital_on" msgid="1544682755514494298">"Activado"</string>
     <string name="capital_off" msgid="6815870386972805832">"Desconectado"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Completar acción utilizando"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Aplicación redireccionada"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> se está ejecutando."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"Inicialmente, se inició la aplicación <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Mostrar todos"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"Almacenamiento USB masivo"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"Conectado por USB"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Has conectado el teléfono al equipo mediante USB. Selecciona el botón situado debajo si deseas copiar archivos entre el equipo y la tarjeta SD del teléfono con Android."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Has conectado el teléfono al equipo mediante USB. Selecciona el botón situado debajo si deseas copiar archivos entre el equipo y la tarjeta SD del teléfono con Android."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Has conectado el teléfono al equipo mediante USB. Selecciona el botón situado debajo si deseas copiar archivos entre el equipo y la tarjeta SD del teléfono con Android."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Activar almacenamiento USB"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Se ha producido un problema al intentar utilizar la tarjeta SD para el almacenamiento USB."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Se ha producido un problema al intentar utilizar la tarjeta SD para el almacenamiento USB."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Se ha producido un problema al intentar utilizar la tarjeta SD para el almacenamiento USB."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"Conectado por USB"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Para copiar archivos al/desde el equipo"</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Desactivar almacenamiento USB"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Seleccionar para desactivar USB."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"El almacenamiento USB está en uso."</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Antes de desactivar el almacenamiento USB, asegúrate de haber desmontado (\"retirado\") la tarjeta SD del teléfono con Android del equipo."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Antes de desactivar el almacenamiento USB, asegúrate de haber desmontado (\"retirado\") la tarjeta SD del teléfono con Android del equipo."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Antes de desactivar el almacenamiento USB, asegúrate de haber desmontado (\"retirado\") la tarjeta SD del teléfono con Android del equipo."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Desactivar almacenamiento USB"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Se ha producido un problema al desactivar el almacenamiento USB. Asegúrate de haber desactivado el host USB y, a continuación, vuelve a intentarlo."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Activar almacenamiento USB"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Si activas el almacenamiento USB, se detendrán algunas aplicaciones que estás utilizando y estas no estarán disponibles hasta que lo desactives."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"No se ha podido realizar la operación USB"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"Aceptar"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formatear tarjeta SD"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formatear tarjeta SD"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de la tarjeta."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de la tarjeta."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formatear tarjeta SD"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de la tarjeta."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formato"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Dispositivo de depuración USB conectado"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Seleccionar para inhabilitar la depuración USB"</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Preparando tarjeta SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Preparando tarjeta SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Preparando tarjeta SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Comprobando errores..."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Tarjeta SD vacía"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Tarjeta SD vacía"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"La tarjeta SD está vacía o utiliza un sistema de archivos incompatible."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"La tarjeta SD está vacía o su sistema de archivos es incompatible."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Tarjeta SD dañada"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Tarjeta SD dañada"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"La tarjeta SD está dañada. Es posible que sea necesario volver a formatearla."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"La tarjeta SD está dañada. Es posible que sea necesario volver a formatearla."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"La tarjeta SD se ha extraído inesperadamente."</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"La tarjeta SD se ha extraído inesperadamente."</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Desactiva la tarjeta SD antes de extraerla para evitar la pérdida de datos."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Desactiva la tarjeta SD antes de extraerla para evitar la pérdida de datos."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"Es seguro extraer la tarjeta SD."</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"Es seguro extraer la tarjeta SD."</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"Ya puedes extraer la tarjeta SD."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Puedes extraer la tarjeta SD de forma segura."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Tarjeta SD extraída"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Tarjeta SD extraída"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"La tarjeta SD se ha extraído. Inserta una nueva tarjeta SD para aumentar la capacidad de almacenamiento de tu dispositivo."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"La tarjeta SD se ha extraído. Inserta una nueva."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Tarjeta SD vacía"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"La tarjeta SD está vacía o su sistema de archivos es incompatible."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Tarjeta SD dañada"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"La tarjeta SD está dañada. Es posible que sea necesario volver a formatearla."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"La tarjeta SD se ha extraído inesperadamente."</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Desactiva la tarjeta SD antes de extraerla para evitar la pérdida de datos."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"Es seguro extraer la tarjeta SD."</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Puedes extraer la tarjeta SD de forma segura."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Tarjeta SD extraída"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"La tarjeta SD se ha extraído. Inserta una nueva."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"No se ha encontrado ninguna actividad coincidente."</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"actualizar estadísticas de uso de componentes"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permite la modificación de estadísticas recopiladas sobre el uso de componentes. No está destinado al uso por parte de aplicaciones normales."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Red privada virtual L2TP/IPSec basada en clave compartida previamente"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Red privada virtual L2TP/IPSec basada en certificado"</string>
     <string name="upload_file" msgid="2897957172366730416">"Seleccionar archivo"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Archivo no seleccionado"</string>
     <string name="reset" msgid="2448168080964209908">"Restablecer"</string>
     <string name="submit" msgid="1602335572089911941">"Enviar"</string>
-    <string name="description_star" msgid="2654319874908576133">"favoritos"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Se ha habilitado el modo coche."</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Selecciona esta opción para salir del modo coche."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Anclaje a red activo o zona Wi-Fi"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Toca para iniciar la configuración."</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Atrás"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Siguiente"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Omitir"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Uso elevado datos móviles"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Más información sobre uso de datos"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Límite datos superado"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Más información sobre uso de datos"</string>
+    <string name="no_matches" msgid="8129421908915840737">"No hay coincidencias."</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Buscar en la página"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"Una coincidencia"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> de <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 5f3427b..2492a85 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Outils de développement"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Ces fonctionnalités sont réservées aux développeurs d\'applications."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Stockage"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Accès à la carte SD"</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Accès à la carte SD"</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permet à une application de libérer de l\'espace dans la mémoire du téléphone en supprimant des fichiers du répertoire du cache des applications. Cet accès est en général limité aux processus système."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Déplacer des ressources d\'application"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Autorise l\'application à déplacer des ressources d\'application d\'un support interne à un support externe et inversement."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"Lecture des fichiers journaux du système"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Permet à une application de lire les différents fichiers journaux du système afin d\'obtenir des informations générales sur la façon dont vous utilisez votre téléphone,  sans pour autant récupérer des informations d\'ordre personnel ou privé."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"Lecture/écriture dans les ressources appartenant aux diagnostics"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Permet à une application de lire et d\'éditer toute ressource appartenant au groupe de diagnostics (par exemple, les fichiers in/dev). Ceci peut affecter la stabilité et la sécurité du système. Cette fonctionnalité est UNIQUEMENT réservée aux diagnostics matériels effectués par le fabricant ou l\'opérateur."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"Activer ou désactiver des éléments de l\'application"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Permet à une application de configurer le téléphone Bluetooth local, d\'identifier des périphériques distants et de les associer au téléphone."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"Création de connexions Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Permet à une application d\'obtenir la configuration du téléphone Bluetooth local et de créer et accepter des connexions à des appareils associés."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"Désactivation du verrouillage des touches"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Permet à une application de désactiver le verrouillage des touches et toute sécurité par mot de passe. Exemple : Votre téléphone désactive le verrouillage du clavier lorsque vous recevez un appel, puis le réactive lorsque vous raccrochez."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"Lecture des paramètres de synchronisation"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Permet à une application de lire tous les mots, noms et expressions que l\'utilisateur a pu enregistrer dans son dictionnaire personnel."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"Enregistrement dans le dictionnaire défini par l\'utilisateur"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Permet à une application d\'enregistrer de nouveaux mots dans le dictionnaire personnel de l\'utilisateur."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"Modifier/supprimer le contenu de la carte SD"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"Modifier/supprimer le contenu de la carte SD"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Autorise une application à écrire sur la carte SD."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Autorise une application à écrire sur la carte SD."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"Modifier/supprimer le contenu de la carte SD"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Autorise une application à écrire sur la carte SD."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"accéder au système de fichiers en cache"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permet à une application de lire et d\'écrire dans le système de fichiers en cache."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Limiter le mot de passe"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Restreint les types de mots de passe que vous êtes autorisé à utiliser."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Surveiller les tentatives de connexion"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Surveille les échecs de connexion au périphérique pour effectuer une action."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Réinitialiser le mot de passe"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Force l\'utilisation d\'un nouveau mot de passe, qui doit vous être communiqué par l\'administrateur pour que vous puissiez vous connecter."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Forcer le verrouillage"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Contrôle le verrouillage du périphérique, avec obligation de saisir à nouveau le mot de passe."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Limiter le mot de passe"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Restreint les types de mots de passe que vous êtes autorisé à utiliser."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Surveiller les tentatives de connexion"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Surveille les échecs de connexion au périphérique pour effectuer une action."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Réinitialiser le mot de passe"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Force l\'utilisation d\'un nouveau mot de passe, qui doit vous être communiqué par l\'administrateur pour que vous puissiez vous connecter."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Forcer le verrouillage"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Contrôle le verrouillage du périphérique, avec obligation de saisir à nouveau le mot de passe."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Effacer toutes les données"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Rétablit les paramètres d\'usine, supprimant toutes vos données sans demande de confirmation."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Rétablit les paramètres d\'usine, supprimant toutes vos données sans demande de confirmation."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Indiquez le proxy global à utiliser pour ce mobile lorsque les règles sont activées. Seul l\'administrateur principal du mobile peut définir le proxy global effectif."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Domicile"</item>
     <item msgid="869923650527136615">"Mobile"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Saisissez le code PIN"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Saisissez le mot de passe pour procéder au déverrouillage."</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Saisissez le code PIN pour procéder au déverrouillage."</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Le code PIN est incorrect !"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Pour débloquer le clavier, appuyez sur \"Menu\" puis sur 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numéro d\'urgence"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Retour à l\'appel"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Combinaison correcte !"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Incorrect. Merci de réessayer."</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Incorrect. Merci de réessayer."</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Chargement (<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Chargé"</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"La carte SIM est verrouillée."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Déblocage de la carte SIM..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Vous avez mal reproduit le schéma de déverrouillage <xliff:g id="NUMBER_0">%d</xliff:g> fois. "\n\n"Veuillez réessayer dans <xliff:g id="NUMBER_1">%d</xliff:g> secondes."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Vous avez saisi un mot de passe incorrect à <xliff:g id="NUMBER_0">%d</xliff:g> reprises. "\n\n"Veuillez réessayer dans <xliff:g id="NUMBER_1">%d</xliff:g> secondes."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Vous avez saisi un code PIN incorrect à <xliff:g id="NUMBER_0">%d</xliff:g> reprises. "\n\n"Veuillez réessayer dans <xliff:g id="NUMBER_1">%d</xliff:g> secondes."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Vous avez mal saisi le schéma de déverrouillage <xliff:g id="NUMBER_0">%d</xliff:g> fois. Au bout de <xliff:g id="NUMBER_1">%d</xliff:g> tentatives supplémentaires, vous devrez débloquer votre téléphone à l\'aide de votre identifiant Google."\n\n"Merci de réessayer dans <xliff:g id="NUMBER_2">%d</xliff:g> secondes."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Réessayez dans <xliff:g id="NUMBER">%d</xliff:g> secondes."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Schéma oublié ?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Vous souhaitez quitter cette page ?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Sélectionnez OK pour continuer ou Annuler pour rester sur la page actuelle."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Confirmer"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Conseil : Appuyez deux fois pour effectuer un zoom avant ou arrière."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"lire l\'historique et les favoris du navigateur"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Autorise l\'application à lire toutes les URL auxquelles le navigateur a accédé et tous ses favoris."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"écrire dans l\'historique et les favoris du navigateur"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Autorise une application à modifier l\'historique du navigateur ou les favoris enregistrés sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonction pour effacer ou modifier les données de votre navigateur."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modifier les autorisations de géolocalisation du navigateur"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permet à une application de modifier les autorisations de géolocalisation du navigateur. Les applications malveillantes peuvent se servir de cette fonctionnalité pour envoyer des informations de lieu à des sites Web arbitraires."</string>
     <string name="save_password_message" msgid="767344687139195790">"Voulez-vous que le navigateur se souvienne de ce mot de passe ?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Tout sélectionner"</string>
-    <string name="selectText" msgid="4862359311088898878">"Sélectionner texte"</string>
     <string name="cut" msgid="3092569408438626261">"Couper"</string>
     <string name="copy" msgid="2681946229533511987">"Copier"</string>
     <string name="paste" msgid="5629880836805036433">"Coller"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copier l\'URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Sélect. le texte..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Sélection de texte"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Mode de saisie"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Ajouter \"<xliff:g id="WORD">%s</xliff:g>\" au dictionnaire"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Modifier le texte"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Actions sur le texte"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Espace disponible faible"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"La mémoire du téléphone commence à être pleine."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Annuler"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Attention"</string>
+    <string name="loading" msgid="1760724998928255250">"Chargement en cours..."</string>
     <string name="capital_on" msgid="1544682755514494298">"ON"</string>
     <string name="capital_off" msgid="6815870386972805832">"OFF"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Continuer avec"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Application redirigée"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> est maintenant lancée."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"Application lancée initialement : <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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 du mode strict."</string>
     <string name="smv_process" msgid="5120397012047462446">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a enfreint ses propres règles du mode strict."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Tout afficher"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"Stockage de masse USB"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"Connecté à l\'aide d\'un câble USB"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Vous avez connecté votre téléphone à votre ordinateur à l\'aide d\'un câble USB. Sélectionnez le bouton ci-dessous pour copier des fichiers de votre ordinateur vers la carte SD de votre Android, ou inversement."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Vous avez connecté votre téléphone à votre ordinateur à l\'aide d\'un câble USB. Sélectionnez le bouton ci-dessous pour copier des fichiers de votre ordinateur vers la carte SD de votre Android, ou inversement."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Vous avez connecté votre téléphone à votre ordinateur à l\'aide d\'un câble USB. Sélectionnez le bouton ci-dessous pour copier des fichiers de votre ordinateur vers la carte SD de votre Android, ou inversement."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Activer le périphérique de stockage USB"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Un problème est survenu lors de l\'utilisation de votre carte SD en tant que périphérique de stockage USB."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Un problème est survenu lors de l\'utilisation de votre carte SD en tant que périphérique de stockage USB."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Un problème est survenu lors de l\'utilisation de votre carte SD en tant que périphérique de stockage USB."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"Connecté avec un câble USB"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Activez pour copier des fichiers vers/de votre ordinateur."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Éteindre le périphérique de stockage USB"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Sélectionner pour éteindre le périphérique de stockage USB"</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"Stockage USB en cours d\'utilisation"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Avant de mettre hors tension le stockage USB, assurez-vous d\'avoir désactivé (\"éjecté\") la carte SD de votre téléphone Android à partir de votre ordinateur."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Avant de mettre hors tension le stockage USB, assurez-vous d\'avoir désactivé (\"éjecté\") la carte SD de votre téléphone Android à partir de votre ordinateur."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Avant de mettre hors tension le stockage USB, assurez-vous d\'avoir désactivé (\"éjecté\") la carte SD de votre téléphone Android à partir de votre ordinateur."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Désactiver le périphérique de stockage USB"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Un problème est survenu lors de la mise hors tension du périphérique de stockage USB. Assurez-vous que l\'hôte USB a bien été désactivé, puis essayez à nouveau."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Activer le périphérique de stockage USB"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Si vous activez le périphérique de stockage USB, certaines applications que vous utilisez se fermeront et risquent de n\'être de nouveau disponibles qu\'après la désactivation du périphérique."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"Échec du fonctionnement USB"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formater la carte SD"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formater la carte SD"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Voulez-vous vraiment formater la carte SD ? Toutes les données de cette carte seront perdues."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Voulez-vous vraiment formater la carte SD ? Toutes les données de cette carte seront perdues."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formater la carte SD"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Voulez-vous vraiment formater la carte SD ? Toutes les données de cette carte seront perdues."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Débogage USB connecté"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Sélectionnez cette option pour désactiver le débogage USB."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"candidats"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Préparation de la carte SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Préparation de la carte SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Préparation de la carte SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Recherche d\'erreurs"</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Carte SD vide"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Carte SD vide"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"La carte SD est vide ou utilise un système de fichiers non pris en charge."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"La carte SD est vide ou son système de fichiers n\'est pas pris en charge."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Carte SD endommagée"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Carte SD endommagée"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"La carte SD est endommagée. Vous devrez peut-être reformater votre carte."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"La carte SD est endommagée. Vous devrez peut-être la reformater."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Carte SD retirée inopinément"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Carte SD retirée inopinément"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Désactiver la carte SD avant de la retirer pour éviter toute perte de données."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Désactiver la carte SD avant de la retirer pour éviter toute perte de données."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"La carte SD peut être retirée en toute sécurité"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"La carte SD peut être retirée en toute sécurité"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"Vous pouvez désormais retirer la carte SD en toute sécurité."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Vous pouvez retirer la carte SD en toute sécurité."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Carte SD manquante"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Carte SD manquante"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"Carte SD manquante. Insérez une autre carte pour augmenter la capacité de stockage."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"La carte SD a été retirée. Insérez-en une autre."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Carte SD vide"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"La carte SD est vide ou son système de fichiers n\'est pas pris en charge."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Carte SD endommagée"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"La carte SD est endommagée. Vous devrez peut-être la reformater."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Carte SD retirée inopinément"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Désactiver la carte SD avant de la retirer pour éviter toute perte de données."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"La carte SD peut être retirée en toute sécurité"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Vous pouvez retirer la carte SD en toute sécurité."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Carte SD manquante"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"La carte SD a été retirée. Insérez-en une autre."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Aucune activité correspondante trouvée"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"mettre à jour les données statistiques du composant"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permet de modifier les données statistiques collectées du composant. Cette option n\'est pas utilisée par les applications standard."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec basé sur une clé pré-partagée"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec basé sur un certificat"</string>
     <string name="upload_file" msgid="2897957172366730416">"Sélectionner un fichier"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Aucun fichier sélectionné"</string>
     <string name="reset" msgid="2448168080964209908">"Réinitialiser"</string>
     <string name="submit" msgid="1602335572089911941">"Envoyer"</string>
-    <string name="description_star" msgid="2654319874908576133">"favori"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Mode Voiture activé"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Sélectionnez cette option pour quitter le mode Voiture."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Partage de connexion ou point d\'accès sans fil activé"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Toucher pour configurer"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Retour"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Suivant"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Ignorer"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Utilisation élevée des données mobiles"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Touchez pour en savoir plus sur l\'utilisation des données mobiles"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Quota d\'utilisation des données mobiles dépassé"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Touchez pour en savoir plus sur l\'utilisation des données mobiles"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Aucune correspondance"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Rechercher sur la page"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 correspondance"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> sur <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 865b0e8..ab37893 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Strumenti di sviluppo"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funzionalità necessarie soltanto agli sviluppatori di applicazioni."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Archiviazione"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Accesso alla scheda SD."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Accesso alla scheda SD."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Consente a un\'applicazione di liberare spazio sul telefono eliminando file nella directory della cache dell\'applicazione. L\'accesso è generalmente limitato a processi di sistema."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Spostare risorse dell\'applicazione"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Consente a un\'applicazione di spostare risorse applicative da supporti interni a esterni e viceversa."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"lettura file di registro sistema"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Consente a un\'applicazione di leggere vari file di registro del sistema per trovare informazioni generali sulle operazioni effettuate con il telefono. Tali file non dovrebbero contenere informazioni personali o riservate."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"lettura/scrittura risorse di proprietà di diag"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Consente a un\'applicazione di leggere le risorse del gruppo diag e scrivere a esse, per esempio i file in /dev. Questa capacità potrebbe influire sulla stabilità e sicurezza del sistema. Dovrebbe essere utilizzata SOLTANTO per diagnostiche specifiche dell\'hardware effettuate dal produttore o dall\'operatore."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"attivazione/disattivazione componenti applicazioni"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Consente a un\'applicazione di configurare il telefono Bluetooth locale e di rilevare e abbinare dispositivi remoti."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"creazione connessioni Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Consente a un\'applicazione di visualizzare la configurazione del telefono Bluetooth locale e di stabilire e accettare connessioni con dispositivi associati."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"disattivazione blocco tastiera"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Consente la disattivazione da parte di un\'applicazione del blocco tastiera e di eventuali protezioni tramite password associate. Un valido esempio è la disattivazione da parte del telefono del blocco tastiera quando riceve una telefonata in entrata, e la successiva riattivazione del blocco al termine della chiamata."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"lettura impostazioni di sincronizz."</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Consente a un\'applicazione di leggere parole, nomi e frasi private che l\'utente potrebbe aver memorizzato nel dizionario utente."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"scrittura nel dizionario definito dall\'utente"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Consente a un\'applicazione di scrivere nuove parole nel dizionario utente."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"modificare/eliminare i contenuti della scheda SD"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"modificare/eliminare i contenuti della scheda SD"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Consente a un\'applicazione di scrivere sulla scheda SD."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Consente a un\'applicazione di scrivere sulla scheda SD."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificare/eliminare i contenuti della scheda SD"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Consente a un\'applicazione di scrivere sulla scheda SD."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"accesso al filesystem nella cache"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Consente a un\'applicazione di leggere e scrivere il filesystem nella cache."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Limita password"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Limita i tipi di password che sei autorizzato a utilizzare."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Controlla i tentativi di accesso"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Monitora i tentativi non riusciti di accedere al dispositivo per eseguire un\'azione."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Reimposta password"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Forza un nuovo valore per la password, chiedendo all\'amministratore di concedertelo prima di poter eseguire l\'accesso."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Forza blocco"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Controlla quando il dispositivo si blocca, chiedendoti di reinserire la password."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Limita password"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Limita i tipi di password che sei autorizzato a utilizzare."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Controlla i tentativi di accesso"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Monitora i tentativi non riusciti di accedere al dispositivo per eseguire un\'azione."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Reimposta password"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Forza un nuovo valore per la password, chiedendo all\'amministratore di concedertelo prima di poter eseguire l\'accesso."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Forza blocco"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Controlla quando il dispositivo si blocca, chiedendoti di reinserire la password."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Cancella tutti i dati"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Esegui un ripristino di fabbrica, eliminando tutti i tuoi dati senza conferma."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Esegui un ripristino di fabbrica, eliminando tutti i tuoi dati senza conferma."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Imposta il proxy globale del dispositivo in modo da utilizzarlo mentre la norma è attiva. Il proxy globale effettivo è impostabile solo dal primo amministratore del dispositivo."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Casa"</item>
     <item msgid="869923650527136615">"Cellulare"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"tramite <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> tramite <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Inserisci il PIN"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Inserisci password per sbloccare"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Inserisci PIN per sbloccare"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Codice PIN errato."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Per sbloccare, premi Menu, poi 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numero di emergenza"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Torna a chiamata"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Corretta."</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Riprova"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Riprova"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"In carica (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Carico."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"La SIM è bloccata."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Sblocco SIM..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi di inserimento della sequenza di sblocco. "\n\n"Riprova fra <xliff:g id="NUMBER_1">%d</xliff:g> secondi."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi errati di inserimento della password. "\n\n"Riprova tra <xliff:g id="NUMBER_1">%d</xliff:g> secondi."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi errati di inserimento del PIN. "\n\n"Riprova tra <xliff:g id="NUMBER_1">%d</xliff:g> secondi."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi di inserimento della sequenza di sblocco. Dopo altri <xliff:g id="NUMBER_1">%d</xliff:g> tentativi falliti, ti verrà chiesto di sbloccare il telefono tramite i dati di accesso di Google."\n\n"Riprova fra <xliff:g id="NUMBER_2">%d</xliff:g> secondi."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Riprova fra <xliff:g id="NUMBER">%d</xliff:g> secondi."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Hai dimenticato la sequenza?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Uscire da questa pagina?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Seleziona OK per continuare o Annulla per rimanere nella pagina corrente."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Conferma"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Suggerimento. Tocca due volte per aumentare/ridurre lo zoom."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"lettura cronologia e segnalibri del browser"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Consente all\'applicazione di leggere tutti gli URL visitati e tutti i segnalibri del browser."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"creazione cronologia e segnalibri del browser"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Consente a un\'applicazione di modificare la cronologia o i segnalibri del browser memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare i dati del browser."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modifica le autorizzazioni di localizzazione geografica del browser"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Consente a un\'applicazione di modificare le autorizzazioni di localizzazione geografica del browser. Le applicazioni dannose possono utilizzare questa autorizzazione per consentire l\'invio di informazioni sulla posizione a siti web arbitrari."</string>
     <string name="save_password_message" msgid="767344687139195790">"Memorizzare la password nel browser?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Seleziona tutto"</string>
-    <string name="selectText" msgid="4862359311088898878">"Seleziona parola"</string>
     <string name="cut" msgid="3092569408438626261">"Taglia"</string>
     <string name="copy" msgid="2681946229533511987">"Copia"</string>
     <string name="paste" msgid="5629880836805036433">"Incolla"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copia URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Seleziona testo..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selezione testo"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metodo inserimento"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Aggiungi \"<xliff:g id="WORD">%s</xliff:g>\" al dizionario"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Modifica testo"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Azioni testo"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Spazio in esaurimento"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Spazio di archiviazione del telefono in esaurimento."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Annulla"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Attenzione"</string>
+    <string name="loading" msgid="1760724998928255250">"Caricamento in corso..."</string>
     <string name="capital_on" msgid="1544682755514494298">"ON"</string>
     <string name="capital_off" msgid="6815870386972805832">"OFF"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Completa l\'azione con"</string>
@@ -763,16 +750,19 @@
     <string name="force_close" msgid="3653416315450806396">"Termina"</string>
     <string name="report" msgid="4060218260984795706">"Segnala"</string>
     <string name="wait" msgid="7147118217226317732">"Attendi"</string>
-    <string name="launch_warning_title" msgid="8323761616052121936">"Applicazione reindirizzata"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> è ora in esecuzione."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> già avviata."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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_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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Mostra tutto"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"Archiviazione di massa USB"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB collegata"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Il telefono è stato collegato al computer tramite USB. Seleziona il pulsante sottostante se desideri copiare file tra il computer e la scheda SD di Android."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Il telefono è stato collegato al computer tramite USB. Seleziona il pulsante sottostante se desideri copiare file tra il computer e la scheda SD di Android."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Il telefono è stato collegato al computer tramite USB. Seleziona il pulsante sottostante se desideri copiare file tra il computer e la scheda SD di Android."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Attiva archivio USB"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Problema di utilizzo della scheda SD per l\'archiviazione USB."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Problema di utilizzo della scheda SD per l\'archiviazione USB."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Problema di utilizzo della scheda SD per l\'archiviazione USB."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB collegata"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Seleziona per copiare file sul/dal tuo computer."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Disattiva archivio USB"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Seleziona per disattivare archivio USB."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"Archiviazione USB in uso"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Prima di disattivare l\'archiviazione USB, assicurati di avere smontato (\"espulso\") la scheda SD di Android dal computer."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Prima di disattivare l\'archiviazione USB, assicurati di avere smontato (\"espulso\") la scheda SD di Android dal computer."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Prima di disattivare l\'archiviazione USB, assicurati di avere smontato (\"espulso\") la scheda SD di Android dal computer."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Disattiva archiviazione USB"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Si è verificato un problema durante la disattivazione dell\'archiviazione USB. Verifica di avere smontato l\'host USB e riprova."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Attiva archivio USB"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Se attivi l\'archivio USB, alcune applicazioni in uso si bloccheranno e potrebbero risultare non disponibili finché non disattiverai l\'archivio USB."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"Operazione USB non riuscita"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formatta scheda SD"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formatta scheda SD"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Formattare la scheda SD? Tutti i dati sulla scheda verranno persi."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Formattare la scheda SD? Tutti i dati sulla scheda verranno persi."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formatta scheda SD"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Formattare la scheda SD? Tutti i dati sulla scheda verranno persi."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatta"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Debug USB collegato"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Seleziona per disattivare il debug USB."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"candidati"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Preparazione scheda SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Preparazione scheda SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Preparazione scheda SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Ricerca errori."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Scheda SD vuota"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Scheda SD vuota"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"La scheda SD è vuota o utilizza un file system non supportato."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Scheda SD vuota o con filesystem non supportato."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Scheda SD danneggiata"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Scheda SD danneggiata"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"La scheda SD è danneggiata. Potrebbe essere necessario riformattarla."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"Scheda SD danneggiata. Potrebbe essere necessario riformattarla."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Rimozione imprevista della scheda SD"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Rimozione imprevista della scheda SD"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Smonta scheda SD prima della rimozione per evitare la perdita di dati."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Smonta scheda SD prima della rimozione per evitare la perdita di dati."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"È possibile rimuovere la scheda SD"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"È possibile rimuovere la scheda SD"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"È ora possibile rimuovere la scheda SD in modo sicuro."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Puoi rimuovere la scheda SD in tutta sicurezza."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Scheda SD rimossa"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Scheda SD rimossa"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"Inserisci una nuova scheda SD per aumentare la memoria del dispositivo."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"Scheda SD rimossa. Inseriscine un\'altra."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Scheda SD vuota"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"Scheda SD vuota o con filesystem non supportato."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Scheda SD danneggiata"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"Scheda SD danneggiata. Potrebbe essere necessario riformattarla."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Rimozione imprevista della scheda SD"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Smonta scheda SD prima della rimozione per evitare la perdita di dati."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"È possibile rimuovere la scheda SD"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Puoi rimuovere la scheda SD in tutta sicurezza."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Scheda SD rimossa"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"Scheda SD rimossa. Inseriscine un\'altra."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Nessuna attività corrispondente trovata"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"aggiornare le statistiche di utilizzo dei componenti"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Consente la modifica delle statistiche di utilizzo dei componenti raccolte. Da non usare per normali applicazioni."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec basata su chiave precondivisa"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec basata su certificato"</string>
     <string name="upload_file" msgid="2897957172366730416">"Scegli file"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Nessun file è stato scelto"</string>
     <string name="reset" msgid="2448168080964209908">"Reimposta"</string>
     <string name="submit" msgid="1602335572089911941">"Invia"</string>
-    <string name="description_star" msgid="2654319874908576133">"preferiti"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modalità automobile attivata"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Seleziona per uscire dalla modalità automobile."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Tethering oppure hotspot attivo"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Tocca per configurare"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Indietro"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Avanti"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Salta"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Utilizzo dati cell. elevato"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tocca per informazioni sull\'utilizzo dati cell."</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Limite dati cell. superato"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Tocca per informazioni sull\'utilizzo dati cell."</string>
+    <string name="no_matches" msgid="8129421908915840737">"Nessuna corrispondenza"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Trova nella pagina"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 corrispondenza"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> di <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 8b0301e..6ad7a750 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"開発ツール"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"アプリケーションのデベロッパーにのみ必要な機能です。"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"ストレージ"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"SDカードにアクセスします。"</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"SDカードにアクセスします。"</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"アプリケーションのキャッシュディレクトリからファイルを削除して携帯電話のメモリを解放することをアプリケーションに許可します。通常、アクセスはシステムプロセスのみに制限されます。"</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"アプリケーションリソースの移動"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"内部と外部のメディア間でのアプリケーションリソースの移動をアプリケーションに許可します。"</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"システムログファイルの読み取り"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"システムのさまざまなログファイルの読み取りをアプリケーションに許可します。これにより携帯電話の使用状況に関する全般情報が取得されますが、個人情報や非公開情報が含まれることはありません。"</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"diagが所有するリソースの読み書き"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"diagグループが所有するリソース(例:/dev内のファイル)への読み書きをアプリケーションに許可します。システムの安定性とセキュリティに影響する恐れがあります。メーカー/オペレーターによるハードウェア固有の診断以外には使用しないでください。"</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"アプリケーションのコンポーネントを有効/無効にする"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"このBluetooth端末の設定、およびリモート端末を検出してペアに設定することをアプリケーションに許可します。"</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"Bluetooth接続の作成"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"このBluetooth端末の設定表示、および別の端末をペアとして設定し接続を承認することをアプリケーションに許可します。"</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"キーロックを無効にする"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"キーロックや関連するパスワードセキュリティを無効にすることをアプリケーションに許可します。正当な利用の例では、かかってきた電話を受信する際にキーロックを無効にし、通話の終了時にキーロックを有効にし直します。"</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"同期設定の読み取り"</string>
@@ -431,26 +416,27 @@
     <string name="permdesc_subscribedFeedsRead" msgid="3622200625634207660">"現在同期しているフィードの詳細の取得をアプリケーションに許可します。"</string>
     <string name="permlab_subscribedFeedsWrite" msgid="9015246325408209296">"登録したフィードの書き込み"</string>
     <string name="permdesc_subscribedFeedsWrite" msgid="8121607099326533878">"現在同期しているフィードの変更をアプリケーションに許可します。悪意のあるアプリケーションが同期フィードを変更する恐れがあります。"</string>
-    <string name="permlab_readDictionary" msgid="432535716804748781">"単語リストの読み込み"</string>
-    <string name="permdesc_readDictionary" msgid="1082972603576360690">"アプリケーションが単語リストに登録されている個人的な語句や名前を読み込むことを許可します。"</string>
-    <string name="permlab_writeDictionary" msgid="6703109511836343341">"単語リストへの書き込み"</string>
-    <string name="permdesc_writeDictionary" msgid="2241256206524082880">"アプリケーションが単語リストに新しい語句を書き込むことを許可します。"</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"SDカードのコンテンツを修正/削除する"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"SDカードのコンテンツを修正/削除する"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"SDカードへの書き込みをアプリケーションに許可します。"</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"SDカードへの書き込みをアプリケーションに許可します。"</string>
+    <string name="permlab_readDictionary" msgid="432535716804748781">"ユーザー定義辞書の読み込み"</string>
+    <string name="permdesc_readDictionary" msgid="1082972603576360690">"アプリケーションがユーザー辞書に登録されている個人的な語句や名前を読み込むことを許可します。"</string>
+    <string name="permlab_writeDictionary" msgid="6703109511836343341">"ユーザー定義辞書への書き込み"</string>
+    <string name="permdesc_writeDictionary" msgid="2241256206524082880">"アプリケーションがユーザー辞書に新しい語句を書き込むことを許可します。"</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"SDカードのコンテンツを修正/削除する"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"SDカードへの書き込みをアプリケーションに許可します。"</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"キャッシュファイルシステムにアクセス"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"キャッシュファイルシステムへの読み書きをアプリケーションに許可します。"</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"パスワードの制限"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"使用できるパスワードの種類を制限します。"</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"ログインの監視"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"携帯電話へのログインの失敗を監視し、何らかの処置をとります。"</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"パスワードのリセット"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"パスワードを強制的に新しい値に変更します。ログインするには管理者からその値を通知してもらう必要があります。"</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"強制ロック"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"携帯電話のロック時を管理します。パスワードの再入力が必要となります。"</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"パスワードの制限"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"使用できるパスワードの種類を制限します。"</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"ログインの監視"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"携帯電話へのログインの失敗を監視し、何らかの処置をとります。"</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"パスワードのリセット"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"パスワードを強制的に新しい値に変更します。ログインするには管理者からその値を通知してもらう必要があります。"</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"強制ロック"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"携帯電話のロック時を管理します。パスワードの再入力が必要となります。"</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"すべてのデータを消去"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"出荷時設定にリセットします。確認なしでデータがすべて削除されます。"</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"出荷時設定にリセットします。確認なしでデータがすべて削除されます。"</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"ポリシーが有効になっている場合は端末のグローバルプロキシが使用されるように設定します。有効なグローバルプロキシを設定できるのは最初のデバイス管理者だけです。"</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"自宅"</item>
     <item msgid="869923650527136615">"携帯"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>経由"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>、更新元: <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PINコードを入力"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"ロックを解除するにはパスワードを入力"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"ロックを解除するにはPINを入力"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PINコードが正しくありません。"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"MENU、0キーでロック解除"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急通報番号"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"通話に戻る"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"一致しました"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"やり直してください"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"やり直してください"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"充電中(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"充電完了。"</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIMカードはロックされています。"</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIMカードのロック解除中..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"ロック解除のパターンは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しく指定されていません。"\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>秒後にもう一度指定してください。"</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"入力したパスワードは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しくありませんでした。"\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>秒後にもう一度入力してください。"</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"入力したPINは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しくありませんでした。"\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>秒後にもう一度入力してください。"</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"指定したパターンは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しくありません。あと<xliff:g id="NUMBER_1">%d</xliff:g>回指定に失敗すると、携帯電話のロックの解除にGoogleへのログインが必要になります。"\n\n"<xliff:g id="NUMBER_2">%d</xliff:g>秒後にもう一度指定してください。"</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g>秒後にやり直してください。"</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"パターンを忘れた場合"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"このページから移動しますか?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"移動する場合は[OK]、今のページに残る場合は[キャンセル]を選択してください。"</string>
     <string name="save_password_label" msgid="6860261758665825069">"確認"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"ヒント: ダブルタップで拡大/縮小できます。"</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"ブラウザの履歴とブックマークを読み取る"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"ブラウザでアクセスしたすべてのURLおよびブラウザのすべてのブックマークの読み取りをアプリケーションに許可します。"</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"ブラウザの履歴とブックマークを書き込む"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"携帯電話に保存されているブラウザの履歴やブックマークの修正をアプリケーショに許可します。これにより悪意のあるアプリケーションが、ブラウザのデータを消去または変更する恐れがあります。"</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"ブラウザの位置情報へのアクセス権を変更"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"ブラウザの位置情報に対するアクセス権の変更をアプリケーションに許可します。この設定では、悪意のあるアプリケーションが任意のウェブサイトに位置情報を送信する可能性があります。"</string>
     <string name="save_password_message" msgid="767344687139195790">"このパスワードをブラウザで保存しますか?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"すべて選択"</string>
-    <string name="selectText" msgid="4862359311088898878">"語句を選択"</string>
     <string name="cut" msgid="3092569408438626261">"切り取り"</string>
     <string name="copy" msgid="2681946229533511987">"コピー"</string>
     <string name="paste" msgid="5629880836805036433">"貼り付け"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URLをコピー"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"テキストを選択..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"テキスト選択"</string>
     <string name="inputMethod" msgid="1653630062304567879">"入力方法"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"辞書に「<xliff:g id="WORD">%s</xliff:g>」を追加"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"テキストを編集"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"テキスト操作"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"空き容量低下"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"携帯電話の空き容量が少なくなっています。"</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"キャンセル"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"注意"</string>
+    <string name="loading" msgid="1760724998928255250">"読み込み中..."</string>
     <string name="capital_on" msgid="1544682755514494298">"ON"</string>
     <string name="capital_off" msgid="6815870386972805832">"OFF"</string>
     <string name="whichApplication" msgid="4533185947064773386">"アプリケーションを選択"</string>
@@ -763,9 +750,12 @@
     <string name="force_close" msgid="3653416315450806396">"強制終了"</string>
     <string name="report" msgid="4060218260984795706">"レポート"</string>
     <string name="wait" msgid="7147118217226317732">"待機"</string>
-    <string name="launch_warning_title" msgid="8323761616052121936">"アプリのリダイレクト"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g>が実行中です。"</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g>が最初に起動していました。"</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"すべて表示"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USBマスストレージ"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB接続"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"携帯電話をUSBでパソコンに接続しています。パソコンとAndroidのSDカード間でファイルをコピーするには、下のボタンを選択します。"</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"携帯電話をUSBでパソコンに接続しています。パソコンとAndroidのSDカード間でファイルをコピーするには、下のボタンを選択します。"</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"携帯電話をUSBでパソコンに接続しています。パソコンとAndroidのSDカード間でファイルをコピーするには、下のボタンを選択します。"</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"USBストレージをONにする"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"USBメモリにSDカードを使用する際に問題が発生しました。"</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"USBメモリにSDカードを使用する際に問題が発生しました。"</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"USBメモリにSDカードを使用する際に問題が発生しました。"</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB接続"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"パソコンとの間でファイルをコピーします。"</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USBストレージをOFFにする"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"USBストレージをOFFにする場合に選択します。"</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USBストレージを使用中"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"USBストレージをOFFにする前に、パソコンで必ずAndroidのSDカードのマウントを解除して(カードを取り出して)ください。"</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"USBストレージをOFFにする前に、パソコンで必ずAndroidのSDカードのマウントを解除して(カードを取り出して)ください。"</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"USBストレージをOFFにする前に、パソコンで必ずAndroidのSDカードのマウントを解除して(カードを取り出して)ください。"</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USBストレージをOFFにする"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"USBストレージをOFFにする際に問題が発生しました。USBホストのマウントが解除されていることを確認してからもう一度お試しください。"</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USBストレージをONにする"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"USBストレージをONにすると、使用中のアプリケーションの一部が停止し、USBストレージをOFFにするまで使用できなくなる場合があります。"</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB操作に失敗しました"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"SDカードをフォーマット"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"SDカードをフォーマット"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"SDカードをフォーマットしてもよろしいですか?カード内のすべてのデータが失われます。"</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"SDカードをフォーマットしてもよろしいですか?カード内のすべてのデータが失われます。"</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"SDカードをフォーマット"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"SDカードをフォーマットしてもよろしいですか?カード内のすべてのデータが失われます。"</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"フォーマット"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"USBデバッグが接続されました"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"USBデバッグを無効にする場合に選択します。"</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"候補"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"SDカードの準備中"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"SDカードの準備中"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"SDカードの準備中"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"エラーを確認しています。"</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"空のSDカード"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"空のSDカード"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"SDカードが空か、サポート対象外のファイルシステムを使用しています。"</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SDカードが空か、サポート対象外のファイルシステムを使用しています。"</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"破損したSDカード"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"破損したSDカード"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"SDカードが破損しています。カードのフォーマットが必要な可能性があります。"</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SDカードが破損しています。カードのフォーマットが必要な可能性があります。"</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"SDカードが予期せず取り外されました"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SDカードが予期せず取り外されました"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"データの喪失を防ぐためSDカードを取り外す前にマウントを解除してください。"</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"データの喪失を防ぐためSDカードを取り外す前にマウントを解除してください。"</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"SDカードを安全に取り外しました"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"SDカードを安全に取り外しました"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"SDカードを安全に取り外せます。"</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"SDカードは安全に取り外せます。"</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"SDカードが取り外されています"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"SDカードが取り外されています"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"SDカードがありません。メモリを使用する際はカードを挿入してください。"</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SDカードが取り外されました。新しいカードを挿入してください。"</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"空のSDカード"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SDカードが空か、サポート対象外のファイルシステムを使用しています。"</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"破損したSDカード"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SDカードが破損しています。カードのフォーマットが必要な可能性があります。"</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SDカードが予期せず取り外されました"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"データの喪失を防ぐためSDカードを取り外す前にマウントを解除してください。"</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SDカードを安全に取り外しました"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"SDカードは安全に取り外せます。"</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"SDカードが取り外されています"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SDカードが取り外されました。新しいカードを挿入してください。"</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"一致するアクティビティが見つかりません"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"コンポーネント使用状況に関する統計情報の更新"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"収集されたコンポーネント使用状況に関する統計情報の変更を許可します。通常のアプリケーションでは使用しません。"</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPNベースの事前共有鍵"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPNベースの証明書"</string>
     <string name="upload_file" msgid="2897957172366730416">"ファイルを選択"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"ファイルが選択されていません"</string>
     <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="tethered_notification_title" msgid="3146694234398202601">"テザリングまたはアクセスポイントが有効です"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"タップして設定する"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"戻る"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"次へ"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"スキップ"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"モバイルデータの使用量が増えています"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"タップしてモバイルデータ利用の詳細を表示します"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"モバイルデータの制限を超えました"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"タップしてモバイルデータ利用の詳細を表示します"</string>
+    <string name="no_matches" msgid="8129421908915840737">"該当なし"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"ページ内を検索"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1件一致"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g>/<xliff:g id="TOTAL">%d</xliff:g>件"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 75d0da1..3b9bd3f 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"개발 도구"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"애플리케이션 개발자에게만 필요한 기능입니다."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"저장"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"SD 카드에 액세스합니다."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"SD 카드에 액세스합니다."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"애플리케이션이 애플리케이션 캐시 디렉토리에 있는 파일을 삭제하여 휴대전화의 저장공간을 늘릴 수 있도록 합니다. 액세스는 일반적으로 시스템 프로세스로 제한됩니다."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"애플리케이션 리소스 이동"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"애플리케이션이 애플리케이션 리소스를 내부에서 외부 미디어로 또는 그 반대로 이동할 수 있도록 합니다."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"시스템 로그 파일 읽기"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"애플리케이션이 시스템의 다양한 로그 파일을 읽을 수 있도록 합니다. 이 경우 애플리케이션은 사용자가 휴대전화로 수행하는 작업에 대한 일반적인 정보를 검색할 수 있습니다. 하지만 로그 파일에 어떠한 개인정보도 포함되어서는 안 됩니다."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"진단 그룹 소유의 리소스 읽기/쓰기"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"애플리케이션이 진단 그룹 소유의 리소스(예: /dev에 있는 파일)를 읽고 쓸 수 있도록 합니다. 이 기능은 시스템 안정성 및 보안에 영향을 미칠 수 있으므로 제조업체 또는 사업자가 하드웨어 관련 진단을 수행하는 경우에만 사용해야 합니다."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"애플리케이션 구성 요소 사용 또는 사용 안함"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"애플리케이션이 로컬 Bluetooth 휴대전화를 구성한 다음 원격 장치를 검색하여 페어링할 수 있도록 합니다."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"Bluetooth 연결 만들기"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"애플리케이션이 로컬 Bluetooth 전화의 구성을 보고 페어링된 장치에 연결하며 연결을 수락할 수 있도록 합니다."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"키 잠금 사용 중지"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"애플리케이션이 키 잠금 및 관련 비밀번호 보안을 사용 중지할 수 있도록 합니다. 예를 들어, 휴대전화가 수신전화를 받을 때 키 잠금을 사용 중지했다가 통화가 끝나면 키 잠금을 다시 사용할 수 있습니다."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"동기화 설정 읽기"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"애플리케이션이 사용자 사전에 보관되어 있는 비공개 단어, 이름 및 구문을 읽도록 합니다."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"사용자정의 사전에 작성"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"애플리케이션이 사용자 사전에 새 단어를 입력할 수 있도록 합니다."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"SD 카드 콘텐츠 수정/삭제"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"SD 카드 콘텐츠 수정/삭제"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"애플리케이션이 SD 카드에 쓸 수 있도록 합니다."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"애플리케이션이 SD 카드에 쓸 수 있도록 합니다."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"SD 카드 콘텐츠 수정/삭제"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"애플리케이션이 SD 카드에 쓸 수 있도록 합니다."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"캐시 파일시스템 액세스"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"애플리케이션이 캐시 파일시스템을 읽고 쓸 수 있도록 합니다."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"비밀번호 제한"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"사용할 수 있는 비밀번호 유형을 제한합니다."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"로그인 시도 보기"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"몇 가지 작업을 수행하기 위해 기기에 대해 실패한 로그인 시도를 모니터링합니다."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"비밀번호 재설정"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"비밀번호를 새 값으로 강제 설정합니다. 이를 수행하려면 로그인하기 전에 관리자에게 새로 지정할 비밀번호 값을 요청해야 합니다."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"강제 잠금"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"기기가 잠겨 있을 때 작동하려면 비밀번호를 다시 입력해야 합니다."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"비밀번호 제한"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"사용할 수 있는 비밀번호 유형을 제한합니다."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"로그인 시도 보기"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"몇 가지 작업을 수행하기 위해 기기에 대해 실패한 로그인 시도를 모니터링합니다."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"비밀번호 재설정"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"비밀번호를 새 값으로 강제 설정합니다. 이를 수행하려면 로그인하기 전에 관리자에게 새로 지정할 비밀번호 값을 요청해야 합니다."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"강제 잠금"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"기기가 잠겨 있을 때 작동하려면 비밀번호를 다시 입력해야 합니다."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"모든 데이터 삭제"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"초기화를 수행하여 모든 데이터를 확인하지 않고 삭제합니다."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"초기화를 수행하여 모든 데이터를 확인하지 않고 삭제합니다."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"정책이 사용 설정되어 있는 동안 사용될 기기 글로벌 프록시를 설정합니다. 첫 번째 기기 관리자가 설정한 글로벌 프록시만 유효합니다."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"집"</item>
     <item msgid="869923650527136615">"모바일"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>을(를) 통해"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>(<xliff:g id="SOURCE">%2$s</xliff:g> 사용)"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN 코드 입력"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"잠금을 해제하려면 비밀번호 입력"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"잠금을 해제하려면 PIN 입력"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 코드가 잘못되었습니다."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"잠금해제하려면 메뉴를 누른 다음 0을 누릅니다."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"비상 전화번호"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"통화로 돌아가기"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"맞습니다."</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"죄송합니다. 다시 시도하세요."</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"죄송합니다. 다시 시도해 주세요."</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"충전 중(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"충전되었습니다."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 카드가 잠겨 있습니다."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM 카드 잠금해제 중..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"잠금해제 패턴을 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 그렸습니다. "\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>초 후에 다시 시도하세요."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"비밀번호를 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 입력했습니다. "\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>초 후에 다시 시도해 주세요."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"PIN을 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 입력했습니다. "\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>초 후에 다시 시도해 주세요."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"잠금해제 패턴을 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 그렸습니다. <xliff:g id="NUMBER_1">%d</xliff:g>회 더 실패하면 Google 로그인을 통해 휴대전화를 잠금해제하도록 요청됩니다. "\n\n" <xliff:g id="NUMBER_2">%d</xliff:g>초 후에 다시 시도하세요."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g>초 후에 다시 시도하세요."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"패턴을 잊으셨나요?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"다른 페이지를 탐색하시겠습니까?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"계속하려면 \'확인\'을 선택하고 현재 페이지에 그대로 있으려면 \'취소\'를 선택하세요."</string>
     <string name="save_password_label" msgid="6860261758665825069">"확인"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"도움말: 축소/확대하려면 두 번 누릅니다."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"브라우저의 기록 및 북마크 읽기"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"애플리케이션이 브라우저로 방문한 모든 URL과 브라우저의 모든 북마크를 읽도록 허용합니다."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"브라우저의 기록 및 북마크 쓰기"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"애플리케이션이 휴대전화에 저장된 브라우저 기록 또는 북마크를 수정할 수 있도록 허용합니다. 이 경우 악성 애플리케이션이 브라우저의 데이터를 지우거나 수정할 수 있습니다."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"브라우저 위치 정보 수정 권한"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"애플리케이션이 브라우저의 위치 정보 권한을 수정할 수 있도록 합니다. 악성 애플리케이션이 이를 사용하여 임의의 웹사이트에 위치 정보를 보낼 수도 있습니다."</string>
     <string name="save_password_message" msgid="767344687139195790">"브라우저에 이 비밀번호를 저장하시겠습니까?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"모두 선택"</string>
-    <string name="selectText" msgid="4862359311088898878">"단어 선택"</string>
     <string name="cut" msgid="3092569408438626261">"잘라내기"</string>
     <string name="copy" msgid="2681946229533511987">"복사"</string>
     <string name="paste" msgid="5629880836805036433">"붙여넣기"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL 복사"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"텍스트 선택..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"텍스트 선택"</string>
     <string name="inputMethod" msgid="1653630062304567879">"입력 방법"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"사전에 \'<xliff:g id="WORD">%s</xliff:g>\' 추가"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"텍스트 수정"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"텍스트 작업"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"저장공간 부족"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"휴대전화 저장공간이 부족합니다."</string>
     <string name="ok" msgid="5970060430562524910">"확인"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"확인"</string>
     <string name="no" msgid="5141531044935541497">"취소"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"주의"</string>
+    <string name="loading" msgid="1760724998928255250">"로드 중..."</string>
     <string name="capital_on" msgid="1544682755514494298">"사용"</string>
     <string name="capital_off" msgid="6815870386972805832">"사용 안함"</string>
     <string name="whichApplication" msgid="4533185947064773386">"작업을 수행할 때 사용하는 애플리케이션"</string>
@@ -763,9 +750,12 @@
     <string name="force_close" msgid="3653416315450806396">"닫기"</string>
     <string name="report" msgid="4060218260984795706">"신고"</string>
     <string name="wait" msgid="7147118217226317732">"대기"</string>
-    <string name="launch_warning_title" msgid="8323761616052121936">"애플리케이션 리디렉션됨"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g>이(가) 실행 중입니다."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"원래 <xliff:g id="APP_NAME">%1$s</xliff:g>을(를) 실행했습니다."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"모두 표시"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB 대용량 저장소"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB 연결됨"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"USB를 통해 휴대전화를 컴퓨터에 연결했습니다. 컴퓨터와 Android의 SD 카드 간에 파일을 복사하려면 아래의 버튼을 선택하세요."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"USB를 통해 휴대전화를 컴퓨터에 연결했습니다. 컴퓨터와 Android의 SD 카드 간에 파일을 복사하려면 아래의 버튼을 선택하세요."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"USB를 통해 휴대전화를 컴퓨터에 연결했습니다. 컴퓨터와 Android의 SD 카드 간에 파일을 복사하려면 아래의 버튼을 선택하세요."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"USB 저장소 사용"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"USB 저장소로 SD 카드를 사용하는 동안 문제가 발생했습니다."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"USB 저장소로 SD 카드를 사용하는 동안 문제가 발생했습니다."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"USB 저장소로 SD 카드를 사용하는 동안 문제가 발생했습니다."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB 연결됨"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"컴퓨터에 파일을 복사하거나 컴퓨터의 파일을 복사하려면 선택합니다."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USB 저장소 끄기"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"USB 저장소 끄기를 선택하세요."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USB 저장소 사용 중"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"USB 저장소를 사용하지 않도록 설정하기 전에 컴퓨터에서 Android의 SD 카드를 마운트 해제했는지(꺼냈는지) 확인하시기 바랍니다."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"USB 저장소를 사용하지 않도록 설정하기 전에 컴퓨터에서 Android의 SD 카드를 마운트 해제했는지(꺼냈는지) 확인하시기 바랍니다."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"USB 저장소를 사용하지 않도록 설정하기 전에 컴퓨터에서 Android의 SD 카드를 마운트 해제했는지(꺼냈는지) 확인하시기 바랍니다."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USB 저장소 사용 안함"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"USB 저장소를 사용하지 않도록 설정하는 동안 문제가 발생했습니다. USB 호스트와 연결을 해제했는지 확인한 다음 다시 시도하세요."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USB 저장소 사용"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"USB 저장소를 사용 설정하면 사용 중인 일부 애플리케이션이 중지되고 USB 저장소를 사용 중지할 때까지 사용할 수 없게 됩니다."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB 작업 실패"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"확인"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"SD 카드 포맷"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"SD 카드 포맷"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"SD 카드를 포맷하시겠습니까? 포맷하면 카드의 모든 데이터를 잃게 됩니다."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"SD 카드를 포맷하시겠습니까? 포맷하면 카드의 모든 데이터를 잃게 됩니다."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"SD 카드 포맷"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"SD 카드를 포맷하시겠습니까? 포맷하면 카드의 모든 데이터를 잃게 됩니다."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"포맷"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"USB 디버깅 연결됨"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"USB 디버깅을 사용하지 않으려면 선택합니다."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"가능한 원인"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"SD 카드 준비 중"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"SD 카드 준비 중"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"SD 카드 준비 중"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"오류 확인 중입니다."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"빈 SD 카드"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"빈 SD 카드"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"SD 카드가 비어 있거나 지원되지 않는 파일시스템을 사용 중입니다."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SD 카드가 비어 있거나 지원되지 않는 파일 시스템을 사용합니다."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"손상된 SD 카드"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"손상된 SD 카드"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"SD 카드가 손상되었습니다. 카드를 다시 포맷하시기 바랍니다."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD 카드가 손상되었습니다. 카드를 다시 포맷해야 할 수 있습니다."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"SD 카드가 예상치 않게 제거되었습니다."</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SD 카드가 예상치 않게 제거되었습니다."</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"데이터 손실을 피하려면 SD 카드를 제거하기 전에 마운트 해제합니다."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"데이터 손실을 피하려면 SD 카드를 제거하기 전에 마운트 해제합니다."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"SD 카드 제거 가능"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"SD 카드 제거 가능"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"이제 SD 카드를 안전하게 제거할 수 있습니다."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"안전하게 SD 카드를 제거할 수 있습니다."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"SD 카드 없음"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"SD 카드 없음"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"SD가 제거되었습니다. 기기의 저장 용량을 늘리려면 새 SD 카드를 삽입하세요."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD 카드가 없습니다.  SD 카드를 넣으세요."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"빈 SD 카드"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD 카드가 비어 있거나 지원되지 않는 파일 시스템을 사용합니다."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"손상된 SD 카드"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD 카드가 손상되었습니다. 카드를 다시 포맷해야 할 수 있습니다."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD 카드가 예상치 않게 제거되었습니다."</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"데이터 손실을 피하려면 SD 카드를 제거하기 전에 마운트 해제합니다."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SD 카드 제거 가능"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"안전하게 SD 카드를 제거할 수 있습니다."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"SD 카드 없음"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD 카드가 없습니다.  SD 카드를 넣으세요."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"일치하는 활동이 없습니다."</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"구성 요소 사용 통계 업데이트"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"수집된 구성요소 사용 통계를 수정할 수 있는 권한을 부여합니다. 일반 애플리케이션은 이 권한을 사용하지 않습니다."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"사전 공유 키 기반 L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"인증서 기반 L2TP/IPSec VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"파일 선택"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"파일을 선택하지 않았습니다."</string>
     <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="tethered_notification_title" msgid="3146694234398202601">"테더링 또는 핫스팟 사용"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"구성하려면 터치하세요."</string>
+    <string name="back_button_label" msgid="2300470004503343439">"뒤로"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"다음"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"건너뛰기"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"높은 모바일 데이터 사용량"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"모바일 데이터 사용에 대해 자세히 알아보려면 터치하세요."</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"모바일 데이터 제한을 초과했습니다."</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"모바일 데이터 사용에 대해 자세히 알아보려면 터치하세요."</string>
+    <string name="no_matches" msgid="8129421908915840737">"검색결과 없음"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"페이지에서 찾기"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"검색결과 1개"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g>/<xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-mcc450-ko/config.xml b/core/res/res/values-mcc450-ko/config.xml
new file mode 100644
index 0000000..335b967
--- /dev/null
+++ b/core/res/res/values-mcc450-ko/config.xml
@@ -0,0 +1,21 @@
+<?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.
+*/
+-->
+<resources>
+    <string name="gsm_alphabet_default_charset">euc-kr</string>
+</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 70ee877..831e183 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Utviklingsverktøy"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funksjonalitet kun utviklere trenger."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Lagring"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Tilgang til minnekortet."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Tilgang til minnekortet."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Lar applikasjonen frigjøre lagringsplass ved å slette filer i applikasjoners hurtigbufferkatalog. Tilgangen er vanligvis sterkt begrenset, til systemprosesser."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Flytter programressurser"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Gir et program tillatelse til å flytte programressurser fra interne til eksterne medier og omvendt."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"lese systemets loggfiler"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Lar applikasjonen lese fra diverse loggfiler på systemet. Disse inneholder generell informasjon om hva som gjøres med telefonen, men skal ikke inneholde personlig eller privat informasjon."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"lese/skrive ressurser eid av diag"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Lar applikasjonen lese og skrive enhver ressurs eid av gruppen diag; for eksempel, filer i /dev. Dette kan potensielt påvirke systemets sikkerhet og stabilitet. Dette bør KUN brukes for maskinvarespesifikke diagnoseverktøy laget av operatøren eller produsenten."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"aktivere eller deaktigere applikasjonskomponenter"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Lar applikasjonen konfigurere den lokale Bluetooth-telefonen, og å oppdage og pare med andre enheter."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"opprette Bluetooth-tilkoblinger"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Lar applikasjonen se konfigurasjonen til den lokale Bluetooth-telefonen, og å opprette og godta tilkoblinger med parede enheter."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"slå av tastaturlås"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Lar applikasjonen slå av tastaturlåsen og enhver tilknyttet passordsikkerhet. Et legitimt eksempel på dette er at telefonen slår av tastaturlåsen når den mottar et innkommende anrop, og så slår den på igjen når samtalen er over."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"lese synkroniseringsinnstillinger"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Lar applikasjonen lese private ord, navn og uttrykk som brukeren har lagret i den brukerdefinerte ordlisten."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"skrive til brukerdefinert ordliste"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Lar applikasjonen skrive nye ord til den brukerdefinerte ordlisten."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"redigere/slette innhold på minnekort"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"redigere/slette innhold på minnekort"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Lar applikasjonen skrive til minnekortet."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Lar applikasjonen skrive til minnekortet."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"redigere/slette innhold på minnekort"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Lar applikasjonen skrive til minnekortet."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"tilgang til bufrede filer"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Tillater et program å lese og skrive til bufrede filer."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Begrens passord"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Begrense typene passord du kan bruke."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Overvåk påloggingsforsøk"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Overvåk mislykkede påloggingsforsøk eller forsøk på handlinger på enheten."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Tilbakestill passordet"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Fremtving tilbakestilling av passord, slik at administratoren må gi deg et nytt passord når du skal logge deg på."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Obligatorisk låsing"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Kontrollerer når enheten låses. Du må skrive inn passordet på nytt for å låse den opp."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Begrens passord"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Begrense typene passord du kan bruke."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Overvåk påloggingsforsøk"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Overvåk mislykkede påloggingsforsøk eller forsøk på handlinger på enheten."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Tilbakestill passordet"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Fremtving tilbakestilling av passord, slik at administratoren må gi deg et nytt passord når du skal logge deg på."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Obligatorisk låsing"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Kontrollerer når enheten låses. Du må skrive inn passordet på nytt for å låse den opp."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Slett alle data"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Utfører tilbakestilling til fabrikkstandard. Alle data slettes uten varsel."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Utfører tilbakestilling til fabrikkstandard. Alle data slettes uten varsel."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Angir den globale mellomtjeneren på enheten som skal brukes når regelen er aktivert. Kun den opprinnelige administratoren av enheten kan angi den globale mellomtjeneren."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Hjemmenummer"</item>
     <item msgid="869923650527136615">"Mobil"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Skriv inn PIN-kode:"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Skriv inn passord for å låse opp"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Skriv inn personlig kode for å låse opp"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Gal PIN-kode!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"For å låse opp, trykk på menyknappen og deretter 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nødnummer"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Tilbake til samtale"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Riktig!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Beklager, prøv igjen:"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Prøv igjen"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Lader (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Fullt ladet"</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kortet er låst."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Låser opp SIM-kort…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%d</xliff:g> times. "\n\n"Please try again in <xliff:g id="NUMBER_1">%d</xliff:g> seconds."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har oppgitt feil passord <xliff:g id="NUMBER_0">%d</xliff:g> ganger. "\n\n"Prøv igjen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Du har oppgitt feil personlig kode <xliff:g id="NUMBER_0">%d</xliff:g> ganger. "\n\n"Prøv igjen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%d</xliff:g> times. After <xliff:g id="NUMBER_1">%d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using your Google sign-in."\n\n" Please try again in <xliff:g id="NUMBER_2">%d</xliff:g> seconds."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Prøv igjen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Glemt mønsteret?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Naviger bort fra denne siden?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Velg OK for å fortsette, eller Avbryt for å forbli på denne siden."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Bekreft"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Dobbelttrykk for å zoome inn og ut."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"lese nettleserens logg og bokmerker"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Lar applikasjonen lese alle adresser nettleseren har besøkt, og alle nettleserens bokmerker."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skrive til nettleserens logg og bokmerker"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Lar applikasjonen endre nettleserens logg og bokmerker lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å fjerne eller redigere nettleserens data."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Endre nettleserens tillatelser for geografisk posisjonering"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Tillater programmet å endre nettleserens tillatelser for geografisk posisjonering. Skadelige programmer kan bruke denne funksjonen til å sende posisjonsopplysninger til vilkårlige nettsteder."</string>
     <string name="save_password_message" msgid="767344687139195790">"Ønsker du at nettleseren skal huske dette passordet?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Merk alt"</string>
-    <string name="selectText" msgid="4862359311088898878">"Velg ord"</string>
     <string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
     <string name="copy" msgid="2681946229533511987">"Kopier"</string>
     <string name="paste" msgid="5629880836805036433">"Lim inn"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopier URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Marker tekst"</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Merket tekst"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Inndatametode"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Legg «<xliff:g id="WORD">%s</xliff:g>» til ordlisten"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Rediger tekst"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksthandlinger"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Lite plass"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Det begynner å bli lite lagringsplass på telefonen."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Avbryt"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Merk"</string>
+    <string name="loading" msgid="1760724998928255250">"Laster inn ..."</string>
     <string name="capital_on" msgid="1544682755514494298">"På"</string>
     <string name="capital_off" msgid="6815870386972805832">"Av"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Fullfør med"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Programmet er omdirigert"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> kjører nå."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> ble opprinnelig startet."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Vis alle"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-masselagring"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB koblet til"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Du har koblet telefonen til datamaskinen via USB. Velg knappen nedenfor hvis du vil kopiere filer mellom datamaskinen og minnekortet i telefonen."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Du har koblet telefonen til datamaskinen via USB. Velg knappen nedenfor hvis du vil kopiere filer mellom datamaskinen og minnekortet i telefonen."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Du har koblet telefonen til datamaskinen via USB. Velg knappen nedenfor hvis du vil kopiere filer mellom datamaskinen og minnekortet i telefonen."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Slå på USB-lagring"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Det oppsto et problem med å bruke minnekortet ditt for USB-lagring."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Det oppsto et problem med å bruke minnekortet ditt for USB-lagring."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Det oppsto et problem med å bruke minnekortet ditt for USB-lagring."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB tilkoblet"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Velg om du ønsker å kopiere filer til/fra en datamaskin."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Slå av USB-lagring"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Velg for å slå av USB-lagring."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-lagring er i bruk"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Før du slår av USB-lagring, sjekk at du har avmontert telefonen på datamaskinen."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Før du slår av USB-lagring, sjekk at du har avmontert telefonen på datamaskinen."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Før du slår av USB-lagring, sjekk at du har avmontert telefonen på datamaskinen."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Slå av USB-lagring"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Det oppstod et problem ved deaktivering av USB-lagring. Kontroller at du har demontert USB-verten, og prøv på nytt."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Slå på USB-lagring"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Hvis du aktiverer USB-lagring, virker ikke lenger enkelte av programmene du bruker, og de kan være utilgjengelige inntil du deaktiverer USB-lagringen."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB-operasjonen mislyktes"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formatere minnekort"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formatere minnekort"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Er du sikker på at du ønsker å formatere minnekortet? Alle data på kortet vil gå tapt."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Er du sikker på at du ønsker å formatere minnekortet? Alle data på kortet vil gå tapt."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formatere minnekort"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Er du sikker på at du ønsker å formatere minnekortet? Alle data på kortet vil gå tapt."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatér"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-debugging tilkoblet"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Velg for å deaktivere USB-debugging."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ"</string>
     <string name="candidates_style" msgid="4333913089637062257">"TAG_FONT"<u>"kandidater"</u>"CLOSE_FONT"</string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Forbereder minnekort"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Forbereder minnekort"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Forbereder minnekort"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Sjekker for feil."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Tomt minnekort"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Tomt minnekort"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"Minnekortet er tomt eller bruker et ustøttet filsystem."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Minnekortet er tomt eller har et ikke-støttet filsystem."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Skadet minnekort"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Skadet minnekort"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"Minnekortet er skadet. Det kan være du må formatere kortet."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"Minnekortet er skadet. Du må kanskje formatere det."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Minnekortet ble tatt ut uventet"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Minnekortet ble tatt ut uventet"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Avmonter minnekortet før det tas ut, for å unngå datatap."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Avmonter minnekortet før det tas ut, for å unngå datatap."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"Trygt å ta ut minnekort"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"Trygt å ta ut minnekort"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"Minnekortet kan nå trygt tas ut."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Det er trygt å ta ut minnekortet."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Minnekortet ble tatt ut"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Minnekortet ble tatt ut"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"Minnekortet ble tatt ut. Sett inn et nytt minnekort for å øke lagringsplassen."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"Minnekortet ble fjernet. Sett inn et nytt."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Tomt minnekort"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"Minnekortet er tomt eller har et ikke-støttet filsystem."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Skadet minnekort"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"Minnekortet er skadet. Du må kanskje formatere det."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Minnekortet ble tatt ut uventet"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Avmonter minnekortet før det tas ut, for å unngå datatap."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"Trygt å ta ut minnekort"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Det er trygt å ta ut minnekortet."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Minnekortet ble tatt ut"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"Minnekortet ble fjernet. Sett inn et nytt."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Fant ingen tilsvarende aktiviteter"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"oppdater statistikk over komponentbruk"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Tillater endring av innsamlet data om bruk av komponenter. Ikke ment for vanlige applikasjoner."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Passordbasert L2TP/IPSec-VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sertifikatbasert L2TP/IPSec-VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"Velg fil"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
     <string name="reset" msgid="2448168080964209908">"Tilbakestill"</string>
     <string name="submit" msgid="1602335572089911941">"Send inn"</string>
-    <string name="description_star" msgid="2654319874908576133">"favoritt"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Bilmodus er aktivert"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Velg for å avslutte bilmodus"</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Tilknytning eller trådløs sone er aktiv"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Trykk for å konfigurere"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Tilbake"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Neste"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Hopp over"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Høy mobildatabruk"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Berør for å lese mer om bruk av mobildata"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Grensen for mobildatabruk er overskredet"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Berør for å lese mer om bruk av mobildata"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Ingen treff"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Finn på side"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 treff"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> av <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 2d07166..28fdcc1 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Ontwikkelingshulpprogramma\'s"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Functies die alleen door toepassingsontwikkelaars worden gebruikt."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Opslagruimte"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Toegang tot de SD-kaart."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Toegang tot de SD-kaart."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Hiermee kan een toepassing opslagruimte op de telefoon vrij maken door bestanden te verwijderen uit de cachemap van de toepassing. De toegang is doorgaans beperkt tot het systeemproces."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Toepassingsbronnen verplaatsen"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Een toepassing toestaan toepassingsbronnen te verplaatsen van interne naar externe media en omgekeerd."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"systeemlogbestanden lezen"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Hiermee kan een toepassing de verschillende logbestanden van het systeem lezen. De toepassing kan op deze manier algemene informatie achterhalen over uw telefoongebruik. Hierin is geen persoonlijke of privé-informatie opgenomen."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"lezen/schrijven naar bronnen van diag"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Hiermee kan een toepassing lezen en schrijven naar elke bron die hoort bij de diagnostische groep, zoals bestanden in /dev. Hierdoor kan de systeemstabiliteit en -veiligheid worden beïnvloed. Dit mag ALLEEN worden gebruikt voor hardwarespecifieke diagnostiek door de fabrikant of operator."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"toepassingscomponenten in- of uitschakelen"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Hiermee kan een toepassing de lokale Bluetooth-telefoon configureren en externe apparaten zoeken en aansluiten."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"Bluetooth-verbindingen maken"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Hiermee kan een toepassing de configuratie van een lokale Bluetooth-telefoon bekijken en verbindingen met gekoppelde apparaten maken en accepteren."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"toetsvergrendeling uitschakelen"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Hiermee kan een toepassing de toetsvergrendeling en bijbehorende wachtwoordbeveiliging uitschakelen. Een voorbeeld: de telefoon schakelt de toetsvergrendeling uit als er een oproep binnenkomt en schakelt de toetsvergrendeling weer in als de oproep is beëindigd."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"synchronisatie-instellingen lezen"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Hiermee kan een toepassing privéwoorden, namen en woordcombinaties lezen die de gebruiker heeft opgeslagen in het gebruikerswoordenboek."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"schrijven naar door gebruiker gedefinieerd woordenboek"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Hiermee kan een toepassing nieuwe woorden schrijven naar het gebruikerswoordenboek."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"inhoud op de SD-kaart aanpassen/verwijderen"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"inhoud op de SD-kaart aanpassen/verwijderen"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Hiermee kan een toepassing schrijven naar de SD-kaart."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Hiermee kan een toepassing schrijven naar de SD-kaart."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"inhoud op de SD-kaart aanpassen/verwijderen"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Hiermee kan een toepassing schrijven naar de SD-kaart."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"het cachebestandssysteem openen"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Staat een toepassing toe het cachebestandssysteem te lezen en te schrijven."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Wachtwoord beperken"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"De typen wachtwoorden beperken die u mag gebruiken."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Aanmeldingspogingen controleren"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Mislukte pogingen controleren voor aanmelding bij het apparaat om een bepaalde actie uit te voeren."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Wachtwoord opnieuw instellen"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Uw wachtwoord gedwongen wijzigen in een nieuwe waarde, wat vereist dat de beheerder u het wachtwoord geeft voordat u zich kunt aanmelden."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Gedwongen vergrendelen"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Beheren wanneer het apparaat wordt vergrendeld, wat vereist dat u het wachtwoord opnieuw invoert."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Wachtwoord beperken"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"De typen wachtwoorden beperken die u mag gebruiken."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Aanmeldingspogingen controleren"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Mislukte pogingen controleren voor aanmelding bij het apparaat om een bepaalde actie uit te voeren."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Wachtwoord opnieuw instellen"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Uw wachtwoord gedwongen wijzigen in een nieuwe waarde, wat vereist dat de beheerder u het wachtwoord geeft voordat u zich kunt aanmelden."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Gedwongen vergrendelen"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Beheren wanneer het apparaat wordt vergrendeld, wat vereist dat u het wachtwoord opnieuw invoert."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Alle gegevens wissen"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"De fabrieksinstellingen herstellen, waarbij alle gegevens worden verwijderd zonder bevestiging."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"De fabrieksinstellingen herstellen, waarbij alle gegevens worden verwijderd zonder bevestiging."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Stel de algemene proxy voor het apparaat in die moet worden gebruikt terwijl het beleid is geactiveerd. Alleen de eerste apparaatbeheerder stelt de algemene proxy is."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Thuis"</item>
     <item msgid="869923650527136615">"Mobiel"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-code invoeren"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Voer het wachtwoord in om te ontgrendelen"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Voer de PIN-code in om te ontgrendelen"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Onjuiste PIN-code!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Druk op \'Menu\' en vervolgens op 0 om te ontgrendelen."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Alarmnummer"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Terug naar gesprek"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Juist!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Probeer het opnieuw"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Probeer het opnieuw"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Opladen (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Opgeladen."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kaart is vergrendeld."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM-kaart ontgrendelen..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"U heeft uw ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. "\n\n"Probeer het over <xliff:g id="NUMBER_1">%d</xliff:g> seconden opnieuw."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"U heeft uw wachtwoord <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist ingevoerd. "\n\n"Probeer het over <xliff:g id="NUMBER_1">%d</xliff:g> seconden opnieuw."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"U heeft uw PIN-code <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist ingevoerd. "\n\n"Probeer het over <xliff:g id="NUMBER_1">%d</xliff:g> seconden opnieuw."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"U heeft uw ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen, wordt u gevraagd om uw telefoon te ontgrendelen met uw Google aanmelding."\n\n" Probeer het over <xliff:g id="NUMBER_2">%d</xliff:g> seconden opnieuw."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Probeer het over <xliff:g id="NUMBER">%d</xliff:g> seconden opnieuw."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Patroon vergeten?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Wilt u deze pagina verlaten?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Kies OK om door te gaan of Annuleren om op de huidige pagina te blijven."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Bevestigen"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Tip: tik tweemaal om in of uit te zoomen."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"browsergeschiedenis en bladwijzers lezen"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Hiermee kan een toepassing de URL\'s lezen die u via de browser heeft bezocht, evenals alle bladwijzers van de browser."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"browsergeschiedenis en bladwijzers schrijven"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Hiermee kan een toepassing de op uw telefoon opgeslagen browsergeschiedenis of bladwijzers wijzigen. Schadelijke toepassingen kunnen hiermee uw browsergegevens verwijderen of wijzigen."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Geolocatierechten voor browser aanpassen"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Staat een toepassing toe de geolocatierechten van de browser aan te passen. Schadelijke toepassingen kunnen dit gebruiken om locatiegegevens te verzenden naar willekeurige websites."</string>
     <string name="save_password_message" msgid="767344687139195790">"Wilt u dat de browser dit wachtwoord onthoudt?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Alles selecteren"</string>
-    <string name="selectText" msgid="4862359311088898878">"Woord selecteren"</string>
     <string name="cut" msgid="3092569408438626261">"Knippen"</string>
     <string name="copy" msgid="2681946229533511987">"Kopiëren"</string>
     <string name="paste" msgid="5629880836805036433">"Plakken"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL kopiëren"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Tekst selecteren..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Tekstselectie"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Invoermethode"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"\'<xliff:g id="WORD">%s</xliff:g>\' toevoegen aan woordenboek"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Tekst bewerken"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Tekstacties"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Weinig ruimte"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Opslagruimte van telefoon raakt op."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Annuleren"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Let op"</string>
+    <string name="loading" msgid="1760724998928255250">"Laden..."</string>
     <string name="capital_on" msgid="1544682755514494298">"AAN"</string>
     <string name="capital_off" msgid="6815870386972805832">"UIT"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Actie voltooien met"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Toepassing omgeleid"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> is nu actief."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> was het eerst gestart."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Alles weergeven"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-massaopslag"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB-verbinding"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"U heeft uw telefoon via USB op uw computer aangesloten. Selecteer de onderstaande knop als u bestanden tussen uw computer en de SD-kaart van uw Android wilt kopiëren."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"U heeft uw telefoon via USB op uw computer aangesloten. Selecteer de onderstaande knop als u bestanden tussen uw computer en de SD-kaart van uw Android wilt kopiëren."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"U heeft uw telefoon via USB op uw computer aangesloten. Selecteer de onderstaande knop als u bestanden tussen uw computer en de SD-kaart van uw Android wilt kopiëren."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"USB-opslag inschakelen"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Er is een probleem bij het gebruik van uw SD-kaart voor USB-opslag."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Er is een probleem bij het gebruik van uw SD-kaart voor USB-opslag."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Er is een probleem bij het gebruik van uw SD-kaart voor USB-opslag."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB-verbinding"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Selecteer dit om bestanden naar/van uw computer te kopiëren."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USB-opslag uitschakelen"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Selecteer dit om USB-opslag uit te schakelen."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-opslag in gebruik"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Voordat u USB-opslag uitschakelt, moet u de SD-kaart van uw Android hebben ontkoppeld (\'uitgeworpen\') van uw computer."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Voordat u USB-opslag uitschakelt, moet u de SD-kaart van uw Android hebben ontkoppeld (\'uitgeworpen\') van uw computer."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Voordat u USB-opslag uitschakelt, moet u de SD-kaart van uw Android hebben ontkoppeld (\'uitgeworpen\') van uw computer."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USB-opslag uitschakelen"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Er is een probleem opgetreden tijdens het uitschakelen van de USB-opslag. Controleer of u de USB-host heeft losgekoppeld en probeer het opnieuw."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USB-opslag inschakelen"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Als u USB-opslag inschakelt, worden bepaalde toepassingen die u gebruikt, gestopt en worden deze mogelijk pas weer beschikbaar wanneer u USB-opslag uitschakelt."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB-bewerking mislukt"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"SD-kaart formatteren"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"SD-kaart formatteren"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Weet u zeker dat u de SD-kaart wilt formatteren? Alle gegevens op uw kaart gaan dan verloren."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Weet u zeker dat u de SD-kaart wilt formatteren? Alle gegevens op uw kaart gaan dan verloren."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"SD-kaart formatteren"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Weet u zeker dat u de SD-kaart wilt formatteren? Alle gegevens op uw kaart gaan dan verloren."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatteren"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-foutopsporing verbonden"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Selecteer deze optie om USB-foutopsporing uit te schakelen."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"kandidaten"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"SD-kaart voorbereiden"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"SD-kaart voorbereiden"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"SD-kaart voorbereiden"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Controleren op fouten."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Lege SD-kaart"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Lege SD-kaart"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"De SD-kaart is leeg of gebruikt een niet-ondersteund bestandssysteem."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SD-kaart is leeg of heeft een niet-ondersteund bestandssysteem."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Beschadigde SD-kaart"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Beschadigde SD-kaart"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"De SD-kaart is beschadigd. U moet de kaart mogelijk opnieuw formatteren."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD-kaart beschadigd. U moet de kaart mogelijk opnieuw formatteren."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"SD-kaart onverwachts verwijderd"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SD-kaart onverwachts verwijderd"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Ontkoppel de SD-kaart voordat u deze verwijdert om gegevensverlies te voorkomen."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Ontkoppel de SD-kaart voordat u deze verwijdert om gegevensverlies te voorkomen."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"De SD-kaart kan veilig worden verwijderd"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"De SD-kaart kan veilig worden verwijderd"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"De SD-kaart kan nu veilig worden verwijderd."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"U kunt de SD-kaart veilig verwijderen."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"SD-kaart is verwijderd"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"SD-kaart is verwijderd"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"De SD-kaart is verwijderd. Plaats een nieuwe SD-kaart om de opslagcapaciteit van uw apparaat te vergroten."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD-kaart verwijderd. Plaats een nieuwe."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Lege SD-kaart"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD-kaart is leeg of heeft een niet-ondersteund bestandssysteem."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Beschadigde SD-kaart"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD-kaart beschadigd. U moet de kaart mogelijk opnieuw formatteren."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD-kaart onverwachts verwijderd"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Ontkoppel de SD-kaart voordat u deze verwijdert om gegevensverlies te voorkomen."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"De SD-kaart kan veilig worden verwijderd"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"U kunt de SD-kaart veilig verwijderen."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"SD-kaart is verwijderd"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD-kaart verwijderd. Plaats een nieuwe."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Geen overeenkomende activiteiten gevonden"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"gebruiksstatistieken van component bijwerken"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Hiermee kunnen verzamelde gebruiksstatistieken van een component worden gewijzigd. Niet voor gebruik door normale toepassingen."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Vooraf gedeelde sleutel op basis van L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificaat op basis van L2TP/IPSec VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"Bestand kiezen"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Geen bestand geselecteerd"</string>
     <string name="reset" msgid="2448168080964209908">"Opnieuw instellen"</string>
     <string name="submit" msgid="1602335572089911941">"Verzenden"</string>
-    <string name="description_star" msgid="2654319874908576133">"favoriet"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Automodus ingeschakeld"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Selecteer dit om de automodus af te sluiten."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Tethering of hotspot actief"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Aanraken om te configureren"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Vorige"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Volgende"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Overslaan"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Hoog mobiel gegevensgebruik"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Raak aan voor meer informatie over mobiel gegevensgebruik"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Mobiele gegevenslimiet overschreden"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Raak aan voor meer informatie over mobiel gegevensgebruik"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Geen overeenkomsten"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Zoeken op pagina"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 overeenkomst"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> van <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 7604c61..33b03ad 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Narzędzia programistyczne"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funkcje potrzebne jedynie programistom"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Pamięć"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Dostęp do karty SD."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Dostęp do karty SD."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Pozwala aplikacji na zwalnianie pamięci telefonu przez usuwanie plików z katalogu pamięci podręcznej aplikacji. Dostęp jest bardzo ograniczony, z reguły do procesów systemu."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Przenoszenie zasobów aplikacji"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Zezwala aplikacji na przeniesienie zasobów aplikacji z nośnika wewnętrznego na zewnętrzny i odwrotnie."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"czytanie plików dziennika systemu"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Umożliwia aplikacji czytanie różnych plików dziennika systemowego. Pozwala to na uzyskanie ogólnych informacji o czynnościach wykonywanych w telefonie, ale bez ujawniania danych osobowych lub osobistych informacji."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"czytanie/zapisywanie w zasobach należących do diagnostyki"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Pozwala aplikacji na czytanie i zapisywanie we wszystkich zasobach posiadanych przez diagnozowaną grupę, jak na przykład pliki w katalogu /dev. Może to potencjalnie wpłynąć na stabilność i bezpieczeństwo systemu. Powinno być wykorzystywane TYLKO w celach diagnozowania sprzętu przez producenta lub operatora."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"włączanie lub wyłączanie składników aplikacji"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Pozwala aplikacji na konfigurowanie lokalnego telefonu Bluetooth, wyszukiwanie urządzeń zdalnych i łączenie się z nimi."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"tworzenie połączeń Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Pozwala aplikacji na wyświetlanie konfiguracji lokalnego telefonu Bluetooth oraz na tworzenie i akceptowanie połączeń ze sparowanymi urządzeniami."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"wyłączanie blokady klawiatury"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Pozwala aplikacji na wyłączenie blokady klawiatury i wszystkich związanych z tym haseł zabezpieczających. Typowym przykładem takiego działania jest wyłączanie blokady klawiatury, gdy pojawia się połączenie przychodzące, a następnie ponowne jej włączanie po zakończeniu połączenia."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"czytanie ustawień synchronizowania"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Zezwala aplikacji na odczytywanie wszelkich prywatnych słów, nazw i wyrażeń zapisanych przez użytkownika w swoim słowniku."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"zapisywanie w słowniku zdefiniowanym przez użytkownika"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Zezwala aplikacjom na zapisywanie nowych słów w słowniku użytkownika."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"modyfikowanie/usuwanie zawartości karty SD"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"modyfikowanie/usuwanie zawartości karty SD"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Umożliwia aplikacji zapis na karcie SD."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Umożliwia aplikacji zapis na karcie SD."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modyfikowanie/usuwanie zawartości karty SD"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Umożliwia aplikacji zapis na karcie SD."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"dostęp do systemu plików pamięci podręcznej"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Zezwala aplikacji na odczyt i zapis w systemie plików pamięci podręcznej."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Ogranicz liczbę prób wprowadzenia hasła"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Ogranicza dozwolone typy haseł użytkownika."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Monitoruj próby logowania"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Monitoruje nieudane próby zalogowania do urządzenia w celu wykonania określonego działania."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Resetuj hasło"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Wymusza podanie nowej wartości hasła, wymagając przekazania go użytkownikowi przez administratora przed zalogowaniem."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Wymuś zablokowanie"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Kontroluje, kiedy urządzenie jest blokowane, wymagając ponownego wprowadzenia hasła."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Ogranicz liczbę prób wprowadzenia hasła"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Ogranicza dozwolone typy haseł użytkownika."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Monitoruj próby logowania"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Monitoruje nieudane próby zalogowania do urządzenia w celu wykonania określonego działania."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Resetuj hasło"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Wymusza podanie nowej wartości hasła, wymagając przekazania go użytkownikowi przez administratora przed zalogowaniem."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Wymuś zablokowanie"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Kontroluje, kiedy urządzenie jest blokowane, wymagając ponownego wprowadzenia hasła."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Usuń wszystkie dane"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Wykonuje reset fabryczny, usuwając wszystkie dane użytkownika bez żadnego potwierdzenia."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Wykonuje reset fabryczny, usuwając wszystkie dane użytkownika bez żadnego potwierdzenia."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Ustaw globalny serwer proxy urządzenia do używania przy włączonych zasadach. Tylko pierwszy administrator urządzenia ustawia obowiązujący globalny serwer proxy."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Dom"</item>
     <item msgid="869923650527136615">"Komórka"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"przez <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> przez <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Wprowadź kod PIN"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Wprowadź hasło, aby odblokować"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Wprowadź kod PIN, aby odblokować"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Błędny kod PIN!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Aby odblokować, naciśnij Menu, a następnie 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numer alarmowy"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Powrót do połączenia"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Poprawnie!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Niestety, spróbuj ponownie"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Spróbuj ponownie"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Ładowanie (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Naładowany."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Karta SIM jest zablokowana."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Odblokowywanie karty SIM..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Wzór odblokowania został nieprawidłowo narysowany <xliff:g id="NUMBER_0">%d</xliff:g> razy. "\n\n"Spróbuj ponownie za <xliff:g id="NUMBER_1">%d</xliff:g> sekund."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Hasło zostało nieprawidłowo wprowadzone <xliff:g id="NUMBER_0">%d</xliff:g> razy. "\n\n"Spróbuj ponownie za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Niepoprawnie wprowadzono kod PIN <xliff:g id="NUMBER_0">%d</xliff:g> razy. "\n\n"Spróbuj ponownie za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Wzór odblokowania został narysowany nieprawidłowo <xliff:g id="NUMBER_0">%d</xliff:g> razy. Po kolejnych <xliff:g id="NUMBER_1">%d</xliff:g> nieudanych próbach telefon trzeba będzie odblokować przez zalogowanie na koncie Google."\n\n" Spróbuj ponownie za <xliff:g id="NUMBER_2">%d</xliff:g> sekund."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Spróbuj ponownie za <xliff:g id="NUMBER">%d</xliff:g> sekund."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Zapomniałeś wzoru?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Czy opuścić tę stronę?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Wybierz opcję OK, aby kontynuować, lub opcję Anuluj, aby pozostać na tej stronie."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Potwierdź"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Wskazówka: dotknij dwukrotnie, aby powiększyć lub pomniejszyć."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"odczyt historii i zakładek przeglądarki"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Umożliwia aplikacji odczyt wszystkich adresów URL odwiedzonych przez przeglądarkę, a także wszystkich zakładek przeglądarki."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"zapis historii i zakładek przeglądarki"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Umożliwia aplikacji modyfikowanie historii lub zakładek przeglądarki zapisanych w telefonie. Złośliwe aplikacje mogą używać tej opcji do usuwania lub modyfikowania danych przeglądarki."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modyfikowanie uprawnień przeglądarki dotyczących lokalizacji geograficznej"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Zezwala aplikacji na modyfikowanie uprawnień przeglądarki dotyczących lokalizacji geograficznej. Złośliwe aplikacje mogą używać tej opcji do wysyłania informacji o lokalizacji do dowolnych witryn internetowych."</string>
     <string name="save_password_message" msgid="767344687139195790">"Czy chcesz, aby zapamiętać to hasło w przeglądarce?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Zaznacz wszystko"</string>
-    <string name="selectText" msgid="4862359311088898878">"Zaznacz wyraz"</string>
     <string name="cut" msgid="3092569408438626261">"Wytnij"</string>
     <string name="copy" msgid="2681946229533511987">"Kopiuj"</string>
     <string name="paste" msgid="5629880836805036433">"Wklej"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopiuj adres URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Zaznacz tekst"</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Zaznaczanie tekstu"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metoda wprowadzania"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Dodaj termin „<xliff:g id="WORD">%s</xliff:g>” do słownika"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Edytuj tekst"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Działania na tekście"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Mało miejsca"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Maleje ilość dostępnej pamięci telefonu."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Anuluj"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Uwaga"</string>
+    <string name="loading" msgid="1760724998928255250">"Wczytywanie..."</string>
     <string name="capital_on" msgid="1544682755514494298">"Włącz"</string>
     <string name="capital_off" msgid="6815870386972805832">"Wyłącz"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Zakończ czynność korzystając z"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Aplikacja przekierowana"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> jest uruchomiona."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> została pierwotnie uruchomiona."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Pokaż wszystko"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"Pamięć masowa USB"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"Połączenie przez USB"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Telefon został połączony z komputerem za pośrednictwem USB. Wybierz poniższy przycisk, aby skopiować pliki między komputerem a kartą SD systemu Android."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Telefon został połączony z komputerem za pośrednictwem USB. Wybierz poniższy przycisk, aby skopiować pliki między komputerem a kartą SD systemu Android."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Telefon został połączony z komputerem za pośrednictwem USB. Wybierz poniższy przycisk, aby skopiować pliki między komputerem a kartą SD systemu Android."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Włącz nośnik USB"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Wystąpił problem z wykorzystaniem karty SD dla pamięci USB."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Wystąpił problem z wykorzystaniem karty SD dla pamięci USB."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Wystąpił problem z wykorzystaniem karty SD dla pamięci USB."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"Połączenie przez USB"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Wybierz, aby skopiować pliki do/z komputera"</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Wyłącz nośnik USB"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Wybierz, aby wyłączyć nośnik USB."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"Nośnik USB w użyciu"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Przed wyłączeniem nośnika USB upewnij się, że karta SD systemu Android została odłączona („wyjęta”) od komputera."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Przed wyłączeniem nośnika USB upewnij się, że karta SD systemu Android została odłączona („wyjęta”) od komputera."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Przed wyłączeniem nośnika USB upewnij się, że karta SD systemu Android została odłączona („wyjęta”) od komputera."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Wyłącz nośnik USB"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Wystąpił problem podczas wyłączania nośnika USB. Upewnij się, że host USB został odłączony, a następnie spróbuj ponownie."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Włącz nośnik USB"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Po włączeniu nośnika USB niektóre używane aplikacje zostaną zatrzymane i mogą być niedostępne do chwili wyłączenia nośnika USB."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"Operacja USB nie powiodła się"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formatuj kartę SD"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formatuj kartę SD"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Czy na pewno sformatować kartę SD? Wszystkie dane na karcie zostaną utracone."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Czy na pewno sformatować kartę SD? Wszystkie dane na karcie zostaną utracone."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formatuj kartę SD"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Czy na pewno sformatować kartę SD? Wszystkie dane na karcie zostaną utracone."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatuj"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Podłączono moduł debugowania USB"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Wybierz, aby wyłączyć debugowanie USB."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" AĄBCĆDEĘFGHIJKLŁMNŃOÓPQRSŚTUVWXYZŹŻ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"kandydaci"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Przygotowywanie karty SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Przygotowywanie karty SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Przygotowywanie karty SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Sprawdzanie w poszukiwaniu błędów."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Pusta karta SD"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Pusta karta SD"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"Karta SD jest pusta lub używa nieobsługiwanego systemu plików."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Karta SD jest pusta lub zawiera nieobsługiwany system plików."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Uszkodzona karta SD"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Uszkodzona karta SD"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"Karta SD jest uszkodzona. Konieczne może być przeformatowanie karty."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"Karta SD jest uszkodzona. Konieczne może być ponowne sformatowanie."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Karta SD została nieoczekiwanie wyjęta"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Karta SD została nieoczekiwanie wyjęta"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Odłącz kartę SD przed jej wyjęciem, aby uniknąć utraty danych."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Odłącz kartę SD przed jej wyjęciem, aby uniknąć utraty danych."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"Można bezpiecznie usunąć kartę SD"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"Można bezpiecznie usunąć kartę SD"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"Można teraz bezpiecznie usunąć kartę SD."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Możesz bezpiecznie wyjąć kartę SD."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Usunięta karta SD"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Usunięta karta SD"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"Karta SD została usunięta. Włóż nową kartę SD, aby zwiększyć pamięć urządzenia."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"Karta SD została wyjęta. Włóż nową kartę."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Pusta karta SD"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"Karta SD jest pusta lub zawiera nieobsługiwany system plików."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Uszkodzona karta SD"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"Karta SD jest uszkodzona. Konieczne może być ponowne sformatowanie."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Karta SD została nieoczekiwanie wyjęta"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Odłącz kartę SD przed jej wyjęciem, aby uniknąć utraty danych."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"Można bezpiecznie usunąć kartę SD"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Możesz bezpiecznie wyjąć kartę SD."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Usunięta karta SD"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"Karta SD została wyjęta. Włóż nową kartę."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Nie znaleziono pasujących działań"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"aktualizowanie statystyk użycia komponentu"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Zezwala na modyfikacje zebranych statystyk użycia komponentu. Nieprzeznaczone dla zwykłych aplikacji."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Sieć VPN L2TP/IPSec z kluczem PSK"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sieć VPN L2TP/IPSec z certyfikatem"</string>
     <string name="upload_file" msgid="2897957172366730416">"Wybierz plik"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Nie wybrano pliku"</string>
     <string name="reset" msgid="2448168080964209908">"Resetuj"</string>
     <string name="submit" msgid="1602335572089911941">"Prześlij"</string>
-    <string name="description_star" msgid="2654319874908576133">"ulubione"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Tryb samochodowy włączony"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Wybierz, aby zakończyć tryb samochodowy."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Jest aktywne powiązanie lub punkt dostępu"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Dotknij, aby skonfigurować"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Wróć"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Dalej"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Pomiń"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Wysoki poziom użycia danych"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Dotknij, aby zobaczyć statystyki przesyłu danych"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Przekroczono limit danych"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Dotknij, aby zobaczyć statystyki przesyłu danych"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Brak wyników"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Znajdź na stronie"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 wynik"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> z <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 892aefd..dc762d1 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Ferramentas de desenvolvimento"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funcionalidades apenas necessárias para programadores de aplicações."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Armazenamento"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Aceder ao cartão SD."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Aceder ao cartão SD."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permite a uma aplicação libertar espaço de armazenamento no telefone eliminando ficheiros no directório da cache da aplicação. Geralmente, o acesso é muito limitado para processamento do sistema."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos de aplicações"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Permite que uma aplicação mova recursos de aplicações de meios internos para meios externos e vice-versa."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"ler ficheiros de registo do sistema"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Permite a uma aplicação ler a partir dos diversos ficheiros de registo do sistema. Isto permite descobrir informações gerais sobre a forma como o utilizador usa o telefone, mas estas não devem conter quaisquer dados pessoais ou privados."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"ler/escrever em recursos propriedade de diag"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Permite a uma aplicação ler e escrever em qualquer recurso que seja propriedade do grupo diag. Por exemplo, ficheiros em /dev. Isto pode afectar potencialmente a estabilidade e a segurança do sistema e deve ser utilizado APENAS para diagnósticos específicos do hardware pelo fabricante ou pelo operador."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"activar ou desactivar componentes da aplicação"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Permite a uma aplicação configurar o telefone Bluetooth local, bem como descobrir e emparelhar com dispositivos remotos."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"criar ligações Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Permite a uma aplicação ver a configuração do telefone Bluetooth local, bem como efectuar e aceitar ligações com dispositivos emparelhados."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"desactivar bloqueio de teclas"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Permite a uma aplicação desactivar o bloqueio de teclas e qualquer segurança por palavra-passe associada. Um exemplo legítimo é a desactivação do bloqueio de teclas pelo telefone ao receber uma chamada, reactivando, em seguida, o bloqueio de teclas ao terminar a chamada."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"ler definições de sincronização"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Permite a uma aplicação ler quaisquer palavras, nomes e expressões privadas que o utilizador possa ter armazenado no dicionário do utilizador."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"escrever no dicionário definido pelo utilizador"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Permite a uma aplicação escrever novas palavras no dicionário do utilizador."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"modificar/eliminar conteúdo do cartão SD"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"modificar/eliminar conteúdo do cartão SD"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Permite que uma aplicação escreva no cartão SD."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Permite que uma aplicação escreva no cartão SD."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/eliminar conteúdo do cartão SD"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Permite que uma aplicação escreva no cartão SD."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"aceder ao sistema de ficheiros da cache"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permite a uma aplicação ler e escrever no sistema de ficheiros da cache."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Limitar palavra-passe"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Restrinja os tipos de palavras-passe que está autorizado a utilizar."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Ver tentativas de início de sessão"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"O monitor falhou as tentativas de iniciar sessão no dispositivo para efectuar algumas acções."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Repor palavra-passe"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Force a palavra-passe para um novo valor, sendo necessário que o administrador lho forneça antes de poder iniciar sessão."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Forçar bloqueio"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Controle quando o dispositivo é bloqueado, sendo necessário reintroduzir a respectiva palavra-passe."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Limitar palavra-passe"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Restrinja os tipos de palavras-passe que está autorizado a utilizar."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Ver tentativas de início de sessão"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"O monitor falhou as tentativas de iniciar sessão no dispositivo para efectuar algumas acções."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Repor palavra-passe"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Force a palavra-passe para um novo valor, sendo necessário que o administrador lho forneça antes de poder iniciar sessão."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Forçar bloqueio"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Controle quando o dispositivo é bloqueado, sendo necessário reintroduzir a respectiva palavra-passe."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Apagar todos os dados"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Efectue uma reposição de fábrica, eliminando todos os dados sem qualquer confirmação."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Efectue uma reposição de fábrica, eliminando todos os dados sem qualquer confirmação."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Definir o proxy global do dispositivo a ser utilizado quando a política estiver activada. Só o primeiro administrador do dispositivo define o proxy global efectivo."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Residência"</item>
     <item msgid="869923650527136615">"Móvel"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"através do <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> através de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduzir código PIN"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Introduza a palavra-passe para desbloquear"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Introduza o PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Código PIN incorrecto!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, prima Menu e, em seguida, 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergência"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Regressar à chamada"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Lamentamos, tente novamente"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Lamentamos, tente novamente"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"A carregar (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Carregado."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"O cartão SIM está bloqueado."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"A desbloquear cartão SIM..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Efectuou incorrectamente o seu padrão de desbloqueio <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Introduziu incorrectamente a palavra-passe <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Introduziu incorrectamente o PIN <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Efectuou incorrectamente o seu padrão de desbloqueio <xliff:g id="NUMBER_0">%d</xliff:g> vezes. Após outras <xliff:g id="NUMBER_1">%d</xliff:g> tentativas sem sucesso, ser-lhe-á pedido para desbloquear o telefone utilizando o seu início de sessão no Google."\n\n" Tente novamente dentro de <xliff:g id="NUMBER_2">%d</xliff:g> segundos."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Tente novamente dentro de <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Esqueceu-se do padrão?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Navegar para outra página?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Seleccione OK para continuar ou Cancelar para permanecer na página actual."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Sugestão: toque duas vezes para aumentar ou diminuir o zoom."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"ler histórico e marcadores do browser"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite que a aplicação leia todos os URLs visitados pelo browser e todos os marcadores do browser."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"gravar histórico e marcadores do browser"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Permite que uma aplicação modifique o histórico e os marcadores do browser armazenados no telefone. As aplicações maliciosas podem utilizar esta permissão para apagar ou modificar os dados do browser."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modificar permissões de localização geográfica do Navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permite a uma aplicação modificar as permissões de localização geográfica do Navegador. As aplicações mal intencionadas podem utilizar isto para enviar informações de localização para Web sites arbitrários."</string>
     <string name="save_password_message" msgid="767344687139195790">"Quer que o browser memorize esta palavra-passe?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Seleccionar tudo"</string>
-    <string name="selectText" msgid="4862359311088898878">"Seleccionar palavra"</string>
     <string name="cut" msgid="3092569408438626261">"Cortar"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Colar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Seleccionar texto..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selecção de texto"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Adicionar \"<xliff:g id="WORD">%s</xliff:g>\" ao dicionário"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Editar texto"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Acções de texto"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Pouco espaço livre"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"O espaço de armazenamento do telefone está a ficar reduzido."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Cancelar"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Atenção"</string>
+    <string name="loading" msgid="1760724998928255250">"A carregar..."</string>
     <string name="capital_on" msgid="1544682755514494298">"Activado"</string>
     <string name="capital_off" msgid="6815870386972805832">"Desactivar"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Concluir acção utilizando"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Aplicação redireccionada"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> está agora a ser executado."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> foi originalmente iniciado."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -810,30 +800,25 @@
     <string name="no_permissions" msgid="7283357728219338112">"Não são necessárias permissões"</string>
     <string name="perms_hide" msgid="7283915391320676226"><b>"Ocultar"</b></string>
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Mostrar tudo"</b></string>
-    <string name="usb_storage_activity_title" msgid="2399289999608900443">"Armazenamento em massa USB"</string>
+    <string name="usb_storage_activity_title" msgid="2399289999608900443">"Armazenamento em massa USB "</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"Ligado através de USB"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Ligou o telefone ao computador através de USB. Seleccione o botão abaixo se pretender copiar ficheiros entre o computador e o cartão SD do Android."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Ligou o telefone ao computador através de USB. Seleccione o botão abaixo se pretender copiar ficheiros entre o computador e o cartão SD do Android."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Ligou o telefone ao computador através de USB. Seleccione o botão abaixo se pretender copiar ficheiros entre o computador e o cartão SD do Android."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Activar armazenamento USB"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Existe um problema ao utilizar o cartão SD para armazenamento USB."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Existe um problema ao utilizar o cartão SD para armazenamento USB."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Existe um problema ao utilizar o cartão SD para armazenamento USB."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"Ligado através de USB"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Seleccione para copiar ficheiro para/do seu computador."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Desactivar armazenamento USB"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Opte por desactivar o armazenamento USB."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"O armazenamento USB está a ser utilizado"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Antes de desactivar o armazenamento USB, certifique-se de que desinstalou (\"ejectou\") o cartão SD do Android do computador."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Antes de desactivar o armazenamento USB, certifique-se de que desinstalou (\"ejectou\") o cartão SD do Android do computador."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Antes de desactivar o armazenamento USB, certifique-se de que desinstalou (\"ejectou\") o cartão SD do Android do computador."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Desactivar armazenamento USB"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Ocorreu um problema ao desactivar o armazenamento USB. Confirme se desinstalou o anfitrião USB e, em seguida, tente novamente."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Activar armazenamento USB"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Se activar o armazenamento USB, algumas aplicações que estiver a utilizar serão paradas e poderão ficar indisponíveis até desactivar o armazenamento USB."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"Falha na operação USB"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formatar cartão SD"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formatar cartão SD"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Tem a certeza de que pretende formatar o cartão SD? Perder-se-ão todos os dados no cartão."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Tem a certeza de que pretende formatar o cartão SD? Perder-se-ão todos os dados no cartão."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formatar cartão SD"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Tem a certeza de que pretende formatar o cartão SD? Perder-se-ão todos os dados no cartão."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatar"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuração USB ligada"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Seleccione para desactivar depuração USB."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"A preparar cartão SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"A preparar cartão SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"A preparar cartão SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"A verificar a presença de erros."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Cartão SD vazio"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Cartão SD vazio"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"O cartão SD está vazio ou a utilizar um sistema de ficheiros não suportado."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Cartão SD vazio ou sistema de ficheiros não suportado."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Cartão SD danificado"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Cartão SD danificado"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"O cartão SD está danificado. Poderá ser necessário reformatar o cartão."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"Cartão SD danificado. Poderá ser necessário reformatá-lo."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Cartão SD removido de forma inesperada"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Cartão SD removido de forma inesperada"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Desmonte o cartão SD antes de retirá-lo para evitar a perda de dados."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Desmonte o cartão SD antes de retirá-lo para evitar a perda de dados."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"É seguro retirar o cartão SD"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"É seguro retirar o cartão SD"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"É possível remover agora o cartão SD em segurança."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Pode remover o cartão SD com segurança."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Cartão SD removido"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Cartão SD removido"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"O cartão SD foi removido. Introduza um novo cartão SD para aumentar a capacidade de armazenamento do dispositivo."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"Cartão SD removido. Insira um novo cartão."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Cartão SD vazio"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"Cartão SD vazio ou sistema de ficheiros não suportado."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Cartão SD danificado"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"Cartão SD danificado. Poderá ser necessário reformatá-lo."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Cartão SD removido de forma inesperada"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Desmonte o cartão SD antes de retirá-lo para evitar a perda de dados."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"É seguro retirar o cartão SD"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Pode remover o cartão SD com segurança."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Cartão SD removido"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"Cartão SD removido. Insira um novo cartão."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Nenhuma actividade correspondente encontrada"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"actualizar estatísticas de utilização de componentes"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permite a modificação de estatísticas de utilização de componentes recolhidas. Não se destina a utilização por aplicações normais."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec baseada em chave pré- partilhada"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec baseada em certificado"</string>
     <string name="upload_file" msgid="2897957172366730416">"Escolher ficheiro"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Não foi seleccionado nenhum ficheiro"</string>
     <string name="reset" msgid="2448168080964209908">"Repor"</string>
     <string name="submit" msgid="1602335572089911941">"Enviar"</string>
-    <string name="description_star" msgid="2654319874908576133">"favorito"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modo automóvel activado"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Seleccionar para sair do modo automóvel."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Ligação ponto a ponto ou hotspot activos"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Tocar para configurar"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Anterior"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Seguinte"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Ignorar"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Utilização elevada de dados móveis"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toque para saber mais sobre a utilização de dados móveis"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Limite de dados móveis excedido"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Toque para saber mais sobre a utilização de dados móveis"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Sem correspondências"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Localizar na página"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 correspondência"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> de <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 4c18677..ee038c3 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Ferramentas de desenvolvimento"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Recursos necessários apenas para desenvolvedores de aplicativo."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Armazenamento"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Acessar o cartão SD."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Acessar o cartão SD."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permite que um aplicativo libere o espaço de armazenamento do telefone excluindo arquivos no diretório de cache do aplicativo. O acesso é normalmente muito restrito para o processo do sistema."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos do aplicativo"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Permite que um aplicativo mova os recursos do aplicativo da mídia interna para a externa e vice-versa."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"ler arquivos de registro do sistema"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Permite que um aplicativo leia os diversos arquivos de registro do sistema. Isso permite que ele descubra informações gerais sobre o que você está fazendo com o telefone, porém esses arquivos não devem conter informações pessoais ou privadas."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"ler/gravar em recursos pertencentes ao diag"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Permite que um aplicativo leia e grave em qualquer recurso que pertença ao grupo de diagnósticos; por exemplo, arquivos em /dev. Isso possivelmente pode afetar a estabilidade e a segurança do sistema. Isso deve ser usado APENAS para diagnósticos específicos do hardware realizados pelo fabricante ou pelo operador."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"ativar ou desativar os componentes do aplicativo"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Permite que um aplicativo configure o telefone Bluetooth local, descubra e pareie com dispositivos remotos."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"criar conexões Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Permite que um aplicativo veja a configuração do telefone Bluetooth local e que possa fazer e aceitar conexões com dispositivos pareados."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"desativar o bloqueio de teclas"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Permite que um aplicativo desative o bloqueio de teclas e qualquer segurança por senha associada. Um exemplo legítimo disso é a desativação do bloqueio de teclas pelo telefone ao receber uma chamada e a reativação do bloqueio quando a chamada é finalizada."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"ler as configurações de sincronização"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Permite que um aplicativo leia quaisquer palavras, nomes e frases particulares armazenados pelo usuário no dicionário do usuário."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"gravar no dicionário definido pelo usuário"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Permite que um aplicativo grave novas palavras no dicionário do usuário."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"modificar/excluir conteúdo do cartão SD"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"modificar/excluir conteúdo do cartão SD"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Permite que um aplicativo grave no cartão SD."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Permite que um aplicativo grave no cartão SD."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/excluir conteúdo do cartão SD"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Permite que um aplicativo grave no cartão SD."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"acessar o sistema de arquivos de cache"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permite que um aplicativo leia e grave no sistema de arquivos de cache."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Limitar senha"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Restringe os tipos de senha que você tem permissão de usar."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Exibir tentativas de login"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Monitora as tentativas malsucedidas de login no aparelho para executar alguma ação."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Redefinir senha"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Força a sua senha para um novo valor, exigindo que o administrador a forneça antes que você possa fazer login."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Forçar bloqueio"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Controla o bloqueio do aparelho, exigindo que a senha seja digitada novamente."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Limitar senha"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Restringe os tipos de senha que você tem permissão de usar."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Exibir tentativas de login"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Monitora as tentativas malsucedidas de login no aparelho para executar alguma ação."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Redefinir senha"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Força a sua senha para um novo valor, exigindo que o administrador a forneça antes que você possa fazer login."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Forçar bloqueio"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Controla o bloqueio do aparelho, exigindo que a senha seja digitada novamente."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Apagar todos os dados"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Execute uma redefinição de fábrica, excluindo todos os seus dados sem qualquer confirmação."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Execute uma redefinição de fábrica, excluindo todos os seus dados sem qualquer confirmação."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Configura o proxy global do dispositivo para ser usado enquanto a política estiver ativada. Somente o primeiro administrador do dispositivo pode configurar um verdadeiro proxy global."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Residencial"</item>
     <item msgid="869923650527136615">"Celular"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"por meio de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Digite o código PIN"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Digite a senha para desbloquear"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Digite o PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Código PIN incorreto!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, pressione Menu e, em seguida, 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergência"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Retornar à chamada"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correto!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Tente novamente"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Tente novamente"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Carregando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Carregado."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"O cartão SIM está bloqueado."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Desbloqueando o cartão SIM…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Você desenhou incorretamente o seu padrão de desbloqueio <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente em <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Você inseriu incorretamente a sua senha <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente em <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Você digitou incorretamente o seu PIN <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente em <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Você desenhou o seu padrão de desbloqueio incorretamente <xliff:g id="NUMBER_0">%d</xliff:g> vezes. Mais <xliff:g id="NUMBER_1">%d</xliff:g> tentativas incorretas e você receberá uma solicitação para desbloquear o seu telefone usando o seu login do Google."\n\n" Tente novamente em <xliff:g id="NUMBER_2">%d</xliff:g> segundos."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Tente novamente em <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Esqueceu o padrão?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Deseja sair desta página?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Selecione OK para continuar ou Cancelar para permanecer na página atual."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Dica: toque duas vezes para aumentar e diminuir o zoom."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"ler histórico e favoritos do Navegador"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite que o aplicativo leia todos os URLs visitados pelo Navegador e todos os favoritos do Navegador."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"gravar histórico e favoritos do Navegador"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Permite que um aplicativo modifique o histórico ou os favoritos do Navegador armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar os dados do seu Navegador."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modifique as permissões de geolocalização do seu navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permite que um aplicativo modifique as permissões de geolocalização do navegador. Aplicativos maliciosos podem usar isso para permitir o envio de informações de localização a sites arbitrários."</string>
     <string name="save_password_message" msgid="767344687139195790">"Deseja que o navegador lembre desta senha?"</string>
@@ -673,7 +659,7 @@
   </plurals>
   <plurals name="abbrev_num_seconds_ago">
     <item quantity="one" msgid="1849036840200069118">"1 seg. atrás"</item>
-    <item quantity="other" msgid="3699169366650930415">"<xliff:g id="COUNT">%d</xliff:g> segundos   atrás"</item>
+    <item quantity="other" msgid="3699169366650930415">"<xliff:g id="COUNT">%d</xliff:g> segundos\n  atrás"</item>
   </plurals>
   <plurals name="abbrev_num_minutes_ago">
     <item quantity="one" msgid="6361490147113871545">"1 minuto atrás"</item>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Selecionar tudo"</string>
-    <string name="selectText" msgid="4862359311088898878">"Selecionar palavra"</string>
     <string name="cut" msgid="3092569408438626261">"Recortar"</string>
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Colar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Selecionar texto..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Seleção de texto"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Adicionar \"<xliff:g id="WORD">%s</xliff:g>\" ao dicionário"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Editar texto"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Ações de texto"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Pouco espaço"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"O espaço de armazenamento do telefone está ficando baixo."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Cancelar"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Atenção"</string>
+    <string name="loading" msgid="1760724998928255250">"Carregando..."</string>
     <string name="capital_on" msgid="1544682755514494298">"ATIVADO"</string>
     <string name="capital_off" msgid="6815870386972805832">"DESATIVADO"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Complete a ação usando"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Aplicativo redirecionado"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> não está em execução."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> foi iniciado."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Mostrar todas"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"Armazenamento USB em massa"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"Conectado por USB"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Você conectou o telefone ao computador via USB. Selecione o botão abaixo se quiser copiar arquivos entre o computador e o cartão SD do seu Android."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Você conectou o telefone ao computador via USB. Selecione o botão abaixo se quiser copiar arquivos entre o computador e o cartão SD do seu Android."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Você conectou o telefone ao computador via USB. Selecione o botão abaixo se quiser copiar arquivos entre o computador e o cartão SD do seu Android."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Ativar o armazenamento USB"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Há um problema com o uso do seu cartão SD para armazenamento USB."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Há um problema com o uso do seu cartão SD para armazenamento USB."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Há um problema com o uso do seu cartão SD para armazenamento USB."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"Conectado por USB"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Selecione para copiar arquivos para/do seu computador."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Desativar o armazenamento USB"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Selecione para desativar o armazenamento USB."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"Armazenamento USB em uso"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Antes de desativar o armazenamento USB, verifique se desconectou (“ejetou”) o cartão SD do Android do computador."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Antes de desativar o armazenamento USB, verifique se desconectou (“ejetou”) o cartão SD do Android do computador."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Antes de desativar o armazenamento USB, verifique se desconectou (“ejetou”) o cartão SD do Android do computador."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Desativar o armazenamento USB"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Houve um problema ao desativar o armazenamento USB. Verifique se desconectou o host USB e tente novamente."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Ativar o armazenamento USB"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Se você ativar o armazenamento USB, alguns aplicativos que estão em uso serão interrompidos e poderão não estar disponíveis até você desativar o armazenamento USB."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"Falha de operação de USB"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formatar cartão SD"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formatar cartão SD"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Tem certeza de que deseja formatar o cartão SD? Todos os dados no seu cartão serão perdidos."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Tem certeza de que deseja formatar o cartão SD? Todos os dados no seu cartão serão perdidos."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formatar cartão SD"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Tem certeza de que deseja formatar o cartão SD? Todos os dados no seu cartão serão perdidos."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatar"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuração USB conectada"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Selecione para desativar a depuração USB."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Preparando o cartão SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Preparando o cartão SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Preparando o cartão SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Procurando erros."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Cartão SD em branco"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Cartão SD em branco"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"O cartão SD está em branco ou está usando um sistema de arquivos não suportado."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Cartão SD vazio ou com sistema de arquivos incompatível."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Cartão SD danificado"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Cartão SD danificado"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"O cartão SD está danificado. Talvez seja necessário reformatar o seu cartão."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"O cartão SD está danificado. Talvez seja necessário reformatá-lo."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Cartão SD removido inesperadamente."</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Cartão SD removido inesperadamente."</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Desmonte o cartão SD antes da remoção para evitar a perda de dados."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Desmonte o cartão SD antes da remoção para evitar a perda de dados."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"O cartão SD já pode ser removido com segurança."</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"O cartão SD já pode ser removido com segurança."</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"O cartão SD já pode ser removido com segurança."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Você pode remover o cartão SD com segurança."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Cartão SD removido"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Cartão SD removido"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"O SD foi removido. Insira um novo cartão SD para aumentar o armazenamento do seu dispositivo."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"Cartão SD removido. Insira um novo."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Cartão SD em branco"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"Cartão SD vazio ou com sistema de arquivos incompatível."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Cartão SD danificado"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"O cartão SD está danificado. Talvez seja necessário reformatá-lo."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Cartão SD removido inesperadamente."</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Desmonte o cartão SD antes da remoção para evitar a perda de dados."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"O cartão SD já pode ser removido com segurança."</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Você pode remover o cartão SD com segurança."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Cartão SD removido"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"Cartão SD removido. Insira um novo."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Nenhum atividade correspondente foi encontrada"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"atualizar estatísticas de uso do componente"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permite a modificação das estatísticas de uso do componente coletadas. Não deve ser usado por aplicativos normais."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec com base em chave pré-compartilhada"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec com base em certificado"</string>
     <string name="upload_file" msgid="2897957172366730416">"Escolher arquivo"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Nenhum arquivo escolhido"</string>
     <string name="reset" msgid="2448168080964209908">"Redefinir"</string>
     <string name="submit" msgid="1602335572089911941">"Enviar"</string>
-    <string name="description_star" msgid="2654319874908576133">"favorito"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modo de carro ativado"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Selecione para sair do modo de carro."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Vínculo ou ponto de acesso ativo"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Toque para configurar"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Voltar"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Avançar"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Ignorar"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Alto uso de dados do celular"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toque para saber mais sobre uso de dados do celular"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Limite de dados do celular excedido"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Toque para saber mais sobre o uso de dados do celular"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Não encontrado"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Localizar na página"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"Uma correspondência"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> de <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-rm-rCH/arrays.xml b/core/res/res/values-rm-rCH/arrays.xml
new file mode 100644
index 0000000..48e8613
--- /dev/null
+++ b/core/res/res/values-rm-rCH/arrays.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 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. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT 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>
+
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_lat_lng">
+        <item>46948020</item>
+        <item>7448206</item>
+    </integer-array>
+    <!-- Do not translate. -->
+    <integer-array name="maps_starting_zoom">
+        <item>4</item>
+    </integer-array>
+
+</resources>
diff --git a/core/res/res/values-rm/donottranslate-cldr.xml b/core/res/res/values-rm/donottranslate-cldr.xml
new file mode 100644
index 0000000..ccdb17c
--- /dev/null
+++ b/core/res/res/values-rm/donottranslate-cldr.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="month_long_standalone_january">schaner</string>
+    <string name="month_long_standalone_february">favrer</string>
+    <string name="month_long_standalone_march">mars</string>
+    <string name="month_long_standalone_april">avrigl</string>
+    <string name="month_long_standalone_may">matg</string>
+    <string name="month_long_standalone_june">zercladur</string>
+    <string name="month_long_standalone_july">fanadur</string>
+    <string name="month_long_standalone_august">avust</string>
+    <string name="month_long_standalone_september">settember</string>
+    <string name="month_long_standalone_october">october</string>
+    <string name="month_long_standalone_november">november</string>
+    <string name="month_long_standalone_december">december</string>
+
+    <string name="month_long_january">schaner</string>
+    <string name="month_long_february">favrer</string>
+    <string name="month_long_march">mars</string>
+    <string name="month_long_april">avrigl</string>
+    <string name="month_long_may">matg</string>
+    <string name="month_long_june">zercladur</string>
+    <string name="month_long_july">fanadur</string>
+    <string name="month_long_august">avust</string>
+    <string name="month_long_september">settember</string>
+    <string name="month_long_october">october</string>
+    <string name="month_long_november">november</string>
+    <string name="month_long_december">december</string>
+
+    <string name="month_medium_january">schan.</string>
+    <string name="month_medium_february">favr.</string>
+    <string name="month_medium_march">mars</string>
+    <string name="month_medium_april">avr.</string>
+    <string name="month_medium_may">matg</string>
+    <string name="month_medium_june">zercl.</string>
+    <string name="month_medium_july">fan.</string>
+    <string name="month_medium_august">avust</string>
+    <string name="month_medium_september">sett.</string>
+    <string name="month_medium_october">oct.</string>
+    <string name="month_medium_november">nov.</string>
+    <string name="month_medium_december">dec.</string>
+
+    <string name="month_shortest_january">S</string>
+    <string name="month_shortest_february">F</string>
+    <string name="month_shortest_march">M</string>
+    <string name="month_shortest_april">A</string>
+    <string name="month_shortest_may">M</string>
+    <string name="month_shortest_june">Z</string>
+    <string name="month_shortest_july">F</string>
+    <string name="month_shortest_august">A</string>
+    <string name="month_shortest_september">S</string>
+    <string name="month_shortest_october">O</string>
+    <string name="month_shortest_november">N</string>
+    <string name="month_shortest_december">D</string>
+
+    <string name="day_of_week_long_sunday">dumengia</string>
+    <string name="day_of_week_long_monday">glindesdi</string>
+    <string name="day_of_week_long_tuesday">mardi</string>
+    <string name="day_of_week_long_wednesday">mesemna</string>
+    <string name="day_of_week_long_thursday">gievgia</string>
+    <string name="day_of_week_long_friday">venderdi</string>
+    <string name="day_of_week_long_saturday">sonda</string>
+
+    <string name="day_of_week_medium_sunday">du</string>
+    <string name="day_of_week_medium_monday">gli</string>
+    <string name="day_of_week_medium_tuesday">ma</string>
+    <string name="day_of_week_medium_wednesday">me</string>
+    <string name="day_of_week_medium_thursday">gie</string>
+    <string name="day_of_week_medium_friday">ve</string>
+    <string name="day_of_week_medium_saturday">so</string>
+
+    <string name="day_of_week_short_sunday">du</string>
+    <string name="day_of_week_short_monday">gli</string>
+    <string name="day_of_week_short_tuesday">ma</string>
+    <string name="day_of_week_short_wednesday">me</string>
+    <string name="day_of_week_short_thursday">gie</string>
+    <string name="day_of_week_short_friday">ve</string>
+    <string name="day_of_week_short_saturday">so</string>
+
+    <string name="day_of_week_shortest_sunday">D</string>
+    <string name="day_of_week_shortest_monday">G</string>
+    <string name="day_of_week_shortest_tuesday">M</string>
+    <string name="day_of_week_shortest_wednesday">M</string>
+    <string name="day_of_week_shortest_thursday">G</string>
+    <string name="day_of_week_shortest_friday">V</string>
+    <string name="day_of_week_shortest_saturday">S</string>
+
+    <string name="yesterday">ier</string>
+    <string name="today">oz</string>
+    <string name="tomorrow">damaun</string>
+
+    <string name="hour_minute_24">%-k:%M</string>
+    <string name="hour_minute_ampm">%-l:%M %p</string>
+    <string name="hour_minute_cap_ampm">%-l:%M %^p</string>
+    <string name="twelve_hour_time_format">h:mm a</string>
+    <string name="twenty_four_hour_time_format">H:mm</string>
+    <string name="numeric_date">%d.%m.%Y</string>
+    <string name="numeric_date_format">dd.MM.yyyy</string>
+    <string name="numeric_date_template">"%s.%s.%s"</string>
+    <string name="month_day_year">%-e. %B %Y</string>
+    <string name="time_of_day">%H:%M:%S</string>
+    <string name="date_and_time">%H:%M:%S %d.%m.%Y</string>
+    <string name="date_time">%1$s, %2$s</string>
+    <string name="time_date">%3$s, %1$s</string>
+    <string name="abbrev_month_day_year">%d.%m.%Y</string>
+    <string name="month_day">%-e. %B</string>
+    <string name="month">%-B</string>
+    <string name="month_year">%B %Y</string>
+    <string name="abbrev_month_day">%-e. %b</string>
+    <string name="abbrev_month">%-b</string>
+    <string name="abbrev_month_year">%b %Y</string>
+    <string name="time1_time2">%1$s - %2$s</string>
+    <string name="date1_date2">%2$s - %5$s</string>
+    <string name="numeric_md1_md2">%3$s.%2$s. - %8$s.%7$s.</string>
+    <string name="numeric_wday1_md1_wday2_md2">%1$s, %3$s.%2$s. - %6$s, %8$s.%7$s.</string>
+    <string name="numeric_mdy1_mdy2">%3$s.%2$s.%4$s - %8$s.%7$s.%9$s</string>
+    <string name="numeric_wday1_mdy1_wday2_mdy2">%1$s, %3$s.%2$s.%4$s - %6$s, %8$s.%7$s.%9$s</string>
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %4$s-%2$s-%3$s - %10$s %6$s, %9$s-%7$s-%8$s</string>
+    <string name="numeric_md1_time1_md2_time2">%5$s %3$s.%2$s. - %10$s %8$s.%7$s.</string>
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">%5$s %1$s, %3$s.%2$s. - %10$s %6$s, %8$s.%7$s.</string>
+    <string name="numeric_mdy1_time1_mdy2_time2">%5$s %3$s.%2$s.%4$s - %10$s %8$s.%7$s.%9$s</string>
+    <string name="wday1_date1_time1_wday2_date2_time2">%3$s %1$s %2$s - %6$s %4$s %5$s</string>
+    <string name="wday1_date1_wday2_date2">%1$s %2$s - %4$s %5$s</string>
+    <string name="date1_time1_date2_time2">%3$s %2$s - %6$s %5$s</string>
+    <string name="time_wday_date">%1$s %2$s %3$s</string>
+    <string name="wday_date">%2$s %3$s</string>
+    <string name="time_wday">%1$s %2$s</string>
+    <string name="same_year_md1_md2">%3$s. %2$s - %8$s. %7$s</string>
+    <string name="same_year_wday1_md1_wday2_md2">%1$s %3$s. %2$s - %6$s %8$s. %7$s</string>
+    <string name="same_year_md1_time1_md2_time2">%5$s %3$s. %2$s - %10$s %8$s. %7$s</string>
+    <string name="same_month_md1_time1_md2_time2">%5$s %3$s. %2$s - %10$s %8$s. %7$s</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %3$s. %2$s - %10$s %6$s %8$s. %7$s</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %3$s. %2$s - %10$s %6$s %8$s. %7$s</string>
+    <string name="same_year_mdy1_time1_mdy2_time2">%5$s %3$s. %2$s %4$s - %10$s %8$s. %7$s %9$s</string>
+    <string name="same_month_mdy1_time1_mdy2_time2">%5$s %3$s. %2$s %4$s - %10$s %8$s. %7$s %9$s</string>
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %3$s. %2$s %4$s - %10$s %6$s, %8$s. %7$s %9$s</string>
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %3$s. %2$s %4$s - %10$s %6$s, %8$s. %7$s %9$s</string>
+    <string name="same_month_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s %4$s - %6$s, %8$s. %7$s %9$s</string>
+    <string name="same_month_md1_md2">%3$s.-%8$s. %2$s</string>
+    <string name="same_month_wday1_md1_wday2_md2">%1$s %3$s. %2$s - %6$s %8$s. %7$s</string>
+    <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string>
+    <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string>
+    <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s</string>
+    <string name="short_format_month">%b</string>
+</resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index a366a85..f040bd0 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Инструменты разработки"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Функции, необходимые только разработчикам приложений."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Память"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Доступ к SD-карте."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Доступ к SD-карте."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Позволяет приложению освобождать память телефона с помощью удаления файлов из каталога кэша приложений. Обычно это разрешается только системным процессам."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Перемещать ресурсы приложения"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Позволяет приложению перемещать ресурсы приложения с внутренних на внешние носители и наоборот."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"считывать системные файлы журналов"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Позволяет приложению считывать информацию из различных журналов системы. Приложение может получить сведения о работе пользователя с телефоном, но они не должны содержать какой-либо личной или конфиденциальной информации."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"считывать/записывать данные в ресурсы, принадлежащие группе диагностики"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Позволяет приложению считывать и записывать данные в любые ресурсы, принадлежащие группе диагностики (например, файлы в каталоге /dev). Это может повлиять на стабильность и безопасность системы. Эта возможность может быть использована ТОЛЬКО производителем или оператором для диагностики аппаратного обеспечения."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"включать или отключать компоненты приложения"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Позволяет приложению настраивать локальный телефон Bluetooth, обнаруживать и выполнять сопряжение удаленных устройств."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"создавать подключения Bluetooth"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Позволяет приложению просматривать конфигурацию локального телефона Bluetooth, создавать подключения с сопряженными устройствами."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"отключать блокировку клавиатуры"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Позволяет приложению отключить блокировку клавиатуры и другие функции защиты паролем. Примером допустимого использования этой функции является отключение блокировки клавиатуры при получении входящего вызова и включение блокировки после завершения разговора."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"считывать настройки синхронизации"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Позволяет приложению считывать любые слова, имена и фразы личного пользования, которые могут храниться в пользовательском словаре."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"записывать в словарь пользователя"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Позволяет приложению записывать новые слова в словарь пользователя."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"изменять/удалять содержимое SD-карты"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"изменять/удалять содержимое SD-карты"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Разрешает приложению запись на SD-карту"</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Разрешает приложению запись на SD-карту"</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"изменять/удалять содержимое SD-карты"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Разрешает приложению запись на SD-карту"</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"получать доступ к кэшу файловой системы"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Разрешает программам доступ для записи и чтения к кэшу файловой системы."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Ограничить пароль"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Ограничить типы паролей, доступных для использования."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Просмотр попыток входа"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Мониторинг неудачных попыток подключения к устройству для выполнения определенных действий."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Сбросить пароль"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Принудительно изменить пароль, который администратор должен будет сообщить вам, чтобы вы смогли выполнить вход."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Принудительная блокировка"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Управление блокировкой устройства, требующей ввода пароля."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Ограничить пароль"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Ограничить типы паролей, доступных для использования."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Просмотр попыток входа"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Мониторинг неудачных попыток подключения к устройству для выполнения определенных действий."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Сбросить пароль"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Принудительно изменить пароль, который администратор должен будет сообщить вам, чтобы вы смогли выполнить вход."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Принудительная блокировка"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Управление блокировкой устройства, требующей ввода пароля."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Удалить все данные"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Выполнить сброс к начальным настройкам с удалением всех данных без запроса подтверждения."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Выполнить сброс к начальным настройкам с удалением всех данных без запроса подтверждения."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Настройте глобальный прокси-сервер устройства, который будет использоваться при активной политике. Глобальный прокси-сервер должен настроить администратор первого устройства."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Домашний"</item>
     <item msgid="869923650527136615">"Мобильный"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"с помощью <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> с помощью <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Введите PIN-код"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Введите пароль для разблокировки"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Введите PIN-код для разблокировки"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Неверный PIN-код!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Для разблокировки нажмите \"Меню\", а затем 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Экстренная служба"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Вернуться к вызову"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Правильно!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Повторите попытку"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Повторите попытку"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Идет зарядка (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Заряжена."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,7 +568,9 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-карта заблокирована."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Разблокировка SIM-карты…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Количество неудачных попыток ввода графического ключа разблокировки: <xliff:g id="NUMBER_0">%d</xliff:g>. "\n\n"Повторите попытку через <xliff:g id="NUMBER_1">%d</xliff:g> с."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Количество неудачных попыток ввода графического ключа разблокировки: <xliff:g id="NUMBER_0">%d</xliff:g>. После <xliff:g id="NUMBER_1">%d</xliff:g> неудачных попыток вам будет предложено разблокировать телефон с помощью учетных данных Google.  "\n\n" Повторите попытку через <xliff:g id="NUMBER_2">%d</xliff:g> с."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Количество неудачных попыток ввода пароля: <xliff:g id="NUMBER_0">%d</xliff:g>. "\n\n"Повторите попытку через <xliff:g id="NUMBER_1">%d</xliff:g> с."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Количество неудачных попыток ввода PIN-кода: <xliff:g id="NUMBER_0">%d</xliff:g>. "\n\n"Повторите попытку через <xliff:g id="NUMBER_1">%d</xliff:g> с."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Количество неудачных попыток ввода графического ключа разблокировки: <xliff:g id="NUMBER_0">%d</xliff:g>. После <xliff:g id="NUMBER_1">%d</xliff:g> неудачных попыток вам будет предложено разблокировать телефон с помощью учетных данных Google.\n "\n\n" Повторите попытку через <xliff:g id="NUMBER_2">%d</xliff:g> с."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Повторите попытку через <xliff:g id="NUMBER">%d</xliff:g> с."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Забыли графический ключ?"</string>
     <string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Снятие блокировки аккаунта"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Перейти с этой страницы?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Нажмите \"ОК\", чтобы продолжить, или \"Отмена\", чтобы остаться на текущей странице."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Подтвердите"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Совет: нажмите дважды, чтобы увеличить и уменьшить масштаб."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"считывать историю и закладки браузера"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Разрешает приложению считывать все URL, посещенные браузером, и все его закладки."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"записывать историю и закладки браузера"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Разрешает приложению изменять историю и закладки браузера, сохраненные в вашем телефоне. Вредоносное ПО может пользоваться этим, чтобы стирать или изменять данные вашего браузера."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Изменить разрешения браузера для доступа к географическому местоположению"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Позволяет программе изменять разрешения браузера для доступа к географическому положению. Вредоносные программы могут пользоваться этим для отправки информации о местоположении на некоторые сайты."</string>
     <string name="save_password_message" msgid="767344687139195790">"Вы хотите, чтобы браузер запомнил этот пароль?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Выбрать все"</string>
-    <string name="selectText" msgid="4862359311088898878">"Выберите слово"</string>
     <string name="cut" msgid="3092569408438626261">"Вырезать"</string>
     <string name="copy" msgid="2681946229533511987">"Копировать"</string>
     <string name="paste" msgid="5629880836805036433">"Вставить"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Копировать URL"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Выбрать текст..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Выбор текста"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Способ ввода"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Добавить \"<xliff:g id="WORD">%s</xliff:g>\" в словарь"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Изменить текст"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Операции с текстом"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Недостаточно места"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Заканчивается место в памяти телефона."</string>
     <string name="ok" msgid="5970060430562524910">"ОК"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"ОК"</string>
     <string name="no" msgid="5141531044935541497">"Отмена"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Внимание"</string>
+    <string name="loading" msgid="1760724998928255250">"Загрузка..."</string>
     <string name="capital_on" msgid="1544682755514494298">"ВКЛ"</string>
     <string name="capital_off" msgid="6815870386972805832">"ВЫКЛ"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Что использовать?"</string>
@@ -763,9 +750,12 @@
     <string name="force_close" msgid="3653416315450806396">"Закрыть"</string>
     <string name="report" msgid="4060218260984795706">"Отчет"</string>
     <string name="wait" msgid="7147118217226317732">"Подождать"</string>
-    <string name="launch_warning_title" msgid="8323761616052121936">"Приложение перенаправлено"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"Приложение <xliff:g id="APP_NAME">%1$s</xliff:g> выполняется."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"Изначально было запущено приложение <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Показать все"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"Запоминающее устройство USB"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB-подключение установлено"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Телефон подключен к компьютеру через порт USB. Нажмите кнопку ниже, если необходимо копировать файлы с компьютера на SD-карту устройства Android (или наоборот)."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Телефон подключен к компьютеру через порт USB. Нажмите кнопку ниже, если необходимо копировать файлы с компьютера на SD-карту устройства Android (или наоборот)."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Телефон подключен к компьютеру через порт USB. Нажмите кнопку ниже, если необходимо копировать файлы с компьютера на SD-карту устройства Android (или наоборот)."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Включить USB-накопитель"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"При использовании SD-карты как USB-накопителя возникла неполадка."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"При использовании SD-карты как USB-накопителя возникла неполадка."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"При использовании SD-карты как USB-накопителя возникла неполадка."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB-подключение установлено"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Выберите копирование файлов на компьютер или с компьютера."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Выключить USB-накопитель"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Выберите, чтобы выключить USB-накопитель."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-накопитель используется"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Перед отключением USB-накопителя убедитесь, что SD-карта устройства Android была отключена от компьютера."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Перед отключением USB-накопителя убедитесь, что SD-карта устройства Android была отключена от компьютера."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Перед отключением USB-накопителя убедитесь, что SD-карта устройства Android была отключена от компьютера."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Выключить USB-накопитель"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"При выключении USB-накопителя произошла неполадка. Убедитесь, что USB-хост отключен, и повторите попытку."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Включение USB-накопителя"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"При включении USB-накопителя некоторые используемые приложения могут прекратить работу и оставаться недоступными до отключения USB-накопителя."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"Сбой операции USB-подключения"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"ОК"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Очистить SD-карту"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Очистить SD-карту"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Отформатировать карту SD? Все данные, находящиеся на карте, будут уничтожены."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Отформатировать карту SD? Все данные, находящиеся на карте, будут уничтожены."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Очистить SD-карту"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Отформатировать карту SD? Все данные, находящиеся на карте, будут уничтожены."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Формат"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Отладка по USB разрешена"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Нажмите, чтобы отключить отладку USB."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"варианты"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Подготовка карты SD"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Подготовка карты SD"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Подготовка карты SD"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Проверка ошибок."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Пустая карта SD"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Пустая карта SD"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"Карта SD пуста или использует неподдерживаемую файловую систему."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SD-карта пуста или использует неподдерживаемую файловую систему"</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Поврежденная карта SD"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Поврежденная карта SD"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"Карта SD повреждена. Возможно, потребуется отформатировать ее."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD-карта повреждена. Попробуйте отформатировать ее."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"Карта SD неожиданно извлечена"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"Карта SD неожиданно извлечена"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Перед извлечением карты SD отключите ее во избежание потери данных."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Перед извлечением карты SD отключите ее во избежание потери данных."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"Безопасное удаление карты SD"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"Безопасное удаление карты SD"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"Теперь карту SD можно безопасно удалить."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Можно безопасно извлечь SD-карту."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Отсутствует карта SD"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Отсутствует карта SD"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"Карта SD отсутствует. Для увеличения емкости устройства вставьте новую карту SD."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD-карта извлечена. Вставьте новую карту."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Пустая карта SD"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD-карта пуста или использует неподдерживаемую файловую систему"</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Поврежденная карта SD"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD-карта повреждена. Попробуйте отформатировать ее."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"Карта SD неожиданно извлечена"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Перед извлечением карты SD отключите ее во избежание потери данных."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"Безопасное удаление карты SD"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Можно безопасно извлечь SD-карту."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Отсутствует карта SD"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD-карта извлечена. Вставьте новую карту."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Подходящих действий не найдено"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"обновлять статистику использования компонентов"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Позволяет изменять собранную статистику использования компонентов. Не предназначено для использования обычными приложениями."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN (на основе предв. общ. ключа)"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN (на основе сертификата)"</string>
     <string name="upload_file" msgid="2897957172366730416">"Выбрать файл"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Не выбран файл"</string>
     <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="tethered_notification_title" msgid="3146694234398202601">"USB-модем или точка доступа Wi-Fi активны"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Нажмите для настройки"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Назад"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Далее"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Пропустить"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Активная передача данных"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Нажмите, чтобы узнать больше о мобильной передаче данных"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Превышен лимит на мобильные данные"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Нажмите, чтобы узнать больше о мобильной передаче данных"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Нет совпадений"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Найти на странице"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 совпадение"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> из <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 137f853..418a8e6 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Utvecklingsverktyg"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funktioner som endast behövs för programutvecklare."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Lagring"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"Få åtkomst till SD-kortet."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"Få åtkomst till SD-kortet."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Tillåter att ett program frigör lagringsutrymme i telefonen genom att ta bort filer i programmets katalog för cachelagring. Åtkomst är mycket begränsad, vanligtvis till systemprocesser."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Flytta programresurser"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Tillåter att ett program flyttar programresurser från interna till externa medier och tvärt om."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"läsa systemets loggfiler"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Tillåter att ett program läser från systemets olika loggfiler. Det innebär att programmet kan upptäcka allmän information om vad du gör med telefonen, men den bör inte innehålla personlig eller privat information."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"läsa/skriva till resurser som ägs av diag"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Tillåter att ett program läser och skriver till en resurs som ägs av diag-gruppen; till exempel filer i /dev. Detta kan eventuellt påverka systemets stabilitet och säkerhet. Detta bör ENDAST används av tillverkaren eller operatören för maskinvaruspecifik diagnostik."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"aktivera eller inaktivera programkomponenter"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Tillåter att ett program konfigurerar den lokala Bluetooth-telefonen samt upptäcker och parkopplar den med fjärranslutna enheter."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"skapa Bluetooth-anslutningar"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Tillåter att ett program ser den lokala Bluetooth-telefonens konfiguration, och skapar och accepterar anslutningar med parkopplade enheter."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"inaktivera tangentlås"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Tillåter att ett program inaktiverar tangentlåset och tillhörande lösenordsskydd. Ett exempel på detta är att telefonen inaktiverar tangentlåset vid inkommande samtal och sedan aktiverar det igen när samtalet är avslutat."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"läsa synkroniseringsinställningar"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Tillåt att ett program läser alla privata ord, namn och fraser som användaren lagrar i sin ordlista."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"skriva till användardefinierad ordlista"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Tillåter att ett program skriver in nya ord i användarordlistan."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"ändra/ta bort innehåll på SD-kortet"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"ändra/ta bort innehåll på SD-kortet"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Tillåter att ett program skriver till SD-kortet."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Tillåter att ett program skriver till SD-kortet."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"ändra/ta bort innehåll på SD-kortet"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Tillåter att ett program skriver till SD-kortet."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"åtkomst till cachefilsystemet"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Tillåter att ett program läser och skriver till cachefilsystemet."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Begränsa lösenord"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Begränsar vilka typer av lösenord som får användas."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Visa inloggningsförsök"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Övervaka misslyckade inloggningsförsök på enheten för att utföra åtgärder."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Återställ lösenord"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Framtvinga ett nytt värde för ditt lösenord. Kräver att administratören tillhandahåller det innan du kan logga in."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Framtvinga låsning"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Kontrollera när enheten låses, vilket kräver att du anger lösenordet igen."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Begränsa lösenord"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Begränsar vilka typer av lösenord som får användas."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Visa inloggningsförsök"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Övervaka misslyckade inloggningsförsök på enheten för att utföra åtgärder."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Återställ lösenord"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Framtvinga ett nytt värde för ditt lösenord. Kräver att administratören tillhandahåller det innan du kan logga in."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Framtvinga låsning"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Kontrollera när enheten låses, vilket kräver att du anger lösenordet igen."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Radera alla data"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Återställ fabriksinställningarna och ta bort alla data utan någon bekräftelse."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Återställ fabriksinställningarna och ta bort alla data utan någon bekräftelse."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Ange vilken global proxyserver som ska användas när policyn är aktiverad. Endast den första enhetsadministratören anger den faktiska globala proxyservern."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Hem"</item>
     <item msgid="869923650527136615">"Mobil"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ange PIN-kod"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Ange lösenord för att låsa upp"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Ange PIN-kod för att låsa upp"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Fel PIN-kod!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Tryck på Menu och sedan på 0 om du vill låsa upp."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nödsamtalsnummer"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Återgå till samtalet"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Korrekt!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Försök igen"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Försök igen"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Laddar (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Laddad."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kortet är låst."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Låser upp SIM-kort…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har angett fel lösenord <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Du har angett din PIN-kod fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter <xliff:g id="NUMBER_1">%d</xliff:g> försök till kommer du att uppmanas att låsa upp telefonen med din Google-inloggning."\n\n" Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Försök igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Glömt ditt grafiska lösenord?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Vill du lämna den här den här sidan?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Tryck på OK om du vill fortsätta eller på Avbryt om du vill vara kvar på den aktuella sidan."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Bekräfta"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Tips! Dubbelklicka om du vill zooma in eller ut."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"läsa webbläsarhistorik och bokmärken"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Tillåter att program läser alla webbadresser som webbläsaren har öppnat och alla webbläsarens bokmärken."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skriva webbläsarhistorik och bokmärken"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Tillåter att ett program ändrar webbläsarhistoriken och bokmärkena i din telefon. Skadliga program kan använda detta för att ta bort eller ändra data i webbläsaren."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Ändra geografisk plats för webbläsaren"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Tillåter att ett program ändrar webbläsarens behörigheter för geografisk plats. Skadliga program kan använda detta för att tillåta att platsinformation skickas till godtyckliga webbplatser."</string>
     <string name="save_password_message" msgid="767344687139195790">"Vill du att webbläsaren ska komma ihåg lösenordet?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Välj alla"</string>
-    <string name="selectText" msgid="4862359311088898878">"Välj ord"</string>
     <string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
     <string name="copy" msgid="2681946229533511987">"Kopiera"</string>
     <string name="paste" msgid="5629880836805036433">"Klistra in"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopiera webbadress"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Markera text..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Textmarkering"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Indatametod"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"Lägg till \"<xliff:g id="WORD">%s</xliff:g>\" i ordlistan"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Redigera text"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Textåtgärder"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Dåligt med utrymme"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Telefonens lagringsutrymme håller på att ta slut."</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Avbryt"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Obs!"</string>
+    <string name="loading" msgid="1760724998928255250">"Läser in..."</string>
     <string name="capital_on" msgid="1544682755514494298">"PÅ"</string>
     <string name="capital_off" msgid="6815870386972805832">"AV"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Slutför åtgärd genom att använda"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Programmet omdirigerades"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> körs."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> startades först."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Visa alla"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-masslagring"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB-ansluten"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Du har anslutit telefonen till datorn via USB. Välj knappen nedan om du vill kopiera filer mellan datorn och SD-kortet i din Android."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Du har anslutit telefonen till datorn via USB. Välj knappen nedan om du vill kopiera filer mellan datorn och SD-kortet i din Android."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Du har anslutit telefonen till datorn via USB. Välj knappen nedan om du vill kopiera filer mellan datorn och SD-kortet i din Android."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"Aktivera USB-lagring"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"Det gick inte att använda ditt SD-kort för USB-lagring."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"Det gick inte att använda ditt SD-kort för USB-lagring."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"Det gick inte att använda ditt SD-kort för USB-lagring."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB-ansluten"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Välj om du vill kopiera filer till/från din dator."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Inaktivera USB-lagring"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Välj om USB-lagring ska inaktiveras."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-lagret används"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"Kontrollera att du har demonterat (\"matat ut\") Android-telefonens SD-kort från datorn, innan du inaktiverar USB-lagring."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Kontrollera att du har demonterat (\"matat ut\") Android-telefonens SD-kort från datorn, innan du inaktiverar USB-lagring."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"Kontrollera att du har demonterat (\"matat ut\") Android-telefonens SD-kort från datorn, innan du inaktiverar USB-lagring."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Inaktivera USB-lagring"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Ett problem uppstod när USB-lagringsplatsen skulle inaktiveras. Kontrollera att USB-värden har demonterats och försök igen."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Aktivera USB-lagring"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Om du aktiverar USB-lagring avbryts några av de program som körs och de kanske inte blir tillgängliga igen förrän du inaktiverar USB-lagring."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB-åtgärd misslyckades"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"Formatera SD-kort"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Formatera SD-kort"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"Vill du formatera SD-kortet? Alla data på ditt kort kommer att gå förlorade."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Vill du formatera SD-kortet? Alla data på ditt kort kommer att gå förlorade."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"Formatera SD-kort"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"Vill du formatera SD-kortet? Alla data på ditt kort kommer att gå förlorade."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-felsökning ansluten"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Välj att inaktivera USB-felsökning."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"kandidater"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"Förbereder SD-kort"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Förbereder SD-kort"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Förbereder SD-kort"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Söker efter fel."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Tomt SD-kort"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Tomt SD-kort"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"SD-kortet är tomt eller så använder det ett filsystem som inte stöds."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SD-kortet är tomt eller så har det ett filsystem som inte stöds."</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Skadat SD-kort"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Skadat SD-kort"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"SD-kortet är skadat. Du måste eventuellt formatera om ditt kort."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD-kortet är skadat. Du måste eventuellt formatera om det."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"SD-kort togs oväntat bort"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SD-kort togs oväntat bort"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Demontera SD-kort innan borttagning för att undvika dataförlust."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Demontera SD-kort innan borttagning för att undvika dataförlust."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"Säkert att ta bort SD-kort"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"Säkert att ta bort SD-kort"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"SD-kortet kan nu tas bort."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"Det är nu säkert att ta bort SD-kortet."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"Borttaget SD-kort"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"Borttaget SD-kort"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"SD har tagits bort. Sätt i ett nytt SD-kort för att öka enhetens lagringsutrymme."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD-kortet har tagits bort. Sätt i ett nytt."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Tomt SD-kort"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD-kortet är tomt eller så har det ett filsystem som inte stöds."</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Skadat SD-kort"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD-kortet är skadat. Du måste eventuellt formatera om det."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD-kort togs oväntat bort"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Demontera SD-kort innan borttagning för att undvika dataförlust."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"Säkert att ta bort SD-kort"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Det är nu säkert att ta bort SD-kortet."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"Borttaget SD-kort"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD-kortet har tagits bort. Sätt i ett nytt."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Inga matchande aktiviteter hittades"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"uppdatera statistik över användning av komponenter"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Tillåter att samlad komponentstatistik ändras. Används inte av vanliga program."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"I förväg delad L2TP/IPSec VPN-nyckel"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatsbaserad L2TP/IPSec VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"Välj fil"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil har valts"</string>
     <string name="reset" msgid="2448168080964209908">"Återställ"</string>
     <string name="submit" msgid="1602335572089911941">"Skicka"</string>
-    <string name="description_star" msgid="2654319874908576133">"favorit"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Billäge aktiverat"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Välj om du vill avsluta billäge."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Internetdelning eller surfpunkt aktiverad"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Tryck om du vill konfigurera"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Tillbaka"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"Nästa"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Hoppa över"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Hög mobildataanvändning"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tryck om du vill veta mer om mobildataanvändning"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Gränsen för mobildata har överskridits"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Tryck om du vill veta mer om mobildataanvändning"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Inga träffar"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Sök på sidan"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 träff"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> av <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index ad3b5e2..6ac5b86d 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Geliştirme araçları"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Yalnızca uygulama geliştiriciler için gerekli özellikler."</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Depolama"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"SD karta erişin."</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"SD karta erişin."</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Uygulamaların uygulama önbelleği dizinindeki dosyaları silerek telefonda yer açmasına izin verir. Erişim genellikle sistem işlemlerine ve yüksek düzeyde kısıtlı olarak verilir."</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"Uygulama kaynaklarını taşı"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"Bir uygulamanın, uygulama kaynaklarını dahili ve harici ortamlar arasında taşımasına olanak tanır."</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"sistem günlük dosyalarını oku"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"Uygulamaların sistemin çeşitli günlük dosyalarından okumalarına izin verir. Bu, uygulamaların telefon ile neler yaptığınız ile ilgili genel bilgi bulmasına izin verir, ancak bunlar kişisel veya özel bir bilgi içermemelidir."</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"sahibi tanılama olan kaynakları oku/bunlara yaz"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Uygulamanın tanılama grubundaki bir kaynağa ait herhangi bir kaynağı; örneğin /dev içindeki dosyaları okumasına ve bunlara yazmasına izin verir. Bu işlevin sistem kararlılığını ve güvenliğini olumsuz etkileme olasılığı vardır. Üretici veya operatör tarafından YALNIZCA donanıma özgü tanılama için kullanılmalıdır."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"uygulama bileşenlerini etkinleştir veya devre dışı bırak"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Uygulamaların yerel Bluetooth telefonunu yapılandırmasına ve uzak cihazları keşfedip bunlar ile eşleşmesine izin verir."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"Bluetooth bağlantıları oluştur"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"Uygulamaların yerel Bluetooth telefonunun yapılandırmasını görüntülemesine ve eşleşilmiş cihazlar ile bağlantı kurup kabul etmesine izin verir."</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"tuş kilidini devre dışı bırak"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Uygulamaların tuş kilidini ve ilgili şifreli güvenlik önlemini devre dışı bırakmasına izin verir. Bunun geçerli bir örneği gelen bir çağrı alındığında tuş kilidinin devre dışı bırakılması, sonra çağrı bittiğinde kilidin yeniden devreye sokulmasıdır."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"senk. ayarlarını oku"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Kullanıcının kullanıcı sözlüğünde depolamış olabileceği kişisel kelimeleri, adları ve kelime öbeklerini uygulamaların okumasına izin verir."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"kullanıcı tanımlı sözlüğe yaz"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Uygulamaların kullanıcı sözlüğüne yeni kelimeler yazmasına izin verir."</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"SD kart içeriklerini değiştir/sil"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"SD kart içeriklerini değiştir/sil"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"Bir uygulamaya SD karta yazma izni verir."</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Bir uygulamaya SD karta yazma izni verir."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"SD kart içeriklerini değiştir/sil"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Bir uygulamaya SD karta yazma izni verir."</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"önbellek dosya sistemine eriş"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Bir uygulamanın önbellek dosya sisteminde okuma yazma yapmasına izin verir."</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"Şifreyi sınırla"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"Kullanmanıza izin verilen şifre türlerini sınırlayın."</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"Oturum açma denemelerini izle"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"Bir işlem gerçekleştirmek için cihazdaki başarısız oturum açma girişimlerini izleyin."</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"Şifre sıfırlama"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"Şifrenizi yeni bir değer alması için zorlayın. Giriş yapabilmeniz için yöneticinin size yeni bir değer sağlamasını gerekecektir."</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"Kilitlemeye zorlama"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"Cihaz kilitlendiğinde, şifresini yeniden girmenizi gerektiren denetim."</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"Şifreyi sınırla"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"Kullanmanıza izin verilen şifre türlerini sınırlayın."</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"Oturum açma denemelerini izle"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"Bir işlem gerçekleştirmek için cihazdaki başarısız oturum açma girişimlerini izleyin."</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"Şifre sıfırlama"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"Şifrenizi yeni bir değer alması için zorlayın. Giriş yapabilmeniz için yöneticinin size yeni bir değer sağlamasını gerekecektir."</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"Kilitlemeye zorlama"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"Cihaz kilitlendiğinde, şifresini yeniden girmenizi gerektiren denetim."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Tüm verileri sil"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"Tüm verilerinizi onay olmadan silmek için fabrika ayarlarına sıfırlayın."</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"Tüm verilerinizi onay olmadan silmek için fabrika ayarlarına sıfırlayın."</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Politika etkin olduğunda kullanılacak cihaz genelinde geçerli proxy\'yi ayarlayın. Etkin genel proxy\'yi yalnızca ilk cihaz yöneticisi ayarlar."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Ev"</item>
     <item msgid="869923650527136615">"Mobil"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g> aracılığıyla"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="SOURCE">%2$s</xliff:g> ile <xliff:g id="DATE">%1$s</xliff:g> tarihinde"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN kodunu gir"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Kilidi açmak için şifreyi girin"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Kilidi açmak için PIN\'i girin"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Yanlış PIN kodu!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Kilidi açmak için önce Menü\'ye, sonra 0\'a basın."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Acil durum numarası"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Çağrıya dön"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Doğru!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Üzgünüz, lütfen yeniden deneyin"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Maalesef, tekrar deneyin"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Şarj oluyor (<xliff:g id="PERCENT">%%</xliff:g><xliff:g id="NUMBER">%d</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Şarj oldu."</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM kart kilitli."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM kart kilidi açılıyor…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Kilit açma deseninizi <xliff:g id="NUMBER_0">%d</xliff:g> kez yanlış çizdiniz. "\n\n"Lütfen <xliff:g id="NUMBER_1">%d</xliff:g> saniye içinde yeniden deneyin."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Şifrenizi <xliff:g id="NUMBER_0">%d</xliff:g> kez yanlış girdiniz. "\n\n"Lütfen <xliff:g id="NUMBER_1">%d</xliff:g> saniye içinde yeniden deneyin."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"PIN\'inizi <xliff:g id="NUMBER_0">%d</xliff:g> kez yanlış girdiniz. "\n\n"Lütfen <xliff:g id="NUMBER_1">%d</xliff:g> saniye içinde yeniden deneyin."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Kilit açma deseninizi <xliff:g id="NUMBER_0">%d</xliff:g> kez yanlış çizdiniz. <xliff:g id="NUMBER_1">%d</xliff:g> başarısız denemeden sonra telefonunuzu Google oturum açma bilgilerinizi kullanarak açmanız istenir."\n\n" Lütfen <xliff:g id="NUMBER_2">%d</xliff:g> saniye içinde yeniden deneyin."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g> saniye içinde yeniden deneyin."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Deseni unuttunuz mu?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"Bu sayfadan ayrılıyor musunuz?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Devam etmek için Tamam\'ı, sayfada kalmak için İptal\'i tıklatın."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Onayla"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"İpucu: Yakınlaştırmak ve uzaklaştırmak için iki kez hafifçe vurun."</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"Tarayıcı geçmişini ve favorileri oku"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Uygulamaya Tarayıcının ziyaret etmiş olduğu tüm URL\'leri ve Tarayıcının tüm favorilerini okuma izni verir."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"Tarayıcı geçmişini ve favorileri yaz"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Uygulamaya telefonunuzda depolanan Tarayıcı geçmişini veya favorileri değiştirme izni verir. Kötü amaçlı uygulamalar bunu Tarayıcı verilerinizi silmek veya değiştirmek için kullanabilir."</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Tarayıcı\'nın coğrafi konum izinlerini değiştir"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Bir uygulamanın, Tarayıcı\'nın coğrafi konum izinlerini değiştirmesine izin verir. Kötü amaçlı uygulamalar, bu özelliği konum bilgilerini rastgele web sitelerine göndermek için kullanabilir."</string>
     <string name="save_password_message" msgid="767344687139195790">"Tarayıcının bu şifreyi anımsamasını istiyor musunuz?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Tümünü seç"</string>
-    <string name="selectText" msgid="4862359311088898878">"Kelime seçin"</string>
     <string name="cut" msgid="3092569408438626261">"Kes"</string>
     <string name="copy" msgid="2681946229533511987">"Kopyala"</string>
     <string name="paste" msgid="5629880836805036433">"Yapıştır"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL\'yi kopyala"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"Metin seç..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Metin seçimi"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Giriş yöntemi"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"\"<xliff:g id="WORD">%s</xliff:g>\" kelimesini sözlüğe ekle"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"Metin düzenle"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"Metin eylemleri"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Yer az"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"Telefonun depolama alanı azalıyor."</string>
     <string name="ok" msgid="5970060430562524910">"Tamam"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"Tamam"</string>
     <string name="no" msgid="5141531044935541497">"İptal"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Dikkat"</string>
+    <string name="loading" msgid="1760724998928255250">"Yükleniyor..."</string>
     <string name="capital_on" msgid="1544682755514494298">"AÇIK"</string>
     <string name="capital_off" msgid="6815870386972805832">"KAPALI"</string>
     <string name="whichApplication" msgid="4533185947064773386">"İşlemi şunu kullanarak tamamla"</string>
@@ -763,9 +750,12 @@
     <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="launch_warning_title" msgid="8323761616052121936">"Uygulama yönlendirildi"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> şimdi çalışıyor."</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"İlk olarak <xliff:g id="APP_NAME">%1$s</xliff:g> başlatıldı."</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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 uyguladığı StrictMode politikasını ihlal etti."</string>
     <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> işlemi kendiliğinden uyguladığı StrictMode politikasını ihlal etti."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Tümünü göster"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB Yığın Depolama"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB bağlandı"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"Telefonunuzu USB ile bilgisayarınıza bağladınız. Bilgisayarınız ile Android\'inizin SD kartı arasında dosya kopyalamak istiyorsanız aşağıdaki düğmeyi seçin."</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"Telefonunuzu USB ile bilgisayarınıza bağladınız. Bilgisayarınız ile Android\'inizin SD kartı arasında dosya kopyalamak istiyorsanız aşağıdaki düğmeyi seçin."</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"Telefonunuzu USB ile bilgisayarınıza bağladınız. Bilgisayarınız ile Android\'inizin SD kartı arasında dosya kopyalamak istiyorsanız aşağıdaki düğmeyi seçin."</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"USB depolama birimini aç"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"SD kartınızı USB depolama birimi için kullanmada bir sorun var."</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"SD kartınızı USB depolama birimi için kullanmada bir sorun var."</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"SD kartınızı USB depolama birimi için kullanmada bir sorun var."</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB bağlandı"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Bilgisayarınıza/bilgisayarınızdan dosya kopyalamak için seçin."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USB depolama birimini kapat"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"USB depolama birimini kapatmak için seçin."</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USB depolama birimi kullanılıyor"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"USB depolama birimini kapatmadan önce Android SD kartını bilgisayarınızdan kaldırdığınızdan (\"çıkardığınızdan\") emin olun."</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"USB depolama birimini kapatmadan önce Android SD kartını bilgisayarınızdan kaldırdığınızdan (\"çıkardığınızdan\") emin olun."</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"USB depolama birimini kapatmadan önce Android SD kartını bilgisayarınızdan kaldırdığınızdan (\"çıkardığınızdan\") emin olun."</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USB depolama birimini kapat"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"USB depolama birimini kapatırken bir sorun oluştu. USB ana makinesini kaldırdığınızdan emin olun ve daha sonra tekrar deneyin."</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USB depolama birimini aç"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"USB depolama birimini açarsanız, kullanmakta olduğunuz bazı uygulamalar durur ve USB depolama birimi kapatılıncaya kadar kullanılamayabilir."</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB işlemi başarısız oldu"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"Tamam"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"SD kartı biçimlendir"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"SD kartı biçimlendir"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"SD kartı biçimlendirmek istediğinizden emin misiniz? Kartınızdaki tüm veriler yok olacak."</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"SD kartı biçimlendirmek istediğinizden emin misiniz? Kartınızdaki tüm veriler yok olacak."</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"SD kartı biçimlendir"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"SD kartı biçimlendirmek istediğinizden emin misiniz? Kartınızdaki tüm veriler yok olacak."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Biçimlendir"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"USB hata ayıklaması bağlandı"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"USB hata ayıklamasını devre dışı bırakmak için tıklayın."</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"adaylar"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"SD kart hazırlanıyor"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"SD kart hazırlanıyor"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"SD kart hazırlanıyor"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Hatalar denetleniyor."</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"Boş SD kart"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Boş SD kart"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"SD kart boş veya desteklenmeyen bir dosya sistemi kullanıyor."</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SD kart boş veya desteklenmeyen dosya sistemi içeriyor"</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"Hasarlı SD kart"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Hasarlı SD kart"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"SD kart hasarlı. Kartınızı yeniden biçimlendirmeniz gerekebilir."</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD kart zarar gördü. Yeniden biçimlendirmeniz gerekebilir."</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"SD kart beklenmedik biçimde çıkarıldı"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SD kart beklenmedik biçimde çıkarıldı"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"Veri kaybından kaçınmak için SD kartı çıkarmadan önce bağlantısını kesin."</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"Veri kaybından kaçınmak için SD kartı çıkarmadan önce bağlantısını kesin."</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"SD kart güvenle çıkarılabilir"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"SD kart güvenle çıkarılabilir"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"SD kart şimdi güvenli bir şekilde çıkarılabilir."</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"SD kartı güvenle kaldırabilirsiniz."</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"SD kart çıkarılmış"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"SD kart çıkarılmış"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"SD kart çıkarılmış. Cihazınızın depolama alanını artırmak için yeni bir SD kart takın."</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD kart çıkarıldı. Yeni bir SD kart takın."</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Boş SD kart"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD kart boş veya desteklenmeyen dosya sistemi içeriyor"</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Hasarlı SD kart"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD kart zarar gördü. Yeniden biçimlendirmeniz gerekebilir."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD kart beklenmedik biçimde çıkarıldı"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Veri kaybından kaçınmak için SD kartı çıkarmadan önce bağlantısını kesin."</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SD kart güvenle çıkarılabilir"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"SD kartı güvenle kaldırabilirsiniz."</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"SD kart çıkarılmış"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD kart çıkarıldı. Yeni bir SD kart takın."</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"Eşleşen hiçbir etkinlik bulunamadı"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"bileşen kullanım istatistiklerini güncelle"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Toplanmış bileşen istatistiklerinin değiştirilmesine izin verir. Normal uygulamalarda kullanılmamalıdır."</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN temelli önceden paylaşılmış anahtar"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN temelli sertifika"</string>
     <string name="upload_file" msgid="2897957172366730416">"Dosya seç"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Seçili dosya yok"</string>
     <string name="reset" msgid="2448168080964209908">"Sıfırla"</string>
     <string name="submit" msgid="1602335572089911941">"Gönder"</string>
-    <string name="description_star" msgid="2654319874908576133">"favori"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Araba modu etkin"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Araba modundan çıkmak için seçin."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Doğrudan bağlantı veya ortak erişim noktası etkin"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"Yapılandırmak için dokunun"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"Geri"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"İleri"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"Atla"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Yüksek düzeyde mobil veri kullanımı"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Mobil veri kullanımı hakkında daha fazla bilgi edinmek için dokunun"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"Mobil veri limiti aşıldı"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"Mobil veri kullanımı hakkında daha fazla bilgi edinmek için dokunun"</string>
+    <string name="no_matches" msgid="8129421908915840737">"Eşleşme yok"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Sayfada bul"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 eşleşme"</item>
+    <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> / <xliff:g id="TOTAL">%d</xliff:g>"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-xlarge/config.xml b/core/res/res/values-xlarge/config.xml
new file mode 100644
index 0000000..7e5a27b
--- /dev/null
+++ b/core/res/res/values-xlarge/config.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.
+*/
+-->
+
+<!-- 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>
+
+    <!-- Show sliding tab before lockscreen -->
+    <bool name="config_enableSlidingTabFirst">false</bool>
+    <!-- Enable lockscreen rotation -->
+    <bool name="config_enableLockScreenRotation">true</bool>
+
+    <!-- Enables 3d task switcher on xlarge device -->
+    <bool name="config_enableRecentApps3D">true</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..115cb30
--- /dev/null
+++ b/core/res/res/values-xlarge/dimens.xml
@@ -0,0 +1,37 @@
+<?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">48dip</dimen>
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_icon_size">48dip</dimen>
+    <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
+    <!-- Margin for permanent screen decorations at the bottom. -->
+    <dimen name="screen_margin_bottom">48dip</dimen>
+    
+    <!-- Default height of a key in the password keyboard for alpha -->
+    <dimen name="password_keyboard_key_height_alpha">0.35in</dimen>
+    <!-- Default height of a key in the password keyboard for numeric -->
+    <dimen name="password_keyboard_key_height_numeric">0.47in</dimen>
+
+    <!-- The width that is used when creating thumbnails of applications. -->
+    <dimen name="thumbnail_width">256dp</dimen>
+    <!-- The height that is used when creating thumbnails of applications. -->
+    <dimen name="thumbnail_height">255dp</dimen>
+</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..4692656
--- /dev/null
+++ b/core/res/res/values-xlarge/styles.xml
@@ -0,0 +1,38 @@
+<?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>
+    </style>
+    <style name="TextAppearance.StatusBar.Ticker">
+    </style>
+    <style name="TextAppearance.StatusBar.Title">
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="TextAppearance.StatusBar.Icon">
+    </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 4c7d72a..697eb98 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"开发工具"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"只有应用程序开发人员才会用到的功能。"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"存储"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"访问 SD 卡。"</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"访问 SD 卡。"</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"允许应用程序通过删除应用程序缓存目录中的文件释放手机存储空间。通常此权限只适用于系统进程。"</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"移动应用程序资源"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"允许应用程序在内部介质和外部介质之间移动应用程序资源。"</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"读取系统日志文件"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"允许应用程序从系统的各日志文件中读取信息。这样应用程序可以发现您的手机使用情况,但这些信息不应包含任何个人信息或保密信息。"</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"读取/写入诊断所拥有的资源"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"允许应用程序读取/写入诊断组所拥有的任何资源(例如,/dev 中的文件)。这可能会影响系统稳定性和安全性。此权限仅供制造商或运营商诊断硬件问题。"</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"启用或停用应用程序组件"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"允许应用程序配置本地蓝牙手机,以及发现远程设备并与其配对。"</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"创建蓝牙连接"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"允许应用程序查看本地蓝牙手机的配置,以及建立或接受与配对设备的连接。"</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"停用键锁"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"允许应用程序停用键锁和任何关联的密码安全设置。例如,在手机上接听电话时停用键锁,在通话结束后重新启用键锁。"</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"读取同步设置"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"允许应用程序读取用户在用户词典中存储的任意私有字词、名称和短语。"</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"写入用户定义的词典"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"允许应用程序向用户词典中写入新词。"</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"修改/删除 SD 卡中的内容"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"修改/删除 SD 卡中的内容"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"允许应用程序写入 SD 卡。"</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"允许应用程序写入 SD 卡。"</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"修改/删除 SD 卡中的内容"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"允许应用程序写入 SD 卡。"</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"访问缓存文件系统"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"允许应用程序读取和写入缓存文件系统。"</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"限制密码选择"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"限制您能够使用的密码类型。"</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"监控登录尝试"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"监控对于登录设备和执行某项操作的失败尝试。"</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"重置密码"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"强行更新密码,您需要获得管理员提供的新密码才能登录。"</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"强行锁定"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"控制何时锁定设备,这需要您重新输入密码。"</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"限制密码选择"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"限制您能够使用的密码类型。"</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"监控登录尝试"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"监控对于登录设备和执行某项操作的失败尝试。"</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"重置密码"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"强行更新密码,您需要获得管理员提供的新密码才能登录。"</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"强行锁定"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"控制何时锁定设备,这需要您重新输入密码。"</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"清除所有数据"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"恢复出厂设置,这会在不提示确认的情况下删除您的所有数据。"</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"恢复出厂设置,这会在不提示确认的情况下删除您的所有数据。"</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"请设置在启用政策的情况下要使用的设备全局代理。只有第一设备管理员才可设置有效的全局代理。"</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"住宅"</item>
     <item msgid="869923650527136615">"手机"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"通过 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"时间:<xliff:g id="DATE">%1$s</xliff:g>,方式:<xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"输入 PIN 码"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"输入密码进行解锁"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"输入 PIN 进行解锁"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 码不正确!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"要解锁,请先按 MENU 再按 0。"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"急救或报警电话"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"返回通话"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"正确!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"很抱歉,请重试"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"很抱歉,请重试"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"正在充电 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"已充满。"</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 卡被锁定"</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"正在解锁 SIM 卡..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次绘错了自己的解锁图案。"\n\n"请在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒后重试。"</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次错误地输入了密码。"\n\n"请在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒后重试。"</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次错误地输入了 PIN。"\n\n"请在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒后重试。"</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次绘错了自己的解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%d</xliff:g> 次后仍不成功,系统会要求您使用自己的 Google 登录信息解锁手机。"\n\n"请在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒后重试。"</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g> 秒后重试。"</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"忘记了图案?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"是否从该页面导航至它处?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"选择“确定”继续,或选择“取消”留在当前页面。"</string>
     <string name="save_password_label" msgid="6860261758665825069">"确认"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"提示:点按两次可放大和缩小。"</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"读取浏览器的历史记录和书签"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"允许应用程序读取用浏览器访问过的所有网址,以及浏览器的所有书签。"</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"写入浏览器的历史记录和书签"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"允许应用程序修改存储在手机中的浏览器历史记录或书签。恶意应用程序可借此清除或修改浏览器数据。"</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"修改浏览器的地理位置权限"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"允许应用程序修改浏览器的地理位置权限。恶意应用程序会利用这一点将位置信息发送到任意网站。"</string>
     <string name="save_password_message" msgid="767344687139195790">"是否希望浏览器记住此密码?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"全选"</string>
-    <string name="selectText" msgid="4862359311088898878">"选择文字"</string>
     <string name="cut" msgid="3092569408438626261">"剪切"</string>
     <string name="copy" msgid="2681946229533511987">"复制"</string>
     <string name="paste" msgid="5629880836805036433">"粘贴"</string>
     <string name="copyUrl" msgid="2538211579596067402">"复制网址"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"选择文字..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"文字选择"</string>
     <string name="inputMethod" msgid="1653630062304567879">"输入法"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"将“<xliff:g id="WORD">%s</xliff:g>”添加到词典"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"编辑文字"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"文字操作"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"存储空间不足"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"手机内存空间所剩不多了。"</string>
     <string name="ok" msgid="5970060430562524910">"确定"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"确定"</string>
     <string name="no" msgid="5141531044935541497">"取消"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"注意"</string>
+    <string name="loading" msgid="1760724998928255250">"正在载入..."</string>
     <string name="capital_on" msgid="1544682755514494298">"打开"</string>
     <string name="capital_off" msgid="6815870386972805832">"关闭"</string>
     <string name="whichApplication" msgid="4533185947064773386">"使用以下方式发送"</string>
@@ -763,9 +750,12 @@
     <string name="force_close" msgid="3653416315450806396">"强行关闭"</string>
     <string name="report" msgid="4060218260984795706">"报告"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</string>
-    <string name="launch_warning_title" msgid="8323761616052121936">"应用程序已重定向"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g>目前正在运行。"</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g>已启动。"</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"全部显示"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB 大容量存储"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB 已连接"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"您已通过 USB 将手机连接至计算机。如果您要在计算机和 Android 手机的 SD 卡之间复制文件,请点击下面的按钮。"</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"您已通过 USB 将手机连接至计算机。如果您要在计算机和 Android 手机的 SD 卡之间复制文件,请点击下面的按钮。"</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"您已通过 USB 将手机连接至计算机。如果您要在计算机和 Android 手机的 SD 卡之间复制文件,请点击下面的按钮。"</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"打开 USB 存储设备"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"使用 SD 卡进行 USB 存储时出现问题。"</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"使用 SD 卡进行 USB 存储时出现问题。"</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"使用 SD 卡进行 USB 存储时出现问题。"</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB 已连接"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"选择将文件复制到计算机或从计算机复制到存储设备。"</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"关闭 USB 存储设备"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"选中以关闭 USB 存储设备。"</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"使用中的 USB 存储设备"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"在关闭 USB 存储设备前,请确保您已从计算机中卸载(“弹出”)Android 手机的 SD 卡。"</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"在关闭 USB 存储设备前,请确保您已从计算机中卸载(“弹出”)Android 手机的 SD 卡。"</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"在关闭 USB 存储设备前,请确保您已从计算机中卸载(“弹出”)Android 手机的 SD 卡。"</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"关闭 USB 存储设备"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"关闭 USB 存储设备时遇到问题。请检查并确保已卸载了 USB 主设备,然后重试。"</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"打开 USB 存储设备"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"如果您打开了 USB 存储设备,则您当前使用的某些应用程序会停止,而且在您关闭 USB 存储设备前可能都无法使用。"</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB 操作失败"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"确定"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"格式化 SD 卡"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"格式化 SD 卡"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"确定要将 SD 卡格式化吗?该卡上的所有数据都将丢失。"</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"确定要将 SD 卡格式化吗?该卡上的所有数据都将丢失。"</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"格式化 SD 卡"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"确定要将 SD 卡格式化吗?该卡上的所有数据都将丢失。"</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"格式化"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"已连接 USB 调试"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"选择停用 USB 调试。"</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"候选"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"正在准备 SD 卡"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"正在准备 SD 卡"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"正在准备 SD 卡"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"正在检查是否有错误。"</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"空 SD 卡"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"空 SD 卡"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"SD 卡为空或使用不支持的文件系统。"</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SD 卡无文件系统,或文件系统不受支持。"</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"SD 卡受损"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"SD 卡受损"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"SD 卡受损。您可能需要重新格式化您的卡。"</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD 卡已损坏。您可能必须将其重新格式化。"</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"SD 卡未正常移除"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SD 卡未正常移除"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"请先卸载 SD 卡,再将其移除,以防数据丢失。"</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"请先卸载 SD 卡,再将其移除,以防数据丢失。"</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"SD 卡已安全移除"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"SD 卡已安全移除"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"现在可以安全移除 SD 卡。"</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"您现在可以安全移除 SD 卡。"</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"已移除 SD 卡"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"已移除 SD 卡"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"SD 卡已移除。请插入新 SD 卡来增加您的设备存储空间。"</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD 卡已移除。请插入新的 SD 卡。"</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"空 SD 卡"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD 卡无文件系统,或文件系统不受支持。"</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"SD 卡受损"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD 卡已损坏。您可能必须将其重新格式化。"</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD 卡未正常移除"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"请先卸载 SD 卡,再将其移除,以防数据丢失。"</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SD 卡已安全移除"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"您现在可以安全移除 SD 卡。"</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"已移除 SD 卡"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD 卡已移除。请插入新的 SD 卡。"</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"找不到匹配的活动"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"更新组件使用情况统计"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"允许修改收集的组件使用情况统计。普通应用程序不能使用此权限。"</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"基于预共享密钥的 L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"基于证书的 L2TP/IPSec VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"选择文件"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"未选定任何文件"</string>
     <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="tethered_notification_title" msgid="3146694234398202601">"USB 绑定或热点已启用"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"触摸可进行配置"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"上一步"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"下一步"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"跳过"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"手机流量过多"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"轻触以了解有关手机流量详情"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"已超出手机数据上限"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"轻触以了解有关手机流量详情"</string>
+    <string name="no_matches" msgid="8129421908915840737">"无匹配项"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"在网页上查找"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 个匹配项"</item>
+    <item quantity="other" msgid="4641872797067609177">"第 <xliff:g id="INDEX">%d</xliff:g> 项,共 <xliff:g id="TOTAL">%d</xliff:g> 项"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 2439042..5dbd8a3 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -169,8 +169,7 @@
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"開發工具"</string>
     <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"只有開發者需要此功能。"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"儲存"</string>
-    <!-- outdated translation 9203302214915355774 -->     <string name="permgroupdesc_storage" product="nosdcard" msgid="5455804353220581312">"存取 SD 卡。"</string>
-    <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"存取 SD 卡。"</string>
+    <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>
@@ -267,8 +266,10 @@
     <string name="permdesc_clearAppCache" msgid="7740465694193671402">"允許應用程式刪除快取目錄裡的檔案,釋放儲存空間。此操作通常受到系統程序嚴格限制。"</string>
     <string name="permlab_movePackage" msgid="728454979946503926">"移動應用程式資源"</string>
     <string name="permdesc_movePackage" msgid="6323049291923925277">"允許應用程式將應用程式資源從內部媒體移到外部媒體,反之亦可。"</string>
-    <!-- outdated translation 4811921703882532070 -->     <string name="permlab_readLogs" msgid="6615778543198967614">"讀取系統記錄檔"</string>
-    <!-- outdated translation 2257937955580475902 -->     <string name="permdesc_readLogs" msgid="8896449437464867766">"允許應用程式讀取系統記錄檔。此項操作可讓應用程式了解目前手機操作狀態,但內容應不含任何個人或隱私資訊。"</string>
+    <!-- no translation found for permlab_readLogs (6615778543198967614) -->
+    <skip />
+    <!-- no translation found for permdesc_readLogs (8896449437464867766) -->
+    <skip />
     <string name="permlab_diagnostic" msgid="8076743953908000342">"讀寫 diag 擁有的資源"</string>
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"允許應用程式讀寫 diag 群組的資源;例如:/dev 裡的檔案。這可能會影響系統穩定性與安全性。此功能僅供製造商或技術人員用於硬體規格偵測。"</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"啟用或停用應用程式元件"</string>
@@ -403,22 +404,6 @@
     <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"允許應用程式設定本機藍牙電話,以及偵測與配對其他遠端裝置。"</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"建立藍牙連線"</string>
     <string name="permdesc_bluetooth" msgid="762515380679392945">"允許應用程式檢視本機藍牙電話設定,並與其他配對裝置連線。"</string>
-    <!-- no translation found for permlab_nfcAdmin (2251302233012250056) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcAdmin (1935842611464895785) -->
-    <skip />
-    <!-- no translation found for permlab_nfcRaw (4423471711114325708) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcRaw (5533022736559385665) -->
-    <skip />
-    <!-- no translation found for permlab_nfcNotify (478470360082792358) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcNotify (2990053517962070617) -->
-    <skip />
-    <!-- no translation found for permlab_nfcLlcp (4972051461007030426) -->
-    <skip />
-    <!-- no translation found for permdesc_nfcLlcp (91592127786004640) -->
-    <skip />
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"停用按鍵鎖定"</string>
     <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"允許應用程式停用按鍵鎖定以及其他相關的密碼安全性。例如:收到來電時解除按鍵鎖定,通話結束後重新啟動按鍵鎖定。"</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"讀取同步處理設定"</string>
@@ -435,22 +420,23 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"允許應用程式讀取使用者儲存在使用者字典內的任何私人字詞、名稱和詞組。"</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"寫入使用者定義的字典"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"允許應用程式將新字詞寫入使用者的字典。"</string>
-    <!-- outdated translation 8079403759001777291 -->     <string name="permlab_sdcardWrite" product="nosdcard" msgid="993707436941016913">"修改/刪除 SD 卡的內容"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"修改/刪除 SD 卡的內容"</string>
-    <!-- outdated translation 6643963204976471878 -->     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="4131871541127211526">"允許應用程式寫入 SD 卡。"</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"允許應用程式寫入 SD 卡。"</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"修改/刪除 SD 卡的內容"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"允許應用程式寫入 SD 卡。"</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"存取快取檔案系統"</string>
     <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"允許應用程式讀取及寫入快取檔案系統。"</string>
-    <!-- outdated translation 4307861496302850201 -->     <string name="policylab_limitPassword" msgid="4497420728857585791">"限制密碼設定規則"</string>
-    <!-- outdated translation 1719877245692318299 -->     <string name="policydesc_limitPassword" msgid="9083400080861728056">"限制允許使用的密碼類型。"</string>
-    <!-- outdated translation 7374780712664285321 -->     <string name="policylab_watchLogin" msgid="914130646942199503">"查看登入嘗試記錄"</string>
-    <!-- outdated translation 1961251179624843483 -->     <string name="policydesc_watchLogin" msgid="7227578260165172673">"監視者無法登入裝置執行部分動作。"</string>
-    <!-- outdated translation 9084772090797485420 -->     <string name="policylab_resetPassword" msgid="2620077191242688955">"重設密碼"</string>
-    <!-- outdated translation 3332167600331799991 -->     <string name="policydesc_resetPassword" msgid="5391240616981297361">"強制重新設定密碼。您必須取得以管理員提供的新密碼,才能登入。"</string>
-    <!-- outdated translation 5760466025247634488 -->     <string name="policylab_forceLock" msgid="2274085384704248431">"強制鎖定"</string>
-    <!-- outdated translation 2819868664946089740 -->     <string name="policydesc_forceLock" msgid="5696964126226028442">"裝置鎖定時可取得控制,但必須重新輸入密碼。"</string>
+    <string name="policylab_limitPassword" msgid="4307861496302850201">"限制密碼設定規則"</string>
+    <string name="policydesc_limitPassword" msgid="1719877245692318299">"限制允許使用的密碼類型。"</string>
+    <string name="policylab_watchLogin" msgid="7374780712664285321">"查看登入嘗試記錄"</string>
+    <string name="policydesc_watchLogin" msgid="1961251179624843483">"監視者無法登入裝置執行部分動作。"</string>
+    <string name="policylab_resetPassword" msgid="9084772090797485420">"重設密碼"</string>
+    <string name="policydesc_resetPassword" msgid="3332167600331799991">"強制重新設定密碼。您必須取得以管理員提供的新密碼,才能登入。"</string>
+    <string name="policylab_forceLock" msgid="5760466025247634488">"強制鎖定"</string>
+    <string name="policydesc_forceLock" msgid="2819868664946089740">"裝置鎖定時可取得控制,但必須重新輸入密碼。"</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"清除所有資料"</string>
-    <!-- outdated translation 2314060933796396205 -->     <string name="policydesc_wipeData" msgid="7669895333814222586">"重設為原廠設定 (系統會刪除所有資料,且不會向您進行確認)。"</string>
+    <string name="policydesc_wipeData" msgid="2314060933796396205">"重設為原廠設定 (系統會刪除所有資料,且不會向您進行確認)。"</string>
+    <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+    <skip />
+    <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"設定政策啟用時所要使用的裝置全域 Proxy,只有第一個裝置管理員所設定的全域 Proxy 具有效力。"</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"住家電話"</item>
     <item msgid="869923650527136615">"行動電話"</item>
@@ -552,10 +538,9 @@
     <skip />
     <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
     <skip />
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"透過 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>透過「<xliff:g id="SOURCE">%2$s</xliff:g>」"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"輸入 PIN 碼"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"輸入密碼即可解鎖"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"輸入 PIN 進行解鎖"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 碼錯誤!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"如要解鎖,請按 Menu 鍵,然後按 0。"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急電話號碼"</string>
@@ -568,6 +553,7 @@
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"返回通話"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"正確!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"很抱歉,請再試一次"</string>
+    <string name="lockscreen_password_wrong" msgid="6237443657358168819">"很抱歉,請再試一次"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"正在充電 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"充電完成。"</string>
     <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -582,6 +568,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 卡已鎖定。"</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"解鎖 SIM 卡中..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。"\n\n" 請在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再嘗試。"</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"您已 <xliff:g id="NUMBER_0">%d</xliff:g> 次輸入不正確的密碼。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再試一次。"</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"您已 <xliff:g id="NUMBER_0">%d</xliff:g> 次輸入不正確的 PIN。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再試一次。"</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。再錯誤 <xliff:g id="NUMBER_1">%d</xliff:g> 次後,系統會要求使用 Google 登入來解鎖。"\n\n" 請在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒後再試一次。"</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g> 秒後再試一次。"</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"忘記解鎖圖形?"</string>
@@ -610,14 +598,12 @@
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"離開此頁?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n" 選取 [確定] 離開此頁;或 [取消] 留在此頁。"</string>
     <string name="save_password_label" msgid="6860261758665825069">"確認"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"提示:輕按兩下可放大縮小。"</string>
+    <!-- no translation found for autofill_this_form (8940110866775097494) -->
+    <skip />
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"讀取瀏覽器的記錄與書籤"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"允許應用程式讀取瀏覽器曾經造訪過的所有網址,以及瀏覽器的所有書籤。"</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"寫入瀏覽器的記錄與書籤"</string>
     <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"允許應用程式修改儲存在電話上的瀏覽記錄或書籤。請注意:惡意應用程式可能會使用此選項來清除或修改您瀏覽器的資料。"</string>
-    <!-- no translation found for permlab_setAlarm (5924401328803615165) -->
-    <skip />
-    <!-- no translation found for permdesc_setAlarm (5966966598149875082) -->
-    <skip />
     <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"修改瀏覽器地理資訊的權限"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"允許應用程式修改瀏覽器的地理位置權限,惡意應用程式可能會透過此方式允許將您的位置資訊任意傳送給某些網站。"</string>
     <string name="save_password_message" msgid="767344687139195790">"是否記住此密碼?"</string>
@@ -730,14 +716,14 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"全部選取"</string>
-    <string name="selectText" msgid="4862359311088898878">"選取字詞"</string>
     <string name="cut" msgid="3092569408438626261">"剪下"</string>
     <string name="copy" msgid="2681946229533511987">"複製"</string>
     <string name="paste" msgid="5629880836805036433">"貼上"</string>
     <string name="copyUrl" msgid="2538211579596067402">"複製網址"</string>
+    <string name="selectTextMode" msgid="6738556348861347240">"選取文字..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"選取文字"</string>
     <string name="inputMethod" msgid="1653630062304567879">"輸入方式"</string>
-    <string name="addToDictionary" msgid="8793624991686948709">"將「<xliff:g id="WORD">%s</xliff:g>」新增至字典"</string>
-    <string name="editTextMenuTitle" msgid="1672989176958581452">"編輯文字"</string>
+    <string name="editTextMenuTitle" msgid="4909135564941815494">"文字動作"</string>
     <string name="low_internal_storage_view_title" msgid="1399732408701697546">"儲存空間即將不足"</string>
     <string name="low_internal_storage_view_text" msgid="635106544616378836">"手機儲存空間即將不足。"</string>
     <string name="ok" msgid="5970060430562524910">"確定"</string>
@@ -745,6 +731,7 @@
     <string name="yes" msgid="5362982303337969312">"確定"</string>
     <string name="no" msgid="5141531044935541497">"取消"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"注意"</string>
+    <string name="loading" msgid="1760724998928255250">"載入中..."</string>
     <string name="capital_on" msgid="1544682755514494298">"開啟"</string>
     <string name="capital_off" msgid="6815870386972805832">"關閉"</string>
     <string name="whichApplication" msgid="4533185947064773386">"完成操作需使用"</string>
@@ -763,9 +750,12 @@
     <string name="force_close" msgid="3653416315450806396">"強制關閉"</string>
     <string name="report" msgid="4060218260984795706">"回報"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</string>
-    <string name="launch_warning_title" msgid="8323761616052121936">"應用程式已重新導向"</string>
-    <string name="launch_warning_replace" msgid="6202498949970281412">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」現在正在執行。"</string>
-    <string name="launch_warning_original" msgid="188102023021668683">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」原先已啟動。"</string>
+    <!-- no translation found for launch_warning_title (8323761616052121936) -->
+    <skip />
+    <!-- no translation found for launch_warning_replace (6202498949970281412) -->
+    <skip />
+    <!-- no translation found for launch_warning_original (188102023021668683) -->
+    <skip />
     <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>
@@ -812,28 +802,23 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"顯示全部"</b></string>
     <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB 大量儲存裝置"</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB 已連接"</string>
-    <!-- outdated translation 4796759646167247178 -->     <string name="usb_storage_message" product="nosdcard" msgid="8231462750010066896">"已透過 USB 連接手機與電腦。如要從電腦或 Android 系統的 SD 卡複製檔案,請選取下方按鈕。"</string>
-    <string name="usb_storage_message" product="default" msgid="4796759646167247178">"已透過 USB 連接手機與電腦。如要從電腦或 Android 系統的 SD 卡複製檔案,請選取下方按鈕。"</string>
+    <string name="usb_storage_message" msgid="4796759646167247178">"已透過 USB 連接手機與電腦。如要從電腦或 Android 系統的 SD 卡複製檔案,請選取下方按鈕。"</string>
     <string name="usb_storage_button_mount" msgid="1052259930369508235">"開啟 USB 儲存裝置"</string>
-    <!-- outdated translation 2534784751603345363 -->     <string name="usb_storage_error_message" product="nosdcard" msgid="4818347756402740261">"把 SD 卡當成 USB 儲存裝置時發生問題。"</string>
-    <string name="usb_storage_error_message" product="default" msgid="2534784751603345363">"把 SD 卡當成 USB 儲存裝置時發生問題。"</string>
+    <string name="usb_storage_error_message" msgid="2534784751603345363">"把 SD 卡當成 USB 儲存裝置時發生問題。"</string>
     <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB 已連接"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"選取此項將檔案複製到電腦,或從電腦複製。"</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"關閉 USB 儲存裝置"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"選取此處關閉 USB 儲存裝置。"</string>
     <string name="usb_storage_stop_title" msgid="660129851708775853">"USB 儲存空間使用中"</string>
-    <!-- outdated translation 3613713396426604104 -->     <string name="usb_storage_stop_message" product="nosdcard" msgid="8745345260780267185">"關閉 USB 儲存裝置前,請務必先將 Android 系統的 SD 卡從電腦上卸下 (退出)。"</string>
-    <string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"關閉 USB 儲存裝置前,請務必先將 Android 系統的 SD 卡從電腦上卸下 (退出)。"</string>
+    <string name="usb_storage_stop_message" msgid="3613713396426604104">"關閉 USB 儲存裝置前,請務必先將 Android 系統的 SD 卡從電腦上卸下 (退出)。"</string>
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"關閉 USB 儲存裝置"</string>
     <string name="usb_storage_stop_error_message" msgid="143881914840412108">"關閉 USB 儲存裝置時發生問題。請檢查您是否已卸載 USB Host,然後再試一次。"</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"開啟 USB 儲存裝置"</string>
     <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"如果您開啟 USB 儲存裝置,則您正在使用的某些應用程式會停止運作,而且可能無法使用,待您將 USB 儲存裝置關閉才會恢復正常。"</string>
     <string name="dlg_error_title" msgid="8048999973837339174">"USB 操作失敗"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"確定"</string>
-    <!-- outdated translation 8663247929551095854 -->     <string name="extmedia_format_title" product="nosdcard" msgid="6577908006949742217">"將 SD 卡格式化"</string>
-    <string name="extmedia_format_title" product="default" msgid="8663247929551095854">"將 SD 卡格式化"</string>
-    <!-- outdated translation 3621369962433523619 -->     <string name="extmedia_format_message" product="nosdcard" msgid="3470815140068502824">"確定要將 SD 卡格式化嗎?該 SD 卡中的所有資料將會遺失。"</string>
-    <string name="extmedia_format_message" product="default" msgid="3621369962433523619">"確定要將 SD 卡格式化嗎?該 SD 卡中的所有資料將會遺失。"</string>
+    <string name="extmedia_format_title" msgid="8663247929551095854">"將 SD 卡格式化"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"確定要將 SD 卡格式化嗎?該 SD 卡中的所有資料將會遺失。"</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"格式化"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"USB 偵錯模式已啟用"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"選取以停用 USB 偵錯。"</string>
@@ -841,29 +826,18 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"待選項目"</u></string>
-    <!-- outdated translation 5457603418970994050 -->     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="4239516675028438997">"正在準備 SD 卡"</string>
-    <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"正在準備 SD 卡"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"正在準備 SD 卡"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"正在檢查錯誤。"</string>
-    <!-- outdated translation 780477838241212997 -->     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="3937345577983730873">"SD 卡為空白"</string>
-    <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"SD 卡為空白"</string>
-    <!-- outdated translation 1312266820092958014 -->     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="1606660756124629708">"SD 卡為空白或使用不支援的檔案系統。"</string>
-    <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"SD 卡內無檔案系統,或檔案系統不受支援。"</string>
-    <!-- outdated translation 6410723906019100189 -->     <string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="1724619902076528889">"SD 卡已損壞"</string>
-    <string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"SD 卡已損壞"</string>
-    <!-- outdated translation 2679412884290061775 -->     <string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="6460311835130671194">"SD 卡已損壞。您可能需要將 SD 卡重新格式化。"</string>
-    <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD 卡已毀損,您可能必須予以重新格式化。"</string>
-    <!-- outdated translation 6872152882604407837 -->     <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="2582175016060432034">"SD 卡未正常移除"</string>
-    <string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SD 卡未正常移除"</string>
-    <!-- outdated translation 7260183293747448241 -->     <string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="2005396464494648937">"請先卸載 SD 卡,再將其移除,以免資料遺失。"</string>
-    <string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"請先卸載 SD 卡,再將其移除,以免資料遺失。"</string>
-    <!-- outdated translation 6729801130790616200 -->     <string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="36525881018679515">"可安全移除 SD 卡"</string>
-    <string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"可安全移除 SD 卡"</string>
-    <!-- outdated translation 7613960686747592770 -->     <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="7503769106285188703">"現在可以安全移除 SD 卡。"</string>
-    <string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"您現在可以安全地移除 SD 卡。"</string>
-    <!-- outdated translation 8902518030404381318 -->     <string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="1874889764367085562">"已移除 SD 卡"</string>
-    <string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"已移除 SD 卡"</string>
-    <!-- outdated translation 4205117227342822275 -->     <string name="ext_media_nomedia_notification_message" product="nosdcard" msgid="2730890441331344776">"已移除 SD 卡。請插入新的 SD 卡來增加裝置的儲存容量。"</string>
-    <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD 卡已移除,請插入新的 SD 卡。"</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"SD 卡為空白"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD 卡內無檔案系統,或檔案系統不受支援。"</string>
+    <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"SD 卡已損壞"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD 卡已毀損,您可能必須予以重新格式化。"</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD 卡未正常移除"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"請先卸載 SD 卡,再將其移除,以免資料遺失。"</string>
+    <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"可安全移除 SD 卡"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"您現在可以安全地移除 SD 卡。"</string>
+    <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"已移除 SD 卡"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD 卡已移除,請插入新的 SD 卡。"</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"找不到符合的活動"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"更新元件使用統計資料"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"允許修改收集到的元件使用統計資料。一般應用程式不會使用此功能。"</string>
@@ -898,15 +872,24 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"採用預先共用金鑰的 L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"採用憑證的 L2TP/IPSec VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"未選擇任何檔案"</string>
     <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="tethered_notification_title" msgid="3146694234398202601">"數據連線或無線基地台已啟用"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"輕觸以設定"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"返回"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"繼續"</string>
+    <string name="skip_button_label" msgid="1275362299471631819">"略過"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"高行動資料用量"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"輕觸即可瞭解更多有關行動資料用量的詳細資訊"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"已達行動資料上限"</string>
     <string name="throttled_notification_message" msgid="4712369856601275146">"輕觸即可瞭解更多有關行動資料用量的詳細資訊"</string>
+    <string name="no_matches" msgid="8129421908915840737">"沒有相符項目"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"在頁面中尋找"</string>
+  <plurals name="matches_found">
+    <item quantity="one" msgid="8167147081136579439">"1 個相符項目"</item>
+    <item quantity="other" msgid="4641872797067609177">"第 <xliff:g id="INDEX">%d</xliff:g> 個相符項目 (共 <xliff:g id="TOTAL">%d</xliff:g> 個相符項目)"</item>
+  </plurals>
 </resources>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index a33851c..830fb01 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -103,4 +103,16 @@
         <item>3</item>
     </integer-array>
 
+    <!-- Used in LocalePicker -->
+    <string-array translatable="false" name="special_locale_codes">
+        <item>zh_CN</item>
+        <item>zh_TW</item>
+    </string-array>
+
+    <!-- Used in LocalePicker -->
+    <string-array translatable="false" name="special_locale_names">
+        <item>中文 (简体)</item>
+        <item>中文 (繁體)</item>
+    </string-array>
+
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6f0ce71..33d3eeb 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.
 -->
 
@@ -46,6 +46,7 @@
              theme does not set this value, meaning it is based on whether the
              window is floating. -->
         <attr name="backgroundDimEnabled" format="boolean" />
+        
 
         <!-- =========== -->
         <!-- Text styles -->
@@ -95,6 +96,15 @@
 
         <!-- Text color for urls in search suggestions, used by things like global search and the browser. @hide -->
         <attr name="textColorSearchUrl" format="reference|color" />
+
+        <!-- Color of highlighted text, when used in a light theme. @hide -->
+        <attr name="textColorHighlightInverse" format="reference|color" />
+        <!-- Color of link text (URLs), when used in a light theme. @hide -->
+        <attr name="textColorLinkInverse" format="reference|color" />
+
+        <!-- Color of list item text in alert dialogs. -->
+        <attr name="textColorAlertDialogListItem" format="reference|color" />
+        
         <!-- Search widget more corpus result item background. -->
         <attr name="searchWidgetCorpusItemBackground" format="reference|color" />
 
@@ -117,10 +127,20 @@
         <!-- Text color, typeface, size, and style for system search result subtitle. Defaults to primary inverse text color. -->
         <attr name="textAppearanceSearchResultSubtitle" format="reference" />
 
-
         <!-- Text color, typeface, size, and style for the text inside of a button. -->
         <attr name="textAppearanceButton" format="reference" />
 
+        <!-- Text color, typeface, size, and style for the text inside of a popup menu. -->
+        <attr name="textAppearanceLargePopupMenu" format="reference" />
+
+        <!-- Text color, typeface, size, and style for small text inside of a popup menu. -->
+        <attr name="textAppearanceSmallPopupMenu" format="reference" />
+
+        <!-- EditText text foreground color. -->
+        <attr name="editTextColor" format="reference|color" />
+        <!-- EditText background drawable. -->
+        <attr name="editTextBackground" format="reference" />
+
         <!-- A styled string, specifying the style to be used for showing
              inline candidate text when composing with an input method.  The
              text itself will be ignored, but the style spans will be applied
@@ -137,6 +157,12 @@
         <!-- Drawable to use for single choice indicators. -->
         <attr name="listChoiceIndicatorSingle" format="reference" />
 
+        <!-- Drawable used as a background for selected list items. -->
+        <attr name="listChoiceBackgroundIndicator" format="reference" />
+
+        <!-- Drawable used as a background for activated items. -->
+        <attr name="activatedBackgroundIndicator" format="reference" />
+
         <!-- ============= -->
         <!-- Button styles -->
         <!-- ============= -->
@@ -174,6 +200,8 @@
         <!-- The list item height for search results. @hide -->
         <attr name="searchResultListItemHeight" format="dimension" />
         <attr name="listDivider" format="reference" />
+        <!-- The list divider used in alert dialogs. -->
+        <attr name="listDividerAlertDialog" format="reference" />
         <!-- TextView style for list separators. -->
         <attr name="listSeparatorTextViewStyle" format="reference" />
         <!-- The preferred left padding for an expandable list item (for child-specific layouts,
@@ -244,6 +272,19 @@
              {@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" />
+
+        <!-- Flag indicating whether this window's Action Bar should overlay
+             application content. Does nothing if the window would not
+             have an Action Bar. -->
+        <attr name="windowActionBarOverlay" format="boolean" />
+
+        <!-- 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 +431,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. -->
@@ -427,6 +474,41 @@
         <!-- Reference to a style that will be used for the window containing a text
              selection anchor. -->
         <attr name="textSelectHandleWindowStyle" format="reference" />
+        <!-- Default ListPopupWindow style. -->
+        <attr name="listPopupWindowStyle" format="reference" />
+        <!-- Default PopupMenu style. -->
+        <attr name="popupMenuStyle" format="reference" />
+
+        <!-- =================== -->
+        <!-- Action bar styles   -->
+        <!-- =================== -->
+        <eat-comment />
+        <!-- Default amount of padding to use between action buttons. -->
+        <attr name="actionButtonPadding" format="dimension" />
+        <!-- Default style for tabs within an action bar -->
+        <attr name="actionBarTabStyle" format="reference" />
+        <attr name="actionBarTabBarStyle" format="reference" />
+        <attr name="actionBarTabTextStyle" format="reference" />
+        <attr name="actionOverflowButtonStyle" format="reference" />
+        <!-- Reference to a style for the Action Bar -->
+        <attr name="actionBarStyle" format="reference" />
+        <!-- Size of the Action Bar, including the contextual
+             bar used to present Action Modes. -->
+        <attr name="actionBarSize" format="dimension" >
+            <enum name="wrap_content" value="0" />
+        </attr>
+
+
+        <!-- =================== -->
+        <!-- Action mode styles  -->
+        <!-- =================== -->
+        <eat-comment />
+        <attr name="actionModeStyle" format="reference" />
+        <attr name="actionModeCloseButtonStyle" format="reference" />
+        <!-- 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   -->
@@ -981,6 +1063,9 @@
         <attr name="textColor" />
         <attr name="backgroundDimEnabled" />
         <attr name="backgroundDimAmount" />
+        <attr name="windowActionBar" />
+        <attr name="windowActionModeOverlay" />
+        <attr name="windowActionBarOverlay" />
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -997,6 +1082,18 @@
         <attr name="centerMedium" format="reference|color" />
     </declare-styleable>
 
+    <!-- Fragment animation class attributes. -->
+    <declare-styleable name="FragmentAnimation">
+        <attr name="fragmentOpenEnterAnimation" format="reference" />
+        <attr name="fragmentOpenExitAnimation" format="reference" />
+        <attr name="fragmentCloseEnterAnimation" format="reference" />
+        <attr name="fragmentCloseExitAnimation" format="reference" />
+        <attr name="fragmentNextEnterAnimation" format="reference" />
+        <attr name="fragmentNextExitAnimation" format="reference" />
+        <attr name="fragmentPrevEnterAnimation" format="reference" />
+        <attr name="fragmentPrevExitAnimation" format="reference" />
+    </declare-styleable>
+
     <!-- Window animation class attributes. -->
     <declare-styleable name="WindowAnimation">
         <!-- The animation used when a window is being added. -->
@@ -1007,7 +1104,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" />
@@ -1048,7 +1145,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). -->
@@ -1065,7 +1162,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
@@ -1350,25 +1447,17 @@
              <code>public void sayHello(View v)</code> method of your context
              (typically, your Activity). -->
         <attr name="onClick" format="string" />
-
-        <!-- Defines overscrolling behavior. This property is used only if the
-             View is scrollable. Overscrolling is the ability for the user to
-             scroll a View beyond its content boundaries into empty space. -->
-        <attr name="overscrollMode">
-            <!-- Always allow the user to overscroll the content. -->
-            <enum name="always" value="0" />
-            <!-- Only allow the user to overscroll content if the content is large
-                 enough to meaningfully scroll. -->
-            <enum name="ifContentScrolls" value="1" />
-            <!-- Never overscroll. -->
-            <enum name="never" value="2" />
-        </attr>
     </declare-styleable>
 
     <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any
          of its subclasses.  Also see {@link #ViewGroup_Layout} for
          attributes that this class processes in its children. -->
     <declare-styleable name="ViewGroup">
+        <!-- Defines whether changes in layout (caused by adding and removing items) should
+             cause a LayoutTransition to run. When this flag is set to true, a default
+             LayoutTransition object will be set on the ViewGroup container and default
+             animations will run when these layout changes occur.-->
+        <attr name="animateLayoutChanges" format="boolean" />
         <!-- Defines whether a child is limited to draw inside of its bounds or not.
              This is useful with animations that scale the size of the children to more
              than 100% for instance. In such a case, this property should be set to false
@@ -1422,6 +1511,17 @@
             <enum name="blocksDescendants" value="2" />
         </attr>
 
+        <!-- Sets whether this ViewGroup should split MotionEvents
+             to separate child views during touch event dispatch.
+             If false (default), touch events will be dispatched to
+             the child view where the first pointer went down until
+             the last pointer goes up.
+             If true, touch events may be dispatched to multiple children.
+             MotionEvents for each pointer will be dispatched to the child
+             view where the initial ACTION_DOWN event happened.
+             See {@link android.view.ViewGroup#setMotionEventSplittingEnabled(boolean)}
+             for more information. -->
+        <attr name="splitMotionEvents" format="boolean" />
     </declare-styleable>
 
     <!-- A {@link android.view.ViewStub} lets you lazily include other XML layouts
@@ -1523,6 +1623,28 @@
         <attr name="isDefault" format="boolean" />
     </declare-styleable>
 
+    <!-- This is the subtype of InputMethod. Subtype can describe locales (e.g. en_US, fr_FR...)
+         and modes (e.g. voice, keyboard...), and is used for IME switch. This subtype allows
+         the system to call the specified subtype of the IME directly. -->
+    <declare-styleable name="InputMethod_Subtype">
+        <!-- The name of the subtype. -->
+        <attr name="label" />
+        <!-- The icon of the subtype. -->
+        <attr name="icon" />
+        <!-- The locale of the subtype. This string should be a locale (e.g. en_US, fr_FR...)
+             and will be passed to the IME when the framework calls the IME
+             with the subtype. This is also used by the framework to know the supported locales
+             of the IME.  -->
+        <attr name="imeSubtypeLocale" format="string" />
+        <!-- The mode of the subtype. This string can be a mode (e.g. voice, keyboard...) and this
+             string will be passed to the IME when the framework calls the IME with the
+             subtype.  -->
+        <attr name="imeSubtypeMode" format="string" />
+        <!-- The extra value of the subtype. This string can be any string and will be passed to
+             the IME when the framework calls the IME with the subtype.  -->
+        <attr name="imeSubtypeExtraValue" format="string" />
+    </declare-styleable>
+
     <!-- =============================== -->
     <!-- Widget package class attributes -->
     <!-- =============================== -->
@@ -1572,6 +1694,28 @@
              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" />
+        <!-- Defines the choice behavior for the view. By default, lists do not have
+             any choice behavior. By setting the choiceMode to singleChoice, the list
+             allows up to one item to be in a chosen state. By setting the choiceMode to
+             multipleChoice, the list allows any number of items to be chosen.
+             Finally, by setting the choiceMode to multipleChoiceModal the list allows
+             any number of items to be chosen in a special selection mode.
+             The application will supply a
+             {@link android.widget.AbsListView.MultiChoiceModeListener} using
+             {@link android.widget.AbsListView#setMultiChoiceModeListener} to control the
+             selection mode. This uses the {@link android.view.ActionMode} API. -->
+        <attr name="choiceMode">
+            <!-- Normal list that does not indicate choices. -->
+            <enum name="none" value="0" />
+            <!-- The list allows up to one choice. -->
+            <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>
     </declare-styleable>
     <declare-styleable name="AbsSpinner">
         <!-- Reference to an array resource that will populate the Spinner.  For static content,
@@ -1767,7 +1911,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,
@@ -1778,28 +1922,12 @@
         <!-- Height of the divider. Will use the intrinsic height of the divider if this
              is not specified. -->
         <attr name="dividerHeight" format="dimension" />
-        <!-- Defines the choice behavior for the ListView. By default, lists do not have
-             any choice behavior. By setting the choiceMode to singleChoice, the List
-             allows up to one item to be in a chosen state. By setting the choiceMode to
-             multipleChoice, the list allows any number of items to be chosen. -->
-        <attr name="choiceMode">
-            <!-- Normal list that does not indicate choices. -->
-            <enum name="none" value="0" />
-            <!-- The list allows up to one choice. -->
-            <enum name="singleChoice" value="1" />
-            <!-- The list allows multiple choices. -->
-            <enum name="multipleChoice" value="2" />
-        </attr>
         <!-- When set to false, the ListView will not draw the divider after each header view.
              The default value is true. -->
         <attr name="headerDividersEnabled" format="boolean" />
         <!-- When set to false, the ListView will not draw the divider before each footer view.
              The default value is true. -->
         <attr name="footerDividersEnabled" format="boolean" />
-        <!-- Drawable to draw above list content. -->
-        <attr name="overscrollHeader" format="reference|color" />
-        <!-- Drawable to draw below list content. -->
-        <attr name="overscrollFooter" format="reference|color" />
     </declare-styleable>
     <declare-styleable name="MenuView">
         <!-- Default appearance of menu item text. -->
@@ -2205,7 +2333,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">
@@ -2242,14 +2370,36 @@
         <attr name="popupAnimationStyle" format="reference" />
     </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 the animator loops to the first view once it
+        has reached the end of the list. -->
+        <attr name="loopViews" format="boolean" />
+        <!-- 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">
@@ -2263,6 +2413,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". -->
@@ -2573,6 +2747,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" />
@@ -2896,6 +3074,69 @@
     </declare-styleable>
 
     <!-- ========================== -->
+    <!-- ValueAnimator 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>
+
+    <!-- ========================== -->
+    <!-- ObjectAnimator class attributes -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <declare-styleable name="PropertyAnimator">
+        <!-- Name of the property being animated. -->
+        <attr name="propertyName" format="string"/>
+    </declare-styleable>
+
+
+    <!-- ========================== -->
+    <!-- AnimatorSet class attributes -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <declare-styleable name="AnimatorSet">
+        <!-- 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 />
@@ -2917,6 +3158,7 @@
          <li>"state_last"
          <li>"state_only"
          <li>"state_pressed"
+         <li>"state_activated"
          <li>"state_error"
          <li>"state_circle"
          <li>"state_rect"
@@ -2924,23 +3166,35 @@
          <li>"state_move"
          </ul>  -->
     <declare-styleable name="DrawableStates">
-        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},
+             set when a view has input focus. -->
         <attr name="state_focused" format="boolean" />
-        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},
+             set when a view's window has input focus. -->
         <attr name="state_window_focused" format="boolean" />
-        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},
+             set when a view is enabled. -->
         <attr name="state_enabled" format="boolean" />
-        <!--  State identifier indicating that the object <var>may</var> display a check mark.
-              See {@link R.attr#state_checked} for the identifier that indicates whether it is
-              actually checked. -->
+        <!-- State identifier indicating that the object <var>may</var> display a check mark.
+             See {@link R.attr#state_checked} for the identifier that indicates whether it is
+             actually checked. -->
         <attr name="state_checkable" format="boolean"/>
-        <!--  State identifier indicating that the object is currently checked.  See
-              {@link R.attr#state_checkable} for an additional identifier that can indicate if
-              any object may ever display a check, regardless of whether state_checked is
-              currently set. -->
+        <!-- State identifier indicating that the object is currently checked.  See
+             {@link R.attr#state_checkable} for an additional identifier that can indicate if
+             any object may ever display a check, regardless of whether state_checked is
+             currently set. -->
         <attr name="state_checked" format="boolean"/>
-        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
+        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},
+             set when a view (or one of its parents) is currently selected. -->
         <attr name="state_selected" format="boolean" />
+        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},
+             set when the user is pressing down in a view. -->
+        <attr name="state_pressed" format="boolean" />
+        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},
+             set when a view or its parent has been "activated" meaning the user has currently
+             marked it as being of interest.  This is an alternative representation of
+             state_checked for when the state should be propagated down the view hierarchy. -->
+        <attr name="state_activated" format="boolean" />
         <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
         <attr name="state_active" format="boolean" />
         <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
@@ -2951,8 +3205,6 @@
         <attr name="state_middle" format="boolean" />
         <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
         <attr name="state_last" format="boolean" />
-        <!--  State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}.-->
-        <attr name="state_pressed" format="boolean" />
     </declare-styleable>
     <declare-styleable name="ViewDrawableStates">
         <attr name="state_pressed" />
@@ -2960,6 +3212,7 @@
         <attr name="state_selected" />
         <attr name="state_window_focused" />
         <attr name="state_enabled" />
+        <attr name="state_activated" />
     </declare-styleable>
     <!-- State array representing a menu item that is currently checked. -->
     <declare-styleable name="MenuItemCheckedState">
@@ -3304,6 +3557,27 @@
         <!-- 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>
+
+        <!-- An optional layout to be used as an action view.
+             See {@link android.view.MenuItem#setActionView(android.view.View)}
+             for more info. -->
+        <attr name="actionLayout" format="reference" />
+
+        <!-- The name of an optional View class to instantiate and use as an
+             action view. See {@link android.view.MenuItem#setActionView(android.view.View)}
+             for more info. -->
+        <attr name="actionViewClass" format="string" />
+
     </declare-styleable>
 
     <!-- **************************************************************** -->
@@ -3319,6 +3593,25 @@
         <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">
+        <!-- Identifier value for the header. -->
+        <attr name="id" />
+        <!-- The title of the item that is shown to the user. -->
+        <attr name="title" />
+        <!-- The summary for the item. -->
+        <attr name="summary" format="string" />
+        <!-- The title for the bread crumb of this item. -->
+        <attr name="breadCrumbTitle" format="string" />
+        <!-- The short title for the bread crumb of this item. -->
+        <attr name="breadCrumbShortTitle" 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. -->
@@ -3331,10 +3624,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" />
@@ -3406,6 +3702,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. -->
@@ -3557,7 +3863,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.
@@ -3574,13 +3880,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
@@ -3620,7 +3962,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.
      -->
@@ -3641,7 +3983,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.
      -->
@@ -3657,7 +3999,7 @@
     <!-- Contacts meta-data attributes -->
     <!-- =============================== -->
     <eat-comment />
-    
+
     <!-- TODO: remove this deprecated styleable. -->
     <eat-comment />
     <declare-styleable name="Icon">
@@ -3683,6 +4025,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>
 
     <!-- =============================== -->
@@ -3714,4 +4059,125 @@
         <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" />
+        <!-- Specifies a fixed height. -->
+        <attr name="height" />
+    </declare-styleable>
+
+    <declare-styleable name="ActionMode">
+        <!-- Specifies a style to use for title text. -->
+        <attr name="titleTextStyle" />
+        <!-- Specifies a style to use for subtitle text. -->
+        <attr name="subtitleTextStyle" />
+        <!-- Specifies a background for the action mode bar. -->
+        <attr name="background" />
+        <!-- Specifies a fixed height for the action mode bar. -->
+        <attr name="height" />
+        <!-- Specifies a padding to use between elements on the bar. -->
+        <attr name="itemPadding" format="dimension" />
+    </declare-styleable>
+
+    <declare-styleable name="SearchView">
+        <!-- The default state of the SearchView. If true, it will be iconified when not in
+             use and expanded when clicked. -->
+        <attr name="iconifiedByDefault" format="boolean"/>
+    </declare-styleable>
 </resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 298463a..949d960 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -75,8 +75,7 @@
          header in the Action Bar. The primary differences between an icon
          and a logo are that logos are often wider and more detailed, and are
          used without an accompanying text caption. This must be a reference
-         to a Drawable resource containing the image definition.
-         @hide -->
+         to a Drawable resource containing the image definition. -->
     <attr name="logo" format="reference" />
 
     <!-- Name of the activity to be launched to manage application's space on
@@ -236,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
@@ -400,8 +403,7 @@
     <attr name="syncable" format="boolean" />
     
     <!-- Flag declaring this activity to be 'immersive'; immersive activities
-         should not be interrupted with other activities or notifications.
-         @hide -->
+         should not be interrupted with other activities or notifications. -->
     <attr name="immersive" format="boolean" />
 
     <!-- Specify the order in which content providers hosted by a process
@@ -735,6 +737,7 @@
         <attr name="theme" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="description" />
         <attr name="permission" />
         <attr name="process" />
@@ -753,6 +756,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" />
@@ -793,6 +797,7 @@
         <attr name="name" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="permissionGroup" />
         <attr name="description" />
         <attr name="protectionLevel" />
@@ -817,6 +822,7 @@
         <attr name="name" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="description" />
     </declare-styleable>
     
@@ -846,6 +852,7 @@
         <attr name="name" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="logo" />
     </declare-styleable>
     
     <!-- The <code>uses-permission</code> tag requests a
@@ -1050,6 +1057,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="process" />
         <attr name="authorities" />
         <attr name="syncable" />
@@ -1129,6 +1137,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="permission" />
         <attr name="process" />
         <!-- Specify whether the service is enabled or not (that is, can be instantiated by the system).
@@ -1161,6 +1170,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="permission" />
         <attr name="process" />
         <!-- Specify whether the receiver is enabled or not (that is, can be instantiated by the system).
@@ -1193,6 +1203,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="launchMode" />
         <attr name="screenOrientation" />
         <attr name="configChanges" />
@@ -1218,6 +1229,8 @@
              this activity.  A value besides "unspecified" here overrides
              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
@@ -1246,6 +1259,7 @@
         <attr name="label" />
         <attr name="description" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="permission" />
         <!-- Specify whether the activity-alias is enabled or not (that is, can be instantiated by the system).
              It can also be specified for an application as a whole, in which case a value of "false"
@@ -1315,6 +1329,7 @@
          parent="AndroidManifestActivity AndroidManifestReceiver AndroidManifestService">
         <attr name="label" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="priority" />
     </declare-styleable>
     
@@ -1421,6 +1436,7 @@
         <attr name="targetPackage" />
         <attr name="label" />
         <attr name="icon" />
+        <attr name="logo" />
         <attr name="handleProfiling" />
         <attr name="functionalTest" />
     </declare-styleable>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index b6af6b2..623368d 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -18,8 +18,8 @@
 */
 -->
 <resources>
-	<drawable name="screen_background_light">#ffffffff</drawable>
-	<drawable name="screen_background_dark">#ff000000</drawable>
+	  <drawable name="screen_background_light">#fff3f3f3</drawable>
+	  <drawable name="screen_background_dark">#ff000000</drawable>
     <drawable name="status_bar_closed_default_background">#ff000000</drawable>
     <drawable name="status_bar_opened_default_background">#ff000000</drawable>
     <drawable name="search_bar_default_color">#ff000000</drawable>
@@ -37,23 +37,29 @@
     <color name="black">#ff000000</color>
     <color name="transparent">#00000000</color>
     <color name="background_dark">#ff000000</color>
-    <color name="bright_foreground_dark">#ffffffff</color>
+    <color name="background_light">#fff3f3f3</color>
+    <color name="bright_foreground_dark">@android:color/background_light</color>
+    <color name="bright_foreground_light">@android:color/background_dark</color>
     <color name="bright_foreground_dark_disabled">#80ffffff</color>
-    <color name="bright_foreground_dark_inverse">#ff000000</color>
+    <color name="bright_foreground_light_disabled">#80000000</color>
+    <color name="bright_foreground_dark_inverse">@android:color/bright_foreground_light</color>
+    <color name="bright_foreground_light_inverse">@android:color/bright_foreground_dark</color>
     <color name="dim_foreground_dark">#bebebe</color>
     <color name="dim_foreground_dark_disabled">#80bebebe</color>
     <color name="dim_foreground_dark_inverse">#323232</color>
     <color name="dim_foreground_dark_inverse_disabled">#80323232</color>
     <color name="hint_foreground_dark">#808080</color>
-    <color name="background_light">#ffffffff</color>
-    <color name="bright_foreground_light">#ff000000</color>
-    <color name="bright_foreground_light_inverse">#ffffffff</color>
-    <color name="bright_foreground_light_disabled">#80000000</color>
     <color name="dim_foreground_light">#323232</color>
     <color name="dim_foreground_light_disabled">#80323232</color>
     <color name="dim_foreground_light_inverse">#bebebe</color>
     <color name="dim_foreground_light_inverse_disabled">#80bebebe</color>
     <color name="hint_foreground_light">#808080</color>
+    <color name="highlight_background">#cc475925</color>
+    <color name="highlight_background_inverse">#ccd2e461</color>
+    <color name="highlighted_text_dark">#cc475925</color>
+    <color name="highlighted_text_light">#ccd2e461</color>
+    <color name="link_text_dark">#5c5cff</color>
+    <color name="link_text_light">#0000ee</color>
 
     <drawable name="stat_notify_sync_noanim">@drawable/stat_notify_sync_anim0</drawable>
     <drawable name="stat_sys_download_done">@drawable/stat_sys_download_anim0</drawable>
@@ -97,5 +103,32 @@
     <color name="keyguard_text_color_soundon">#e69310</color>
     <color name="keyguard_text_color_decline">#fe0a5a</color>
 
+    <!-- For holo theme -->
+	  <drawable name="screen_background_holo_light">#fff3f3f3</drawable>
+	  <drawable name="screen_background_holo_dark">#ff000000</drawable>
+    <color name="background_holo_dark">#ff000000</color>
+    <color name="background_holo_light">#fff3f3f3</color>
+    <color name="bright_foreground_holo_dark">@android:color/background_holo_light</color>
+    <color name="bright_foreground_holo_light">@android:color/background_holo_dark</color>
+    <color name="bright_foreground_disabled_holo_dark">#80ffffff</color>
+    <color name="bright_foreground_disabled_holo_light">#80000000</color>
+    <color name="bright_foreground_inverse_holo_dark">@android:color/bright_foreground_holo_light</color>
+    <color name="bright_foreground_inverse_holo_light">@android:color/bright_foreground_holo_dark</color>
+    <color name="dim_foreground_holo_dark">#bebebe</color>
+    <color name="dim_foreground_disabled_holo_dark">#80bebebe</color>
+    <color name="dim_foreground_inverse_holo_dark">#323232</color>
+    <color name="dim_foreground_inverse_disabled_holo_dark">#80323232</color>
+    <color name="hint_foreground_holo_dark">#808080</color>
+    <color name="dim_foreground_holo_light">#323232</color>
+    <color name="dim_foreground_disabled_holo_light">#80323232</color>
+    <color name="dim_foreground_inverse_holo_light">#bebebe</color>
+    <color name="dim_foreground_inverse_disabled_holo_light">#80bebebe</color>
+    <color name="hint_foreground_holo_light">#808080</color>
+    <color name="highlight_background_holo">#cc475925</color>
+    <color name="highlight_background_inverse_holo">#ccd2e461</color>
+    <color name="highlighted_text_holo_dark">#cc475925</color>
+    <color name="highlighted_text_holo_light">#ccd2e461</color>
+    <color name="link_text_holo_dark">#5c5cff</color>
+    <color name="link_text_holo_light">#0000ee</color>
 </resources>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7023647..71967d4 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. -->
@@ -71,6 +72,17 @@
          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>
+
+    <!-- Set to true if external storage is case sensitive.
+         Typically external storage is FAT, which is case insensitive. -->
+    <bool name="config_caseSensitiveExternalStorage">false</bool>
+    
     <!-- A product with no SD card == not removable. -->
     <bool name="config_externalStorageRemovable" product="nosdcard">false</bool>
     <!-- Configures whether the primary external storage device is
@@ -106,6 +118,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.*" -->
@@ -118,6 +133,16 @@
     <string-array translatable="false" name="config_tether_wifi_regexs">
     </string-array>
 
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         bluetooth interfaces.  If the device doesn't want to support tethering over bluetooth this
+         should be empty. -->
+    <string-array translatable="false" name="config_tether_bluetooth_regexs">
+    </string-array>
+
+    <!-- Max number of Bluetooth tethering connections allowed. If this is
+         updated config_tether_dhcp_range has to be updated appropriately. -->
+    <integer translateable="false" name="config_max_pan_devices">5</integer>
+
     <!-- Dhcp range (min, max) to use for tethering purposes -->
     <string-array translatable="false" name="config_tether_dhcp_range">
     </string-array>
@@ -199,6 +224,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>
@@ -281,6 +309,15 @@
     <!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
     <bool name="config_disableMenuKeyInLockScreen">false</bool>
 
+    <!-- Show sliding tab before lockscreen -->
+    <bool name="config_enableSlidingTabFirst">true</bool>
+
+    <!-- Diable lockscreen rotation by default -->
+    <bool name="config_enableLockScreenRotation">false</bool>
+
+    <!-- Enable 3D RecentApplications view -->
+    <bool name="config_enableRecentApps3D">false</bool>
+
     <!-- Array of light sensor LUX values to define our levels for auto backlight brightness support.
          The N entries of this array define N + 1 zones as follows:
 
@@ -365,6 +402,30 @@
     <!-- 2 means give warning -->
     <integer name="config_datause_notification_type">2</integer>
 
+    <!-- Flag indicating whether the current device is "voice capable".
+         If true, this means that the device supports circuit-switched
+         (i.e. voice) phone calls over the telephony network, and is
+         allowed to display the in-call UI while a cellular voice call is
+         active.  This can be overridden to false for "data only" devices
+         which can't make voice calls and don't support any in-call UI.
+
+         Note: this flag is subtly different from the
+         PackageManager.FEATURE_TELEPHONY system feature, which is
+         available on *any* device with a telephony radio, even if the
+         device is data-only. -->
+    <bool name="config_voice_capable">true</bool>
+
+    <!-- IP address of the dns server to use if nobody else suggests one -->
+    <string name="config_default_dns_server">8.8.8.8</string>
+
+    <!-- The default character set for GsmAlphabet -->
+    <!-- Empty string means MBCS is not considered -->
+    <string name="gsm_alphabet_default_charset"></string>
+
     <!-- Enables SIP on WIFI only -->
     <bool name="config_sip_wifi_only">false</bool>
+
+    <!-- Number of database connections opened and managed by framework layer
+         to handle queries on each database. -->
+    <integer name="db_connection_pool_size">1</integer>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 11f3e50..171bb45 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>
@@ -32,14 +35,22 @@
     <dimen name="status_bar_icon_size">25dip</dimen>
     <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
     <dimen name="status_bar_edge_ignore">5dp</dimen>
+    <!-- Margin for permanent screen decorations at the bottom. -->
+    <dimen name="screen_margin_bottom">0dip</dimen>
     <!-- Size of the fastscroll hint letter -->
     <dimen name="fastscroll_overlay_size">104dp</dimen>
     <!-- Width of the fastscroll thumb -->
     <dimen name="fastscroll_thumb_width">64dp</dimen>
     <!-- Height of the fastscroll thumb -->
     <dimen name="fastscroll_thumb_height">52dp</dimen>
-    <!-- Default height of a key in the password keyboard -->
-    <dimen name="password_keyboard_key_height">56dip</dimen>
+    <!-- Min width for a tablet device -->
+    <dimen name="min_xlarge_screen_width">800dp</dimen>
+    <!-- Fixed viewport margin for website content width change -->
+    <dimen name="fixed_viewport_margin">7dp</dimen>
+    <!-- Default height of a key in the password keyboard for alpha -->
+    <dimen name="password_keyboard_key_height_alpha">56dip</dimen>
+    <!-- Default height of a key in the password keyboard for numeric -->
+    <dimen name="password_keyboard_key_height_numeric">56dip</dimen>
     <!-- Default correction for the space key in the password keyboard -->
     <dimen name="password_keyboard_spacebar_vertical_correction">4dip</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 10cb446..2c3c4fc 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,16 @@
   <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 Gingerbread.
      =============================================================== -->
-     
+  <eat-comment />
   <public type="attr" name="logo" id="0x010102be" />
   <public type="attr" name="xlargeScreens" id="0x010102bf" />
   <public type="attr" name="immersive" id="0x010102c0" />
-  <public type="attr" name="overscrollMode" id="0x010102c1" />
-  <public type="attr" name="overscrollHeader" id="0x010102c2" />
-  <public type="attr" name="overscrollFooter" id="0x010102c3" />
   <public type="attr" name="filterTouchesWhenObscured" id="0x010102c4" />
   <public type="attr" name="textSelectHandleLeft" id="0x010102c5" />
   <public type="attr" name="textSelectHandleRight" id="0x010102c6" />
@@ -1262,7 +1259,7 @@
   <public type="attr" name="popupAnimationStyle" id="0x010102c9" />
 
   <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" />
 
@@ -1273,9 +1270,8 @@
   <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-padding type="drawable" name="kraken_resource_pad" end="0x01080100" />
-  
+
   <public type="style" name="TextAppearance.StatusBar.Title" id="0x01030065" />
   <public type="style" name="TextAppearance.StatusBar.Icon" id="0x01030066" />
   <public type="style" name="TextAppearance.StatusBar.EventContent" id="0x01030067" />
@@ -1289,4 +1285,137 @@
   <public-padding type="dimen" name="kraken_resource_pad" end="0x01050010" />
   <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="actionBarStyle" />
+  <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="attr" name="windowActionBarOverlay" />
+  <public type="attr" name="fragmentOpenEnterAnimation" />
+  <public type="attr" name="fragmentOpenExitAnimation" />
+  <public type="attr" name="fragmentCloseEnterAnimation" />
+  <public type="attr" name="fragmentCloseExitAnimation" />
+  <public type="attr" name="fragmentNextEnterAnimation" />
+  <public type="attr" name="fragmentNextExitAnimation" />
+  <public type="attr" name="fragmentPrevEnterAnimation" />
+  <public type="attr" name="fragmentPrevExitAnimation" />
+  <public type="attr" name="actionBarSize" />
+  <public type="attr" name="imeSubtypeLocale" />
+  <public type="attr" name="imeSubtypeMode" />
+  <public type="attr" name="imeSubtypeExtraValue" />
+  <public type="attr" name="splitMotionEvents" />
+  <public type="attr" name="listChoiceBackgroundIndicator" />
+  <public type="attr" name="spinnerMode" />
+  <public type="attr" name="animateLayoutChanges" />
+  <public type="attr" name="actionBarTabStyle" />
+  <public type="attr" name="actionBarTabBarStyle" />
+  <public type="attr" name="actionBarTabTextStyle" />
+  <public type="attr" name="actionOverflowButtonStyle" />
+  <public type="attr" name="itemPadding" />
+  <public type="attr" name="actionModeCloseButtonStyle" />
+  <public type="attr" name="titleTextStyle" />
+  <public type="attr" name="subtitleTextStyle" />
+  <public type="attr" name="iconifiedByDefault" />
+  <public type="attr" name="actionLayout" />
+  <public type="attr" name="actionViewClass" />
+  <public type="attr" name="activatedBackgroundIndicator" />
+  <public type="attr" name="state_activated" />
+  <public type="attr" name="listPopupWindowStyle" />
+  <public type="attr" name="popupMenuStyle" />
+  <public type="attr" name="textAppearanceLargePopupMenu" />
+  <public type="attr" name="textAppearanceSmallPopupMenu" />
+  <public type="attr" name="breadCrumbTitle" />
+  <public type="attr" name="breadCrumbShortTitle" />
+  <public type="attr" name="listDividerAlertDialog" />
+  <public type="attr" name="textColorAlertDialogListItem" />
+  <public type="attr" name="loopViews" />
+
+  <public type="anim" name="animator_fade_in" />
+  <public type="anim" name="animator_fade_out" />
+
+  <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" />
+
+  <!-- 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" />
+
+  <!-- A simple ListView item layout which can contain text and support (single or multiple) item selection. -->
+  <public type="layout" name="simple_selectable_list_item" />
+
+  <!-- A version of {@link #simple_list_item_1} that is able to change its
+       background state to indicate when it is activated (that is checked by
+       a ListView). -->
+  <public type="layout" name="simple_list_item_activated_1" />
+
+  <!-- A version of {@link #simple_list_item_2} that is able to change its
+       background state to indicate when it is activated (that is checked by
+       a ListView). -->
+  <public type="layout" name="simple_list_item_activated_2" />
+
+  <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" />
+
+  <public type="style" name="Theme.Holo" />
+  <public type="style" name="Theme.Light.Holo" />
+  <public type="style" name="Theme.Holo.NoActionBar" />
+  <public type="style" name="Theme.Holo.NoActionBar.Fullscreen" />
+  <public type="style" name="Theme.Light.Holo.NoActionBar" />
+  <public type="style" name="Theme.Light.Holo.NoActionBar.Fullscreen" />
+
+  <public type="style" name="Widget.ListPopupWindow" />
+  <public type="style" name="Widget.PopupMenu" />
+  <public type="style" name="Widget.ActionButton.Overflow" />
+  <public type="style" name="Widget.ActionButton.CloseMode" />
+  <public type="style" name="TextAppearance.Widget.PopupMenu.Large" />
+  <public type="style" name="TextAppearance.Widget.PopupMenu.Small" />
+  <public type="style" name="Widget.FragmentBreadCrumbs" />
+
+  <public type="string" name="selectTextMode" />
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d9177e7..8b4f91f 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -283,9 +283,15 @@
     <!-- Shutdown Confirmation Dialog.  When the user chooses to power off the phone, there will be a confirmation dialog.  This is the message. -->
     <string name="shutdown_confirm">Your phone will shut down.</string>
 
-    <!-- Recent Tasks dialog: title -->
+    <!-- Recent Tasks dialog: title
+     TODO: this should move to SystemUI.apk, but the code for the old 
+            recent dialog is still in the framework
+     -->
     <string name="recent_tasks_title">Recent</string>
-    <!-- Recent Tasks dialog: message when there are no recent applications -->
+    <!-- Recent Tasks dialog: message when there are no recent applications
+     TODO: this should move to SystemUI.apk, but the code for the old 
+            recent dialog is still in the framework
+     -->
     <string name="no_recent_tasks">No recent applications.</string>
 
     <!-- Title of the Global Actions Dialog -->
@@ -795,8 +801,8 @@
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_writeCalendar">add or modify calendar events and send email to guests</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_writeCalendar">Allows an application to add or change the 
-        events on your calendar, which may send email to guests. Malicious applications can use this 
+    <string name="permdesc_writeCalendar">Allows an application to add or change the
+        events on your calendar, which may send email to guests. Malicious applications can use this
         to erase or modify your calendar events or to send email to guests.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1245,14 +1251,14 @@
     <!-- Title of policy access to limiting the user's password choices -->
     <string name="policylab_limitPassword">Set password rules</string>
     <!-- Description of policy access to limiting the user's password choices -->
-    <string name="policydesc_limitPassword">Control the length and the characters allowed in
-    screen-unlock passwords</string>
+    <string name="policydesc_limitPassword">Control the length and the characters 
+    allowed in screen-unlock passwords</string>
     <!-- Title of policy access to watch user login attempts -->
     <string name="policylab_watchLogin">Monitor screen-unlock attempts</string>
     <!-- Description of policy access to watch user login attempts -->
-    <string name="policydesc_watchLogin">Monitor the number of incorrect passwords entered when unlocking 
-    the screen, and lock the phone or erase all the phone\'s data if too many incorrect passwords are entered
-    </string>
+    <string name="policydesc_watchLogin">Monitor the number of incorrect passwords 
+    entered when unlocking the screen, and lock the phone or erase all the phone\'s 
+    data if too many incorrect passwords are entered</string>
     <!-- Title of policy access to reset user's password -->
     <string name="policylab_resetPassword">Change the screen-unlock password</string>
     <!-- Description of policy access to reset user's password -->
@@ -1264,7 +1270,13 @@
     <!-- Title of policy access to wipe the user's data -->
     <string name="policylab_wipeData">Erase all data</string>
     <!-- Description of policy access to wipe the user's data -->
-    <string name="policydesc_wipeData">Erase the phone\'s data without warning, by performing a factory data reset</string>
+    <string name="policydesc_wipeData">Erase the phone\'s data without warning, 
+    by performing a factory data reset</string>
+    <string name="policylab_setGlobalProxy">Set the device global proxy</string>
+    <!-- Description of policy access to wipe the user's data -->
+    <string name="policydesc_setGlobalProxy">Set the device global proxy
+        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. -->
@@ -1443,20 +1455,18 @@
     <!-- Other SIP address type -->
     <string name="sipAddressTypeOther">Other</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>
@@ -1493,6 +1503,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 %.  -->
@@ -1536,12 +1548,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">
@@ -1611,8 +1638,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.9 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.9</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>
@@ -1628,6 +1657,9 @@
     <!-- Toast for double-tap -->
     <string name="double_tap_toast">Tip: double-tap to zoom in and out.</string>
 
+    <!-- Text to show in the auto complete drop down list on a text view when the browser can auto fill the entire form -->
+    <string name="autofill_this_form">AutoFill this form</string>
+
     <!-- Title of an application permission, listed so the user can choose whether
         they want to allow the application to do this. -->
     <string name="permlab_readHistoryBookmarks">read Browser\'s history and bookmarks</string>
@@ -1884,9 +1916,6 @@
     <!-- 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. [CHAR LIMIT=20] -->
-    <string name="selectText">Select word</string>
-
     <!-- Item on EditText context menu.  This action is used to cut selected the text into the clipboard.  -->
     <string name="cut">Cut</string>
 
@@ -1899,15 +1928,17 @@
     <!-- 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. [CHAR LIMIT=20] -->
+    <string name="selectTextMode">Select text...</string>
+
+    <!-- Text selection contextual mode title, displayed in the CAB. [CHAR LIMIT=20] -->
+    <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>
+    <!-- Title for EditText context menu [CHAR LIMIT=20] -->
+    <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>
@@ -1926,6 +1957,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. -->
@@ -1982,23 +2016,23 @@
 
     <!-- Notification text to tell the user that a heavy-weight application is running. -->
     <string name="heavy_weight_notification"><xliff:g id="app">%1$s</xliff:g> running</string>
-    
+
     <!-- Notification details to tell the user that a heavy-weight application is running. -->
     <string name="heavy_weight_notification_detail">Select to switch to application</string>
-    
+
     <!-- Title of dialog prompting whether user wants to switch between heavy-weight apps. -->
     <string name="heavy_weight_switcher_title">Switch applications?</string>
-    
+
     <!-- Descriptive text for switching to a new heavy-weight application. -->
     <string name="heavy_weight_switcher_text">Another application is already running
     that must be stopped before you can start a new one.</string>
-    
+
     <string name="old_app_action">Return to <xliff:g id="old_app">%1$s</xliff:g></string>
     <string name="old_app_description">Don\'t start the new application.</string>
-    
+
     <string name="new_app_action">Start <xliff:g id="old_app">%1$s</xliff:g></string>
     <string name="new_app_description">Stop the old application without saving.</string>
-    
+
     <!-- Displayed in the title of the chooser for things to do with text that
          is to be sent to another application. For example, I can send
          text through SMS or IM.  A dialog with those choices would be shown,
@@ -2302,17 +2336,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>
@@ -2323,6 +2353,13 @@
     <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>
+
+    <!-- Optional button to Skip a PreferenceActivity [CHAR LIMIT=20] -->
+    <string name="skip_button_label">Skip</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>
@@ -2332,4 +2369,21 @@
     <!-- Shown when the users bandwidth is reduced because of excessive data use -->
     <string name="throttled_notification_title">Mobile data limit exceeded</string>
     <string name="throttled_notification_message">Touch to learn more about mobile data use</string>
+
+    <!-- Displayed on the Find dialog when there are no matches [CHAR LIMIT=NONE]-->
+    <string name="no_matches">No matches</string>
+
+    <!-- Find dialog hint text.  Also used in the menu item to open find on page [CHAR LIMIT=NONE] -->
+    <string name="find_on_page">Find on page</string>
+
+    <!-- Displayed on the Find dialog to display the index of the highlighted
+         match and total number of matches found in the current page. [CHAR LIMIT=NONE] -->
+    <plurals name="matches_found">
+        <!-- Case of one match -->
+        <item quantity="one">1 match</item>
+        <!-- Case of multiple total matches -->
+        <item quantity="other"><xliff:g id="index" example="2">%d</xliff:g> of <xliff:g id="total" example="137">%d</xliff:g></item>
+    </plurals>
+
+
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index a8cd7ce..4b5047e 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -74,6 +74,14 @@
         <item name="wallpaperIntraOpenExitAnimation">@anim/wallpaper_intra_open_exit</item>
         <item name="wallpaperIntraCloseEnterAnimation">@anim/wallpaper_intra_close_enter</item>
         <item name="wallpaperIntraCloseExitAnimation">@anim/wallpaper_intra_close_exit</item>
+        <item name="fragmentOpenEnterAnimation">@anim/fragment_open_enter</item>
+        <item name="fragmentOpenExitAnimation">@anim/fragment_open_exit</item>
+        <item name="fragmentCloseEnterAnimation">@anim/fragment_close_enter</item>
+        <item name="fragmentCloseExitAnimation">@anim/fragment_close_exit</item>
+        <item name="fragmentNextEnterAnimation">@anim/fragment_next_enter</item>
+        <item name="fragmentNextExitAnimation">@anim/fragment_next_exit</item>
+        <item name="fragmentPrevEnterAnimation">@anim/fragment_prev_enter</item>
+        <item name="fragmentPrevExitAnimation">@anim/fragment_prev_exit</item>
     </style>
 
     <!-- Standard animations for a non-full-screen window or activity. -->
@@ -88,6 +96,13 @@
         <item name="windowExitAnimation">@anim/status_bar_exit</item>
     </style>
 
+    <!-- {@hide} -->
+    <style name="Animation.StatusBar.IntruderAlert"
+        parent="@android:style/Animation.StatusBar">
+        <item name="android:windowEnterAnimation">@anim/priority_alert_enter</item>
+        <item name="android:windowExitAnimation">@anim/priority_alert_exit</item>
+    </style>
+
     <!-- Standard animations for a translucent window or activity.  This
          style is <em>not<em> used by default for the translucent theme
          (since translucent activities are a special case that have no
@@ -191,8 +206,7 @@
     <!-- Status Bar Styles -->
 
     <style name="TextAppearance.StatusBar">
-        <item name="android:textSize">14sp</item>
-        <item name="android:textStyle">normal</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
     <style name="TextAppearance.StatusBar.Ticker">
@@ -205,12 +219,11 @@
         <item name="android:textStyle">bold</item>
     </style>
     <style name="TextAppearance.StatusBar.EventContent">
-        <item name="android:textColor">#ff6b6b6b</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
     </style>
     <style name="TextAppearance.StatusBar.EventContent.Title">
-        <item name="android:textSize">16sp</item>
+        <item name="android:textSize">18sp</item>
         <item name="android:textStyle">bold</item>
-        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
     </style>
 
     <!-- Widget Styles -->
@@ -221,7 +234,7 @@
 
     <style name="Widget.AbsListView">
         <item name="android:scrollbars">vertical</item>
-        <item name="android:fadingEdge">vertical</item>
+        <item name="android:fadingEdge">none</item>
     </style>
 
     <style name="Widget.GestureOverlayView">
@@ -247,7 +260,6 @@
         <item name="android:clickable">true</item>
         <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>
         <item name="android:textColor">@android:color/primary_text_light</item>
-        <item name="android:textStyle">bold</item>
         <item name="android:gravity">center_vertical|center_horizontal</item>
     </style>
 
@@ -275,12 +287,12 @@
 
     <style name="Widget.CompoundButton.CheckBox">
         <item name="android:background">@android:drawable/btn_check_label_background</item>
-        <item name="android:button">@android:drawable/btn_check</item>
+        <item name="android:button">?android:attr/listChoiceIndicatorMultiple</item>
     </style>
 
     <style name="Widget.CompoundButton.RadioButton">
         <item name="android:background">@android:drawable/btn_radio_label_background</item>
-        <item name="android:button">@android:drawable/btn_radio</item>
+        <item name="android:button">?android:attr/listChoiceIndicatorSingle</item>
     </style>
 
     <style name="Widget.CompoundButton.Star">
@@ -413,9 +425,9 @@
         <item name="android:focusable">true</item>
         <item name="android:focusableInTouchMode">true</item>
         <item name="android:clickable">true</item>
-        <item name="android:background">@android:drawable/edit_text</item>
+        <item name="android:background">?android:attr/editTextBackground</item>
         <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
-        <item name="android:textColor">@android:color/primary_text_light</item>
+        <item name="android:textColor">?android:attr/editTextColor</item>
         <item name="android:gravity">center_vertical</item>
     </style>
     
@@ -441,14 +453,8 @@
         <item name="android:background">@android:drawable/btn_default</item>
     </style>
 
-    <style name="Widget.AutoCompleteTextView">
-        <item name="android:focusable">true</item>
-        <item name="android:focusableInTouchMode">true</item>
-        <item name="android:clickable">true</item>
-        <item name="android:background">@android:drawable/edit_text</item>
+    <style name="Widget.AutoCompleteTextView" parent="Widget.EditText">
         <item name="android:completionHintView">@android:layout/simple_dropdown_hint</item>
-        <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
-        <item name="android:gravity">center_vertical</item>
         <item name="android:completionThreshold">2</item>
         <item name="android:dropDownSelector">@android:drawable/list_selector_background</item>
         <item name="android:popupBackground">@android:drawable/spinner_dropdown_background</item>
@@ -460,6 +466,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">
@@ -479,17 +497,17 @@
     </style>
     
     <style name="Widget.DropDownItem.Spinner">
-        <item name="android:checkMark">@android:drawable/btn_radio</item>
+        <item name="android:checkMark">?android:attr/listChoiceIndicatorSingle</item>
     </style>
 
     <style name="Widget.ScrollView">
         <item name="android:scrollbars">vertical</item>
-        <item name="android:fadingEdge">vertical</item>
+        <item name="android:fadingEdge">none</item>
     </style>
 
     <style name="Widget.HorizontalScrollView">
         <item name="android:scrollbars">horizontal</item>
-        <item name="android:fadingEdge">horizontal</item>
+        <item name="android:fadingEdge">none</item>
     </style>
 
     <style name="Widget.ListView" parent="Widget.AbsListView">
@@ -499,7 +517,7 @@
     </style>
     
     <style name="Widget.ListView.White" parent="Widget.AbsListView">
-        <item name="android:listSelector">@android:drawable/list_selector_background</item>
+        <item name="android:listSelector">@android:drawable/list_selector_background_light</item>
         <item name="android:cacheColorHint">?android:attr/colorBackgroundCacheHint</item>
         <item name="android:divider">@android:drawable/divider_horizontal_bright_opaque</item>
     </style>    
@@ -512,7 +530,7 @@
     <style name="Widget.ListView.Menu">
 		<item name="android:cacheColorHint">@null</item>
         <item name="android:scrollbars">vertical</item>
-        <item name="android:fadingEdge">vertical</item>
+        <item name="android:fadingEdge">none</item>
         <item name="listSelector">@android:drawable/menu_selector</item>
         <!-- Light background for the list in menus, so the divider for bright themes -->
         <item name="android:divider">@android:drawable/divider_horizontal_dark</item>
@@ -545,7 +563,7 @@
     </style>
 
     <style name="Widget.Gallery">
-        <item name="android:fadingEdge">horizontal</item>
+        <item name="android:fadingEdge">none</item>
         <item name="android:gravity">center_vertical</item>
         <item name="android:spacing">-20dip</item>
         <item name="android:unselectedAlpha">0.85</item>
@@ -556,6 +574,11 @@
         <item name="android:popupAnimationStyle">@android:style/Animation.PopupWindow</item>
     </style>
 
+    <!-- Default style for {@link android.app.FragmentBreadCrumbs} view. -->
+    <style name="Widget.FragmentBreadCrumbs">
+        <item name="android:padding">4dp</item>
+    </style>
+
     <style name="Widget.KeyboardView" parent="android:Widget">
         <item name="android:background">@android:drawable/keyboard_background</item>
         <item name="android:keyBackground">@android:drawable/btn_keyboard_key</item>
@@ -577,6 +600,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">
@@ -616,9 +640,9 @@
 
     <style name="TextAppearance">
         <item name="android:textColor">?textColorPrimary</item>
-        <item name="android:textColorHighlight">#FFFF9200</item>
+        <item name="android:textColorHighlight">?textColorHighlight</item>
         <item name="android:textColorHint">?textColorHint</item>
-        <item name="android:textColorLink">#5C5CFF</item>
+        <item name="android:textColorLink">?textColorLink</item>
         <item name="android:textSize">16sp</item>
         <item name="android:textStyle">normal</item>
     </style>
@@ -626,7 +650,8 @@
     <style name="TextAppearance.Inverse">
         <item name="textColor">?textColorPrimaryInverse</item>
         <item name="android:textColorHint">?textColorHintInverse</item>
-        <item name="android:textColorLink">#0000EE</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
     </style>
 
     <style name="TextAppearance.Theme">
@@ -634,41 +659,40 @@
 
     <style name="TextAppearance.DialogWindowTitle">
         <item name="android:textSize">18sp</item>
-        <item name="android:textStyle">normal</item>
-        <item name="android:textColor">?textColorPrimary</item>
     </style>
 
     <style name="TextAppearance.Large">
         <item name="android:textSize">22sp</item>
-        <item name="android:textStyle">normal</item>
-        <item name="android:textColor">?textColorPrimary</item>
     </style>
 
     <style name="TextAppearance.Large.Inverse">
         <item name="android:textColor">?textColorPrimaryInverse</item>
         <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
     </style>
 
     <style name="TextAppearance.Medium">
         <item name="android:textSize">18sp</item>
-        <item name="android:textStyle">normal</item>
-        <item name="android:textColor">?textColorPrimary</item>
     </style>
 
     <style name="TextAppearance.Medium.Inverse">
         <item name="android:textColor">?textColorPrimaryInverse</item>
         <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
     </style>
 
     <style name="TextAppearance.Small">
         <item name="android:textSize">14sp</item>
-        <item name="android:textStyle">normal</item>
         <item name="android:textColor">?textColorSecondary</item>
     </style>
 
     <style name="TextAppearance.Small.Inverse">
         <item name="android:textColor">?textColorSecondaryInverse</item>
         <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
     </style>
 
     <style name="TextAppearance.Theme.Dialog" parent="TextAppearance.Theme">
@@ -875,4 +899,799 @@
         <item name="windowEnterAnimation">@android:anim/fade_in</item>
         <item name="windowExitAnimation">@android:anim/fade_out</item>
     </style>
+
+    <style name="Widget.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>
+        <item name="android:height">?android:attr/actionBarSize</item>
+        <item name="android:paddingLeft">3dip</item>
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingRight">3dip</item>
+        <item name="android:paddingBottom">0dip</item>
+        <item name="android:titleTextStyle">@android:style/TextAppearance.Widget.ActionBar.Title</item>
+        <item name="android:subtitleTextStyle">@android:style/TextAppearance.Widget.ActionBar.Subtitle</item>
+    </style>
+
+    <style name="Widget.ActionMode">
+        <item name="android:background">?android:attr/actionModeBackground</item>
+        <item name="android:height">?android:attr/actionBarSize</item>
+        <item name="android:itemPadding">?android:attr/actionButtonPadding</item>
+        <item name="android:titleTextStyle">@android:style/TextAppearance.Widget.ActionMode.Title</item>
+        <item name="android:subtitleTextStyle">@android:style/TextAppearance.Widget.ActionMode.Subtitle</item>
+    </style>
+
+    <style name="TextAppearance.Widget.ActionBar.Title"
+           parent="@android:style/TextAppearance.Medium.Inverse">
+    </style>
+
+    <style name="TextAppearance.Widget.ActionBar.Subtitle"
+           parent="@android:style/TextAppearance.Small.Inverse">
+    </style>
+
+    <style name="TextAppearance.Widget.ActionMode.Title"
+           parent="@android:style/TextAppearance.Medium.Inverse">
+    </style>
+
+    <style name="TextAppearance.Widget.ActionMode.Subtitle"
+           parent="@android:style/TextAppearance.Small.Inverse">
+    </style>
+
+    <style name="Widget.ActionButton">
+        <item name="android:background">@null</item>
+    </style>
+
+    <style name="Widget.ActionButton.Overflow">
+        <item name="android:src">@drawable/ic_menu_more</item>
+        <item name="android:contentDescription">@string/more_item_label</item>
+    </style>
+
+    <style name="Widget.ActionButton.CloseMode">
+        <item name="android:src">?android:attr/actionModeCloseDrawable</item>
+    </style>
+
+    <style name="Widget.ActionBarView_TabView">
+        <item name="android:background">@drawable/minitab_lt</item>
+        <item name="android:paddingLeft">4dip</item>
+        <item name="android:paddingRight">4dip</item>
+    </style>
+
+    <style name="Widget.ActionBarView_TabBar">
+    </style>
+
+    <style name="Widget.ActionBarView_TabText">
+        <item name="android:textAppearance">@style/TextAppearance.Widget.TextView.PopupMenu</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <style name="Widget.ListPopupWindow">
+        <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>        
+    </style>
+
+    <style name="Widget.PopupMenu" parent="Widget.ListPopupWindow">
+    </style>
+
+    <style name="TextAppearance.Widget.PopupMenu">
+        <item name="android:textColor">@android:color/primary_text_light</item>
+        <item name="android:textColorHint">@android:color/hint_foreground_light</item>
+        <item name="android:textColorHighlight">@android:color/highlighted_text_light</item>
+        <item name="android:textColorLink">@android:color/link_text_light</item>
+    </style>
+
+    <style name="TextAppearance.Widget.PopupMenu.Large">
+        <item name="android:textSize">22sp</item>
+    </style>
+
+    <style name="TextAppearance.Widget.PopupMenu.Small">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@android:color/secondary_text_light</item>
+    </style>
+
+    <!-- Begin Holo theme styles -->
+
+    <!-- Text Styles -->
+    <style name="TextAppearance.Holo" parent="TextAppearance">
+    </style>
+
+    <style name="TextAppearance.Holo.Inverse" parent="TextAppearance.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Large" parent="TextAppearance.Large">
+    </style>
+
+    <style name="TextAppearance.Holo.Medium" parent="TextAppearance.Medium">
+    </style>
+
+    <style name="TextAppearance.Holo.Small" parent="TextAppearance.Small">
+    </style>
+
+    <style name="TextAppearance.Holo.Large.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Medium.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Small.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
+    </style>
+
+    <style name="TextAppearance.Holo.SearchResult">
+    </style>
+
+    <style name="TextAppearance.Holo.SearchResult.Title">
+    </style>
+
+    <style name="TextAppearance.Holo.SearchResult.Subtitle">
+    </style>
+
+    <style name="TextAppearance.Holo.Widget" parent="TextAppearance.Widget">
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.Button" parent="TextAppearance.Holo.Small.Inverse">
+        <item name="android:textColor">@android:color/primary_text_light_nodisable</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.IconMenu.Item" parent="TextAppearance.Holo.Small">
+        <item name="android:textColor">?textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.TabWidget">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">@android:color/tab_indicator_text</item>
+    </style>
+    
+    <style name="TextAppearance.Holo.Widget.TextView">
+        <item name="android:textColor">?textColorPrimaryDisableOnly</item>
+        <item name="android:textColorHint">?textColorHint</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.TextView.PopupMenu">
+        <item name="android:textSize">18sp</item>
+        <item name="android:textColor">?textColorPrimaryDisableOnly</item>
+        <item name="android:textColorHint">?textColorHint</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.DropDownHint">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textSize">14sp</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.DropDownItem">
+        <item name="android:textColor">@android:color/primary_text_light_disable_only</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.TextView.SpinnerItem">
+        <item name="android:textColor">@android:color/primary_text_light_disable_only</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.EditText">
+        <item name="android:textColor">@color/widget_edittext_holo_dark</item>
+        <item name="android:textColorHint">@android:color/hint_foreground_holo_light</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.PopupMenu" parent="TextAppearance.Widget.PopupMenu">
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.PopupMenu.Large" parent="TextAppearance.Widget.PopupMenu.Large">
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.PopupMenu.Small" parent="TextAppearance.Widget.PopupMenu.Small">
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.ActionBar.Title"
+           parent="TextAppearance.Holo.Medium">
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.ActionBar.Subtitle"
+           parent="TextAppearance.Holo.Small">
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.ActionMode">
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.ActionMode.Title" parent="TextAppearance.Widget.ActionMode.Title">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Widget.ActionMode.Subtitle" parent="TextAppearance.Widget.ActionMode.Subtitle">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+    </style>
+
+    <style name="TextAppearance.Holo.WindowTitle">
+        <item name="android:textColor">#fff</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="TextAppearance.Holo.DialogWindowTitle">
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <!-- Light text styles -->
+    <style name="TextAppearance.Holo.Light" parent="TextAppearance.Holo">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Large">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Medium">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Small">
+    </style>
+ 
+   <style name="TextAppearance.Holo.Light.Large.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Medium.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Small.Inverse">
+        <item name="android:textColor">?textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?textColorHintInverse</item>
+        <item name="android:textColorHighlight">?textColorHighlightInverse</item>
+        <item name="android:textColorLink">?textColorLinkInverse</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.SearchResult" parent="TextAppearance.Holo.SearchResult">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.SearchResult.Title">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.SearchResult.Subtitle">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Widget" parent="TextAppearance.Widget">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Widget.Button">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Widget.EditText">
+        <item name="android:textColor">@color/widget_edittext_holo_light</item>
+        <item name="android:textColorHint">@android:color/hint_foreground_holo_dark</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Widget.PopupMenu" parent="TextAppearance.Holo.Widget.PopupMenu">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Widget.PopupMenu.Large" parent="TextAppearance.Holo.Widget.PopupMenu.Large">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Widget.PopupMenu.Small" parent="TextAppearance.Holo.Widget.PopupMenu.Small">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Widget.DropDownHint" parent="TextAppearance.Holo.Widget.DropDownHint">
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Widget.ActionMode.Title" parent="TextAppearance.Widget.ActionMode.Title">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.Widget.ActionMode.Subtitle" parent="TextAppearance.Widget.ActionMode.Subtitle">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.WindowTitle">
+        <item name="android:textColor">#fff</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.DialogWindowTitle">
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <!-- Widget Styles -->
+
+    <style name="Widget.Holo" parent="Widget">
+    </style>
+
+    <style name="Widget.Holo.Button" parent="Widget.Button">
+        <item name="android:background">@android:drawable/btn_default_holo_dark</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
+        <item name="android:textColor">@android:color/primary_text_holo_dark</item>
+        <item name="android:minHeight">48dip</item>
+        <item name="android:paddingLeft">32dip</item>
+        <item name="android:paddingRight">32dip</item>
+    </style>
+
+    <style name="Widget.Holo.Button.Small">
+        <item name="android:background">@android:drawable/btn_default_holo_dark</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+        <item name="android:textColor">@android:color/primary_text_holo_dark</item>
+        <item name="android:minHeight">40dip</item>
+        <item name="android:paddingLeft">24dip</item>
+        <item name="android:paddingRight">24dip</item>
+    </style>
+
+    <style name="Widget.Holo.Button.Inset">
+        <item name="android:background">@android:drawable/button_inset</item>
+    </style>
+
+    <style name="Widget.Holo.Button.Toggle">
+        <item name="android:background">@android:drawable/btn_toggle_holo_dark</item>
+        <item name="android:textOn">@android:string/capital_on</item>
+        <item name="android:textOff">@android:string/capital_off</item>
+        <item name="android:disabledAlpha">?android:attr/disabledAlpha</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+        <item name="android:minHeight">48dip</item>
+        <item name="android:paddingLeft">24dip</item>
+        <item name="android:paddingRight">24dip</item>
+    </style>
+
+    <style name="Widget.Holo.TextView" parent="Widget.TextView">
+    </style>
+
+    <style name="Widget.Holo.TextView.ListSeparator" parent="Widget.TextView.ListSeparator">
+    </style>
+
+    <style name="Widget.Holo.TextSelectHandle" parent="Widget.TextSelectHandle">
+    </style>
+
+    <style name="Widget.Holo.AbsListView" parent="Widget.AbsListView">
+    </style>
+
+    <style name="Widget.Holo.AutoCompleteTextView" parent="Widget.AutoCompleteTextView">
+    </style>
+
+    <style name="Widget.Holo.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox">
+    </style>
+
+    <style name="Widget.Holo.ListView.DropDown">
+    </style>
+
+    <style name="Widget.Holo.EditText" parent="Widget.EditText">
+    </style>
+
+    <style name="Widget.Holo.ExpandableListView" parent="Widget.ExpandableListView">
+    </style>
+
+    <style name="Widget.Holo.ExpandableListView.White">
+    </style>
+
+    <style name="Widget.Holo.Gallery" parent="Widget.Gallery">
+    </style>
+
+    <style name="Widget.Holo.GestureOverlayView" parent="Widget.GestureOverlayView">
+    </style>
+
+    <style name="Widget.Holo.GridView" parent="Widget.GridView">
+    </style>
+
+    <style name="Widget.Holo.ImageButton" parent="Widget.ImageButton">
+    </style>
+
+    <style name="Widget.Holo.ImageWell" parent="Widget.ImageWell">
+    </style>
+
+    <style name="Widget.Holo.ListView" parent="Widget.ListView">
+    </style>
+
+    <style name="Widget.Holo.ListView.White">
+    </style>
+
+    <style name="Widget.Holo.PopupWindow" parent="Widget.PopupWindow">
+    </style>
+
+    <style name="Widget.Holo.ProgressBar" parent="Widget.ProgressBar">
+    </style>
+
+    <style name="Widget.Holo.ProgressBar.Horizontal" parent="Widget.ProgressBar.Horizontal">
+    </style>
+
+    <style name="Widget.Holo.ProgressBar.Small" parent="Widget.ProgressBar.Horizontal">
+    </style>
+
+    <style name="Widget.Holo.ProgressBar.Small.Title" parent="Widget.ProgressBar.Small.Title">
+    </style>
+
+    <style name="Widget.Holo.ProgressBar.Large" parent="Widget.ProgressBar.Large">
+    </style>
+
+    <style name="Widget.Holo.ProgressBar.Inverse" parent="Widget.ProgressBar.Inverse">
+    </style>
+
+    <style name="Widget.Holo.ProgressBar.Small.Inverse" parent="Widget.ProgressBar.Small.Inverse">
+    </style>
+
+    <style name="Widget.Holo.ProgressBar.Large.Inverse" parent="Widget.ProgressBar.Large.Inverse">
+    </style>
+
+    <style name="Widget.Holo.SeekBar" parent="Widget.SeekBar">
+    </style>
+
+    <style name="Widget.Holo.RatingBar" parent="Widget.RatingBar">
+    </style>
+
+    <style name="Widget.Holo.RatingBar.Indicator" parent="Widget.RatingBar.Indicator">
+    </style>
+
+    <style name="Widget.Holo.RatingBar.Small">
+    </style>
+
+    <style name="Widget.Holo.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton">
+    </style>
+
+    <style name="Widget.Holo.ScrollView" parent="Widget.ScrollView">
+    </style>
+
+    <style name="Widget.Holo.HorizontalScrollView" parent="Widget.HorizontalScrollView">
+    </style>
+
+    <style name="Widget.Holo.Spinner" parent="Widget.Spinner.DropDown">
+    </style>
+
+    <style name="Widget.Holo.Spinner.DropDown">
+    </style>
+
+    <style name="Widget.Holo.CompoundButton.Star" parent="Widget.CompoundButton.Star">
+    </style>
+
+    <style name="Widget.Holo.TabWidget" parent="Widget.TabWidget">
+    </style>
+
+    <style name="Widget.Holo.WebTextView" parent="Widget.WebTextView">
+    </style>
+
+    <style name="Widget.Holo.WebView" parent="Widget.WebView">
+    </style>
+
+    <style name="Widget.Holo.DropDownItem" parent="Widget.DropDownItem">
+    </style>
+
+    <style name="Widget.Holo.DropDownItem.Spinner">
+    </style>
+
+    <style name="Widget.Holo.TextView.SpinnerItem" parent="Widget.TextView.SpinnerItem">
+    </style>
+
+    <style name="Widget.Holo.KeyboardView" parent="Widget.KeyboardView">
+    </style>
+
+    <style name="Widget.Holo.QuickContactBadge.WindowSmall" parent="Widget.QuickContactBadge.WindowSmall">
+    </style>
+
+    <style name="Widget.Holo.QuickContactBadge.WindowMedium" parent="Widget.QuickContactBadge.WindowMedium">
+    </style>
+
+    <style name="Widget.Holo.QuickContactBadge.WindowLarge" parent="Widget.QuickContactBadge.WindowLarge">
+    </style>
+
+    <style name="Widget.Holo.QuickContactBadgeSmall.WindowSmall" parent="Widget.QuickContactBadgeSmall.WindowSmall">
+    </style>
+
+    <style name="Widget.Holo.QuickContactBadgeSmall.WindowMedium" parent="Widget.QuickContactBadgeSmall.WindowMedium">
+    </style>
+
+    <style name="Widget.Holo.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge">
+    </style>
+
+    <style name="Widget.Holo.ListPopupWindow" parent="Widget.ListPopupWindow">
+    </style>
+
+    <style name="Widget.Holo.PopupMenu" parent="Widget.PopupMenu">
+    </style>
+
+    <style name="Widget.Holo.ActionButton" parent="Widget.ActionButton">
+    </style>
+
+    <style name="Widget.Holo.ActionButton.Overflow" parent="Widget.ActionButton.Overflow">
+    </style>
+
+    <style name="Widget.Holo.ActionBarView_TabView" parent="Widget.ActionBarView_TabView">
+    </style>
+
+    <style name="Widget.Holo.ActionBarView_TabBar" parent="Widget.ActionBarView_TabBar">
+    </style>
+
+    <style name="Widget.Holo.ActionBarView_TabText" parent="Widget.ActionBarView_TabText">
+        <item name="android:textAppearance">@style/TextAppearance.Holo.Medium</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <style name="Widget.Holo.ActionMode" parent="Widget.ActionMode">
+        <item name="android:background">@android:drawable/cab_holo_dark</item>
+        <item name="android:titleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionMode.Title</item>
+        <item name="android:subtitleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionMode.Subtitle</item>
+    </style>
+
+    <style name="Widget.Holo.ActionButton.CloseMode" parent="Widget.ActionButton.CloseMode">
+        <item name="android:src">@drawable/cab_ic_close_holo</item>
+    </style>
+
+    <style name="Widget.Holo.ActionBar" parent="Widget.ActionBar">
+        <item name="android:titleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item>
+        <item name="android:subtitleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle</item>
+    </style>
+
+    <!-- Light widget styles -->
+
+    <style name="Widget.Holo.Light">
+    </style>
+
+    <style name="Widget.Holo.Light.Button" parent="Widget.Button">
+    </style>
+
+    <style name="Widget.Holo.Light.Button.Small">
+    </style>
+
+    <style name="Widget.Holo.Light.Button.Inset">
+    </style>
+
+    <style name="Widget.Holo.Light.Button.Toggle">
+        <item name="android:background">@android:drawable/btn_toggle_holo_light</item>
+        <item name="android:textOn">@android:string/capital_on</item>
+        <item name="android:textOff">@android:string/capital_off</item>
+        <item name="android:disabledAlpha">?android:attr/disabledAlpha</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+        <item name="android:minHeight">48dip</item>
+        <item name="android:paddingLeft">24dip</item>
+        <item name="android:paddingRight">24dip</item>
+    </style>
+
+    <style name="Widget.Holo.Light.TextView" parent="Widget.TextView">
+    </style>
+
+    <style name="Widget.Holo.Light.TextView.ListSeparator" parent="Widget.TextView.ListSeparator">
+    </style>
+
+    <style name="Widget.Holo.Light.TextSelectHandle" parent="Widget.TextSelectHandle">
+    </style>
+
+    <style name="Widget.Holo.Light.AbsListView" parent="Widget.AbsListView">
+    </style>
+
+    <style name="Widget.Holo.Light.AutoCompleteTextView" parent="Widget.AutoCompleteTextView">
+    </style>
+
+    <style name="Widget.Holo.Light.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox">
+    </style>
+
+    <style name="Widget.Holo.Light.ListView.DropDown">
+    </style>
+
+    <style name="Widget.Holo.Light.EditText" parent="Widget.EditText">
+    </style>
+
+    <style name="Widget.Holo.Light.ExpandableListView" parent="Widget.ExpandableListView">
+    </style>
+
+    <style name="Widget.Holo.Light.ExpandableListView.White">
+    </style>
+
+    <style name="Widget.Holo.Light.Gallery" parent="Widget.Gallery">
+    </style>
+
+    <style name="Widget.Holo.Light.GestureOverlayView" parent="Widget.GestureOverlayView">
+    </style>
+
+    <style name="Widget.Holo.Light.GridView" parent="Widget.GridView">
+    </style>
+
+    <style name="Widget.Holo.Light.ImageButton" parent="Widget.ImageButton">
+    </style>
+
+    <style name="Widget.Holo.Light.ImageWell" parent="Widget.ImageWell">
+    </style>
+
+    <style name="Widget.Holo.Light.ListView" parent="Widget.ListView">
+    </style>
+
+    <style name="Widget.Holo.Light.ListView.White">
+    </style>
+
+    <style name="Widget.Holo.Light.PopupWindow" parent="Widget.PopupWindow">
+    </style>
+
+    <style name="Widget.Holo.Light.ProgressBar" parent="Widget.ProgressBar">
+    </style>
+
+    <style name="Widget.Holo.Light.ProgressBar.Horizontal" parent="Widget.ProgressBar.Horizontal">
+    </style>
+
+    <style name="Widget.Holo.Light.ProgressBar.Small" parent="Widget.ProgressBar.Small">
+    </style>
+
+    <style name="Widget.Holo.Light.ProgressBar.Small.Title" parent="Widget.ProgressBar.Small.Title">
+    </style>
+
+    <style name="Widget.Holo.Light.ProgressBar.Large" parent="Widget.ProgressBar.Large">
+    </style>
+
+    <style name="Widget.Holo.Light.ProgressBar.Inverse" parent="Widget.ProgressBar.Inverse">
+    </style>
+
+    <style name="Widget.Holo.Light.ProgressBar.Small.Inverse" parent="Widget.ProgressBar.Small.Inverse">
+    </style>
+
+    <style name="Widget.Holo.Light.ProgressBar.Large.Inverse" parent="Widget.ProgressBar.Large.Inverse">
+    </style>
+
+    <style name="Widget.Holo.Light.SeekBar" parent="Widget.SeekBar">
+    </style>
+
+    <style name="Widget.Holo.Light.RatingBar" parent="Widget.RatingBar">
+    </style>
+
+    <style name="Widget.Holo.Light.RatingBar.Indicator" parent="Widget.RatingBar.Indicator">
+    </style>
+
+    <style name="Widget.Holo.Light.RatingBar.Small">
+    </style>
+
+    <style name="Widget.Holo.Light.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton">
+    </style>
+
+    <style name="Widget.Holo.Light.ScrollView" parent="Widget.ScrollView">
+    </style>
+
+    <style name="Widget.Holo.Light.HorizontalScrollView" parent="Widget.HorizontalScrollView">
+    </style>
+
+    <style name="Widget.Holo.Light.Spinner" parent="Widget.Spinner.DropDown">
+    </style>
+
+    <style name="Widget.Holo.Light.Spinner.DropDown">
+    </style>
+
+    <style name="Widget.Holo.Light.CompoundButton.Star" parent="Widget.CompoundButton.Star">
+    </style>
+
+    <style name="Widget.Holo.Light.TabWidget" parent="Widget.TabWidget">
+    </style>
+
+    <style name="Widget.Holo.Light.WebTextView" parent="Widget.WebTextView">
+    </style>
+
+    <style name="Widget.Holo.Light.WebView" parent="Widget.WebView">
+    </style>
+
+    <style name="Widget.Holo.Light.DropDownItem" parent="Widget.DropDownItem">
+    </style>
+
+    <style name="Widget.Holo.Light.DropDownItem.Spinner">
+    </style>
+
+    <style name="Widget.Holo.Light.TextView.SpinnerItem" parent="Widget.TextView.SpinnerItem">
+    </style>
+
+    <style name="Widget.Holo.Light.KeyboardView" parent="Widget.KeyboardView">
+    </style>
+
+    <style name="Widget.Holo.Light.QuickContactBadge.WindowSmall" parent="Widget.QuickContactBadge.WindowSmall">
+    </style>
+
+    <style name="Widget.Holo.Light.QuickContactBadge.WindowMedium" parent="Widget.QuickContactBadge.WindowMedium">
+    </style>
+
+    <style name="Widget.Holo.Light.QuickContactBadge.WindowLarge" parent="Widget.QuickContactBadge.WindowLarge">
+    </style>
+
+    <style name="Widget.Holo.Light.QuickContactBadgeSmall.WindowSmall" parent="Widget.QuickContactBadgeSmall.WindowSmall">
+    </style>
+
+    <style name="Widget.Holo.Light.QuickContactBadgeSmall.WindowMedium" parent="Widget.QuickContactBadgeSmall.WindowMedium">
+    </style>
+
+    <style name="Widget.Holo.Light.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge">
+    </style>
+
+    <style name="Widget.Holo.Light.ListPopupWindow" parent="Widget.ListPopupWindow">
+    </style>
+
+    <style name="Widget.Holo.Light.PopupMenu" parent="Widget.PopupMenu">
+    </style>
+
+    <style name="Widget.Holo.Light.ActionButton" parent="Widget.ActionButton">
+    </style>
+
+    <style name="Widget.Holo.Light.ActionButton.Overflow" parent="Widget.ActionButton.Overflow">
+    </style>
+
+    <style name="Widget.Holo.Light.ActionBarView_TabView" parent="Widget.ActionBarView_TabView">
+    </style>
+
+    <style name="Widget.Holo.Light.ActionBarView_TabBar" parent="Widget.ActionBarView_TabBar">
+    </style>
+
+    <style name="Widget.Holo.Light.ActionBarView_TabText" parent="Widget.ActionBarView_TabText">
+    </style>
+
+    <style name="Widget.Holo.Light.ActionMode" parent="Widget.ActionMode">
+        <item name="android:background">@android:drawable/cab_holo_light</item>
+        <item name="android:titleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionMode.Title</item>
+        <item name="android:subtitleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionMode.Subtitle</item>
+    </style>
+
+    <style name="Widget.Holo.Light.ActionButton.CloseMode" parent="Widget.ActionButton.CloseMode">
+    </style>
+
+    <style name="Widget.Holo.Light.ActionBar" parent="Widget.ActionBar">
+        <item name="android:titleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item>
+        <item name="android:subtitleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle</item>
+    </style>
+
+    <!-- Animation Styles -->
+
+    <style name="Animation.Holo" parent="Animation">
+    </style>
+
+    <style name="Animation.Holo.Activity" parent="Animation.Activity">
+    </style>
+
+    <style name="Animation.Holo.Dialog" parent="Animation.Dialog">
+    </style>
+
+    <!-- Dialog styles -->
+
+    <style name="AlertDialog.Holo" parent="AlertDialog">
+        <item name="fullDark">@android:drawable/dialog_full_holo</item>
+        <item name="topDark">@android:drawable/dialog_top_holo</item>
+        <item name="centerDark">@android:drawable/dialog_middle_holo</item>
+        <item name="bottomDark">@android:drawable/dialog_bottom_holo</item>
+        <item name="fullBright">@android:drawable/dialog_full_holo</item>
+        <item name="topBright">@android:drawable/dialog_top_holo</item>
+        <item name="centerBright">@android:drawable/dialog_middle_holo</item>
+        <item name="bottomBright">@android:drawable/dialog_bottom_holo</item>
+        <item name="bottomMedium">@android:drawable/dialog_bottom_holo</item>
+        <item name="centerMedium">@android:drawable/dialog_middle_holo</item>
+    </style>
+
+    <!-- Window title -->
+    <style name="WindowTitleBackground.Holo">
+        <item name="android:background">@android:drawable/title_bar</item>
+    </style>
+
+    <style name="WindowTitle.Holo">
+        <item name="android:singleLine">true</item>
+        <item name="android:textAppearance">@style/TextAppearance.Holo.WindowTitle</item>
+        <item name="android:shadowColor">#BB000000</item>
+        <item name="android:shadowRadius">2.75</item>
+    </style>
+
+    <style name="DialogWindowTitle.Holo">
+        <item name="android:maxLines">1</item>
+        <item name="android:scrollHorizontally">true</item>
+        <item name="android:textAppearance">@style/TextAppearance.Holo.DialogWindowTitle</item>
+    </style>
+
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index e7cb593..21d91ba 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -53,6 +53,11 @@
         <item name="textColorHint">@android:color/hint_foreground_dark</item>
         <item name="textColorHintInverse">@android:color/hint_foreground_light</item>
         <item name="textColorSearchUrl">@android:color/search_url_text</item>
+        <item name="textColorHighlight">@android:color/highlighted_text_dark</item>
+        <item name="textColorHighlightInverse">@android:color/highlighted_text_light</item>
+        <item name="textColorLink">@android:color/link_text_dark</item>
+        <item name="textColorLinkInverse">@android:color/link_text_light</item>
+        <item name="textColorAlertDialogListItem">@android:color/primary_text_light_disable_only</item>
 
         <item name="textAppearanceLarge">@android:style/TextAppearance.Large</item>
         <item name="textAppearanceMedium">@android:style/TextAppearance.Medium</item>
@@ -65,11 +70,17 @@
         
         <item name="textAppearanceButton">@android:style/TextAppearance.Widget.Button</item>
         
+        <item name="editTextColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="editTextBackground">@android:drawable/edit_text</item>
+        
         <item name="candidatesTextStyleSpans">@android:string/candidates_style</item>
         
         <item name="textCheckMark">@android:drawable/indicator_check_mark_dark</item>
         <item name="textCheckMarkInverse">@android:drawable/indicator_check_mark_light</item>
 
+        <item name="textAppearanceLargePopupMenu">@android:style/TextAppearance.Widget.PopupMenu.Large</item>
+        <item name="textAppearanceSmallPopupMenu">@android:style/TextAppearance.Widget.PopupMenu.Small</item>
+
         <!-- Button styles -->
         <item name="buttonStyle">@android:style/Widget.Button</item>
 
@@ -84,9 +95,15 @@
         <item name="searchResultListItemHeight">58dip</item>
         <item name="listDivider">@drawable/divider_horizontal_dark</item>
         <item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator</item>   
-        
-		<item name="listChoiceIndicatorSingle">@android:drawable/btn_radio</item>
-    	<item name="listChoiceIndicatorMultiple">@android:drawable/btn_check</item>    
+
+        <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio</item>
+        <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check</item>
+
+        <item name="listChoiceBackgroundIndicator">@android:drawable/list_selected_background</item>
+
+        <item name="activatedBackgroundIndicator">@android:drawable/activated_background</item>
+
+        <item name="listDividerAlertDialog">@android:drawable/divider_horizontal_bright</item>
 
         <item name="expandableListPreferredItemPaddingLeft">40dip</item>
         <item name="expandableListPreferredChildPaddingLeft">
@@ -115,6 +132,8 @@
         <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>
 
         <!-- Dialog attributes -->
         <item name="alertDialogStyle">@android:style/AlertDialog</item>
@@ -164,8 +183,8 @@
         <item name="progressBarStyleSmallTitle">@android:style/Widget.ProgressBar.Small.Title</item>
         <item name="progressBarStyleLarge">@android:style/Widget.ProgressBar.Large</item>
         <item name="progressBarStyleInverse">@android:style/Widget.ProgressBar.Inverse</item>
-		<item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small.Inverse</item>
-	    <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large.Inverse</item> 
+        <item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small.Inverse</item>
+        <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large.Inverse</item>
         <item name="seekBarStyle">@android:style/Widget.SeekBar</item>
         <item name="ratingBarStyle">@android:style/Widget.RatingBar</item>
         <item name="ratingBarStyleIndicator">@android:style/Widget.RatingBar.Indicator</item>
@@ -174,6 +193,7 @@
         <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="starStyle">@android:style/Widget.CompoundButton.Star</item>
         <item name="tabWidgetStyle">@android:style/Widget.TabWidget</item>
         <item name="textViewStyle">@android:style/Widget.TextView</item>
@@ -190,6 +210,8 @@
         <item name="quickContactBadgeStyleSmallWindowSmall">@android:style/Widget.QuickContactBadgeSmall.WindowSmall</item>
         <item name="quickContactBadgeStyleSmallWindowMedium">@android:style/Widget.QuickContactBadgeSmall.WindowMedium</item>
         <item name="quickContactBadgeStyleSmallWindowLarge">@android:style/Widget.QuickContactBadgeSmall.WindowLarge</item>
+        <item name="listPopupWindowStyle">@android:style/Widget.ListPopupWindow</item>
+        <item name="popupMenuStyle">@android:style/Widget.PopupMenu</item>
         
         <!-- Preference styles -->
         <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item>
@@ -205,6 +227,21 @@
 
         <!-- Search widget styles -->
         <item name="searchWidgetCorpusItemBackground">@android:color/search_widget_corpus_item_background</item>
+
+        <!-- Action bar styles -->
+        <item name="actionDropDownStyle">@android:style/Widget.Spinner.DropDown</item>
+        <item name="actionButtonStyle">@android:style/Widget.ActionButton</item>
+        <item name="actionOverflowButtonStyle">@android:style/Widget.ActionButton.Overflow</item>
+        <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>
+        <item name="actionBarTabStyle">@style/Widget.ActionBarView_TabView</item>
+        <item name="actionBarTabBarStyle">@style/Widget.ActionBarView_TabBar</item>
+        <item name="actionBarTabTextStyle">@style/Widget.ActionBarView_TabText</item>
+        <item name="actionModeStyle">@style/Widget.ActionMode</item>
+        <item name="actionModeCloseButtonStyle">@style/Widget.ActionButton.CloseMode</item>
+        <item name="actionBarStyle">@android:style/Widget.ActionBar</item>
+        <item name="actionBarSize">50dip</item>
     </style>
     
     <!-- Variant of the default (dark) theme with no title bar -->
@@ -228,7 +265,7 @@
         <item name="colorBackground">@android:color/background_light</item>
         <item name="colorForeground">@color/bright_foreground_light</item>
         <item name="colorForegroundInverse">@android:color/bright_foreground_light_inverse</item>
-
+        
         <item name="textColorPrimary">@android:color/primary_text_light</item>
         <item name="textColorSecondary">@android:color/secondary_text_light</item>
         <item name="textColorTertiary">@android:color/tertiary_text_light</item>
@@ -242,8 +279,17 @@
         <item name="textColorPrimaryInverseNoDisable">@android:color/primary_text_dark_nodisable</item>
         <item name="textColorSecondaryInverseNoDisable">@android:color/secondary_text_dark_nodisable</item>
         <item name="textColorHint">@android:color/hint_foreground_light</item>
-        <item name="textColorHintInverse">@android:color/hint_foreground_dark</item>
+        <item name="textColorHintInverse">@android:color/hint_foreground_dark</item>        
+        <item name="textColorHighlight">@android:color/highlighted_text_light</item>
+        <item name="textColorHighlightInverse">@android:color/highlighted_text_dark</item>
+        <item name="textColorLink">@android:color/link_text_light</item>
+        <item name="textColorLinkInverse">@android:color/link_text_dark</item>
         
+        <item name="editTextColor">?android:attr/textColorPrimary</item>
+        <item name="listChoiceBackgroundIndicator">@android:drawable/list_selected_background_light</item>
+
+        <item name="activatedBackgroundIndicator">@android:drawable/activated_background_light</item>
+
         <item name="popupWindowStyle">@android:style/Widget.PopupWindow</item>
         
         <item name="textCheckMark">@android:drawable/indicator_check_mark_light</item>
@@ -254,13 +300,13 @@
         <item name="listViewStyle">@android:style/Widget.ListView.White</item>
         <item name="listDivider">@drawable/divider_horizontal_bright</item>
         <item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator.White</item>
-        
+
         <item name="progressBarStyle">@android:style/Widget.ProgressBar.Inverse</item>
-		<item name="progressBarStyleSmall">@android:style/Widget.ProgressBar.Small.Inverse</item>
-		<item name="progressBarStyleLarge">@android:style/Widget.ProgressBar.Large.Inverse</item>
-		<item name="progressBarStyleInverse">@android:style/Widget.ProgressBar</item>
-		<item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small</item>
-		<item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large</item> 
+        <item name="progressBarStyleSmall">@android:style/Widget.ProgressBar.Small.Inverse</item>
+        <item name="progressBarStyleLarge">@android:style/Widget.ProgressBar.Large.Inverse</item>
+        <item name="progressBarStyleInverse">@android:style/Widget.ProgressBar</item>
+        <item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small</item>
+        <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large</item>
     </style>
     
     <!-- Variant of the light theme with no title bar -->
@@ -423,13 +469,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>
     
@@ -535,5 +593,549 @@
         <item name="android:windowAnimationStyle">@android:style/Animation.Toast</item>
         <item name="android:backgroundDimEnabled">false</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>
     
+    <!-- New Honeycomb holographic theme. Dark version.  The widgets in the
+         holographic theme are translucent on their brackground, so applications
+         must ensure that any background they use with this theme is itself
+         dark; otherwise, it will be difficult to see the widgets.  The new
+         UI style also includes a full action bar by default.
+
+         Styles used by the Holo theme are named using the convention Type.Holo.Etc.
+         (For example, Widget.Holo.Button, TextAppearance.Holo.Widget.PopupMenu.Large.)
+         Specific resources used by Holo are named using the convention @type/foo_bar_baz_holo
+         with trailing _dark or _light specifiers if they are not shared between both light and
+         dark versions of the theme. -->
+    <style name="Theme.Holo">
+        <item name="colorForeground">@android:color/bright_foreground_holo_dark</item>
+        <item name="colorForegroundInverse">@android:color/bright_foreground_inverse_holo_dark</item>
+        <item name="colorBackground">@android:color/background_holo_dark</item>
+        <item name="colorBackgroundCacheHint">?android:attr/colorBackground</item>
+        <item name="disabledAlpha">0.5</item>
+        <item name="backgroundDimAmount">0.6</item>
+
+        <!-- Text styles -->
+        <item name="textAppearance">@android:style/TextAppearance.Holo</item>
+        <item name="textAppearanceInverse">@android:style/TextAppearance.Holo.Inverse</item>
+
+        <item name="textColorPrimary">@android:color/primary_text_holo_dark</item>
+        <item name="textColorSecondary">@android:color/secondary_text_holo_dark</item>
+        <item name="textColorTertiary">@android:color/tertiary_text_holo_dark</item>
+        <item name="textColorPrimaryInverse">@android:color/primary_text_holo_light</item>
+        <item name="textColorSecondaryInverse">@android:color/secondary_text_holo_light</item>
+        <item name="textColorTertiaryInverse">@android:color/tertiary_text_holo_light</item>
+        <item name="textColorPrimaryDisableOnly">@android:color/primary_text_disable_only_holo_dark</item>
+        <item name="textColorPrimaryInverseDisableOnly">@android:color/primary_text_disable_only_holo_light</item>
+        <item name="textColorPrimaryNoDisable">@android:color/primary_text_nodisable_holo_dark</item>
+        <item name="textColorSecondaryNoDisable">@android:color/secondary_text_nodisable_holo_dark</item>
+        <item name="textColorPrimaryInverseNoDisable">@android:color/primary_text_nodisable_holo_light</item>
+        <item name="textColorSecondaryInverseNoDisable">@android:color/secondary_text_nodisable_holo_light</item>
+        <item name="textColorHint">@android:color/hint_foreground_holo_dark</item>
+        <item name="textColorHintInverse">@android:color/hint_foreground_holo_light</item>
+        <item name="textColorSearchUrl">@android:color/search_url_text_holo</item>
+        <item name="textColorHighlight">@android:color/highlighted_text_holo_dark</item>
+        <item name="textColorHighlightInverse">@android:color/highlighted_text_holo_light</item>
+        <item name="textColorLink">@android:color/link_text_holo_dark</item>
+        <item name="textColorLinkInverse">@android:color/link_text_holo_light</item>
+        <item name="textColorAlertDialogListItem">@android:color/primary_text_holo_dark</item>
+
+        <item name="textAppearanceLarge">@android:style/TextAppearance.Holo.Large</item>
+        <item name="textAppearanceMedium">@android:style/TextAppearance.Holo.Medium</item>
+        <item name="textAppearanceSmall">@android:style/TextAppearance.Holo.Small</item>
+        <item name="textAppearanceLargeInverse">@android:style/TextAppearance.Holo.Large.Inverse</item>
+        <item name="textAppearanceMediumInverse">@android:style/TextAppearance.Holo.Medium.Inverse</item>
+        <item name="textAppearanceSmallInverse">@android:style/TextAppearance.Holo.Small.Inverse</item>
+        <item name="textAppearanceSearchResultTitle">@android:style/TextAppearance.Holo.SearchResult.Title</item>
+        <item name="textAppearanceSearchResultSubtitle">@android:style/TextAppearance.Holo.SearchResult.Subtitle</item>
+        
+        <item name="textAppearanceButton">@android:style/TextAppearance.Holo.Widget.Button</item>
+        
+        <item name="editTextColor">?android:attr/textColorPrimary</item>
+        <item name="editTextBackground">@android:drawable/edit_text_holo_dark</item>
+        
+        <item name="candidatesTextStyleSpans">@android:string/candidates_style</item>
+        
+        <item name="textCheckMark">@android:drawable/indicator_check_mark_dark</item>
+        <item name="textCheckMarkInverse">@android:drawable/indicator_check_mark_light</item>
+
+        <item name="textAppearanceLargePopupMenu">@android:style/TextAppearance.Holo.Widget.PopupMenu.Large</item>
+        <item name="textAppearanceSmallPopupMenu">@android:style/TextAppearance.Holo.Widget.PopupMenu.Small</item>
+
+        <!-- Button styles -->
+        <item name="buttonStyle">@android:style/Widget.Holo.Button</item>
+
+        <item name="buttonStyleSmall">@android:style/Widget.Holo.Button.Small</item>
+        <item name="buttonStyleInset">@android:style/Widget.Holo.Button.Inset</item>
+
+        <item name="buttonStyleToggle">@android:style/Widget.Holo.Button.Toggle</item>
+
+        <!-- List attributes -->
+        <item name="listPreferredItemHeight">64dip</item>
+        <!-- @hide -->
+        <item name="searchResultListItemHeight">58dip</item>
+        <item name="listDivider">@drawable/divider_horizontal_holo_dark</item>
+        <item name="listSeparatorTextViewStyle">@android:style/Widget.Holo.TextView.ListSeparator</item>   
+
+        <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio_holo_dark</item>
+        <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check_holo_dark</item>
+
+        <item name="listChoiceBackgroundIndicator">@android:drawable/list_selected_background</item>
+
+        <item name="activatedBackgroundIndicator">@android:drawable/activated_background</item>
+
+        <item name="listDividerAlertDialog">@android:drawable/divider_horizontal_holo_dark</item>
+
+        <item name="expandableListPreferredItemPaddingLeft">40dip</item>
+        <item name="expandableListPreferredChildPaddingLeft">
+                ?android:attr/expandableListPreferredItemPaddingLeft</item>
+
+        <item name="expandableListPreferredItemIndicatorLeft">3dip</item>
+        <item name="expandableListPreferredItemIndicatorRight">33dip</item>
+        <item name="expandableListPreferredChildIndicatorLeft">
+                ?android:attr/expandableListPreferredItemIndicatorLeft</item>
+        <item name="expandableListPreferredChildIndicatorRight">
+                ?android:attr/expandableListPreferredItemIndicatorRight</item>
+
+        <!-- Gallery attributes -->
+        <item name="galleryItemBackground">@android:drawable/gallery_item_background</item>
+        
+        <!-- Window attributes -->
+        <item name="windowBackground">@android:drawable/screen_background_holo_dark</item>
+        <item name="windowFrame">@null</item>
+        <item name="windowNoTitle">false</item>
+        <item name="windowFullscreen">false</item>
+        <item name="windowIsFloating">false</item>
+        <item name="windowContentOverlay">@android:drawable/title_bar_shadow</item>
+        <item name="windowShowWallpaper">false</item>
+        <item name="windowTitleStyle">@android:style/WindowTitle.Holo</item>
+        <item name="windowTitleSize">25dip</item>
+        <item name="windowTitleBackgroundStyle">@android:style/WindowTitleBackground.Holo</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.Holo.Activity</item>
+        <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
+        <item name="windowActionBar">true</item>
+        <item name="windowActionModeOverlay">false</item>
+
+        <!-- Dialog attributes -->
+        <item name="alertDialogStyle">@android:style/AlertDialog.Holo</item>
+        
+        <!-- Panel attributes -->
+        <item name="panelBackground">@android:drawable/menu_background</item>
+        <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item>
+        <!-- These three attributes do not seems to be used by the framework. Declared public though -->
+        <item name="panelColorBackground">#000</item>
+        <item name="panelColorForeground">?android:attr/textColorPrimary</item>
+        <item name="panelTextAppearance">?android:attr/textAppearance</item>
+
+        <!-- Scrollbar attributes -->
+        <item name="scrollbarFadeDuration">250</item>
+        <item name="scrollbarDefaultDelayBeforeFade">300</item> 
+        <item name="scrollbarSize">10dip</item>
+        <item name="scrollbarThumbHorizontal">@android:drawable/scrollbar_handle_horizontal</item>
+        <item name="scrollbarThumbVertical">@android:drawable/scrollbar_handle_vertical</item>
+        <item name="scrollbarTrackHorizontal">@null</item>
+        <item name="scrollbarTrackVertical">@null</item>
+
+        <!-- Text selection handle attributes -->
+        <item name="textSelectHandleLeft">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandleRight">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandle">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandleWindowStyle">@android:style/Widget.Holo.TextSelectHandle</item>
+
+        <!-- Widget styles -->
+        <item name="absListViewStyle">@android:style/Widget.Holo.AbsListView</item>
+        <item name="autoCompleteTextViewStyle">@android:style/Widget.Holo.AutoCompleteTextView</item>
+        <item name="checkboxStyle">@android:style/Widget.Holo.CompoundButton.CheckBox</item>
+        <item name="dropDownListViewStyle">@android:style/Widget.Holo.ListView.DropDown</item>
+        <item name="editTextStyle">@android:style/Widget.Holo.EditText</item>
+        <item name="expandableListViewStyle">@android:style/Widget.Holo.ExpandableListView</item>
+        <item name="expandableListViewWhiteStyle">@android:style/Widget.Holo.ExpandableListView.White</item>
+        <item name="galleryStyle">@android:style/Widget.Holo.Gallery</item>
+        <item name="gestureOverlayViewStyle">@android:style/Widget.Holo.GestureOverlayView</item>
+        <item name="gridViewStyle">@android:style/Widget.Holo.GridView</item>
+        <item name="imageButtonStyle">@android:style/Widget.Holo.ImageButton</item>
+        <item name="imageWellStyle">@android:style/Widget.Holo.ImageWell</item>
+        <item name="listViewStyle">@android:style/Widget.Holo.ListView</item>
+        <item name="listViewWhiteStyle">@android:style/Widget.Holo.ListView.White</item>
+        <item name="popupWindowStyle">@android:style/Widget.Holo.PopupWindow</item>
+        <item name="progressBarStyle">@android:style/Widget.Holo.ProgressBar</item>
+        <item name="progressBarStyleHorizontal">@android:style/Widget.Holo.ProgressBar.Horizontal</item>
+        <item name="progressBarStyleSmall">@android:style/Widget.Holo.ProgressBar.Small</item>
+        <item name="progressBarStyleSmallTitle">@android:style/Widget.Holo.ProgressBar.Small.Title</item>
+        <item name="progressBarStyleLarge">@android:style/Widget.Holo.ProgressBar.Large</item>
+        <item name="progressBarStyleInverse">@android:style/Widget.Holo.ProgressBar.Inverse</item>
+        <item name="progressBarStyleSmallInverse">@android:style/Widget.Holo.ProgressBar.Small.Inverse</item>
+        <item name="progressBarStyleLargeInverse">@android:style/Widget.Holo.ProgressBar.Large.Inverse</item>
+        <item name="seekBarStyle">@android:style/Widget.Holo.SeekBar</item>
+        <item name="ratingBarStyle">@android:style/Widget.Holo.RatingBar</item>
+        <item name="ratingBarStyleIndicator">@android:style/Widget.Holo.RatingBar.Indicator</item>
+        <item name="ratingBarStyleSmall">@android:style/Widget.Holo.RatingBar.Small</item>
+        <item name="radioButtonStyle">@android:style/Widget.Holo.CompoundButton.RadioButton</item>
+        <item name="scrollViewStyle">@android:style/Widget.Holo.ScrollView</item>
+        <item name="horizontalScrollViewStyle">@android:style/Widget.Holo.HorizontalScrollView</item>
+        <item name="spinnerStyle">?android:attr/dropDownSpinnerStyle</item>
+        <item name="dropDownSpinnerStyle">@android:style/Widget.Holo.Spinner.DropDown</item>
+        <item name="starStyle">@android:style/Widget.Holo.CompoundButton.Star</item>
+        <item name="tabWidgetStyle">@android:style/Widget.Holo.TabWidget</item>
+        <item name="textViewStyle">@android:style/Widget.Holo.TextView</item>
+        <item name="webTextViewStyle">@android:style/Widget.Holo.WebTextView</item>
+        <item name="webViewStyle">@android:style/Widget.Holo.WebView</item>
+        <item name="dropDownItemStyle">@android:style/Widget.Holo.DropDownItem</item>
+        <item name="spinnerDropDownItemStyle">@android:style/Widget.Holo.DropDownItem.Spinner</item>
+        <item name="spinnerItemStyle">@android:style/Widget.Holo.TextView.SpinnerItem</item>
+        <item name="dropDownHintAppearance">@android:style/TextAppearance.Holo.Widget.DropDownHint</item>
+        <item name="keyboardViewStyle">@android:style/Widget.Holo.KeyboardView</item>
+        <item name="quickContactBadgeStyleWindowSmall">@android:style/Widget.Holo.QuickContactBadge.WindowSmall</item>
+        <item name="quickContactBadgeStyleWindowMedium">@android:style/Widget.Holo.QuickContactBadge.WindowMedium</item>
+        <item name="quickContactBadgeStyleWindowLarge">@android:style/Widget.Holo.QuickContactBadge.WindowLarge</item>
+        <item name="quickContactBadgeStyleSmallWindowSmall">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowSmall</item>
+        <item name="quickContactBadgeStyleSmallWindowMedium">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowMedium</item>
+        <item name="quickContactBadgeStyleSmallWindowLarge">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowLarge</item>
+        <item name="listPopupWindowStyle">@android:style/Widget.Holo.ListPopupWindow</item>
+        <item name="popupMenuStyle">@android:style/Widget.Holo.PopupMenu</item>
+        
+        <!-- Preference styles -->
+        <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item>
+        <item name="preferenceCategoryStyle">@android:style/Preference.Category</item>
+        <item name="preferenceStyle">@android:style/Preference</item>
+        <item name="preferenceInformationStyle">@android:style/Preference.Information</item>
+        <item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item>
+        <item name="yesNoPreferenceStyle">@android:style/Preference.DialogPreference.YesNoPreference</item>
+        <item name="dialogPreferenceStyle">@android:style/Preference.DialogPreference</item>
+        <item name="editTextPreferenceStyle">@android:style/Preference.DialogPreference.EditTextPreference</item>
+        <item name="ringtonePreferenceStyle">@android:style/Preference.RingtonePreference</item>
+        <item name="preferenceLayoutChild">@android:layout/preference_child</item>
+
+        <!-- Search widget styles -->
+        <item name="searchWidgetCorpusItemBackground">@android:color/search_widget_corpus_item_background</item>
+
+        <!-- Action bar styles -->
+        <item name="actionDropDownStyle">@android:style/Widget.Holo.Spinner.DropDown</item>
+        <item name="actionButtonStyle">@android:style/Widget.Holo.ActionButton</item>
+        <item name="actionOverflowButtonStyle">@android:style/Widget.Holo.ActionButton.Overflow</item>
+        <item name="actionButtonPadding">12dip</item>
+        <item name="actionModeBackground">@android:drawable/cab_holo_dark</item>
+        <item name="actionModeCloseDrawable">@android:drawable/cab_ic_close_holo</item>
+        <item name="actionBarTabStyle">@style/Widget.Holo.ActionBarView_TabView</item>
+        <item name="actionBarTabBarStyle">@style/Widget.Holo.ActionBarView_TabBar</item>
+        <item name="actionBarTabTextStyle">@style/Widget.Holo.ActionBarView_TabText</item>
+        <item name="actionModeStyle">@style/Widget.Holo.ActionMode</item>
+        <item name="actionModeCloseButtonStyle">@style/Widget.Holo.ActionButton.CloseMode</item>
+        <item name="actionBarStyle">@android:style/Widget.Holo.ActionBar</item>
+        <item name="actionBarSize">50dip</item>
+
+    </style>
+
+    <!-- New Honeycomb holographic theme. Light version.  The widgets in the
+         holographic theme are translucent on their brackground, so applications
+         must ensure that any background they use with this theme is itself
+         light; otherwise, it will be difficult to see the widgets.  The new
+         UI style also includes a full action bar by default. -->
+    <style name="Theme.Holo.Light" parent="Theme.Light">
+        <item name="colorForeground">@android:color/bright_foreground_holo_light</item>
+        <item name="colorForegroundInverse">@android:color/bright_foreground_inverse_holo_light</item>
+        <item name="colorBackground">@android:color/background_holo_light</item>
+        <item name="colorBackgroundCacheHint">?android:attr/colorBackground</item>
+        <item name="disabledAlpha">0.5</item>
+        <item name="backgroundDimAmount">0.6</item>
+
+        <!-- Text styles -->
+        <item name="textAppearance">@android:style/TextAppearance.Holo.Light</item>
+        <item name="textAppearanceInverse">@android:style/TextAppearance.Holo.Light.Inverse</item>
+
+        <item name="textColorPrimary">@android:color/primary_text_holo_light</item>
+        <item name="textColorSecondary">@android:color/secondary_text_holo_light</item>
+        <item name="textColorTertiary">@android:color/tertiary_text_holo_light</item>
+        <item name="textColorPrimaryInverse">@android:color/primary_text_holo_dark</item>
+        <item name="textColorSecondaryInverse">@android:color/secondary_text_holo_dark</item>
+        <item name="textColorTertiaryInverse">@android:color/tertiary_text_holo_dark</item>
+        <item name="textColorPrimaryDisableOnly">@android:color/primary_text_disable_only_holo_light</item>
+        <item name="textColorPrimaryInverseDisableOnly">@android:color/primary_text_disable_only_holo_dark</item>
+        <item name="textColorPrimaryNoDisable">@android:color/primary_text_nodisable_holo_light</item>
+        <item name="textColorSecondaryNoDisable">@android:color/secondary_text_nodisable_holo_light</item>
+        <item name="textColorPrimaryInverseNoDisable">@android:color/primary_text_nodisable_holo_dark</item>
+        <item name="textColorSecondaryInverseNoDisable">@android:color/secondary_text_nodisable_holo_dark</item>
+        <item name="textColorHint">@android:color/hint_foreground_holo_light</item>
+        <item name="textColorHintInverse">@android:color/hint_foreground_holo_dark</item>
+        <item name="textColorSearchUrl">@android:color/search_url_text_holo</item>
+        <item name="textColorHighlight">@android:color/highlighted_text_holo_light</item>
+        <item name="textColorHighlightInverse">@android:color/highlighted_text_holo_dark</item>
+        <item name="textColorLink">@android:color/link_text_holo_light</item>
+        <item name="textColorLinkInverse">@android:color/link_text_holo_dark</item>
+        <item name="textColorAlertDialogListItem">@android:color/primary_text_holo_dark</item>
+
+        <item name="textAppearanceLarge">@android:style/TextAppearance.Holo.Light.Large</item>
+        <item name="textAppearanceMedium">@android:style/TextAppearance.Holo.Light.Medium</item>
+        <item name="textAppearanceSmall">@android:style/TextAppearance.Holo.Light.Small</item>
+        <item name="textAppearanceLargeInverse">@android:style/TextAppearance.Holo.Light.Large.Inverse</item>
+        <item name="textAppearanceMediumInverse">@android:style/TextAppearance.Holo.Light.Medium.Inverse</item>
+        <item name="textAppearanceSmallInverse">@android:style/TextAppearance.Holo.Light.Small.Inverse</item>
+        <item name="textAppearanceSearchResultTitle">@android:style/TextAppearance.Holo.Light.SearchResult.Title</item>
+        <item name="textAppearanceSearchResultSubtitle">@android:style/TextAppearance.Holo.Light.SearchResult.Subtitle</item>
+        
+        <item name="textAppearanceButton">@android:style/TextAppearance.Holo.Light.Widget.Button</item>
+        
+        <item name="editTextColor">?android:attr/textColorPrimary</item>
+        <item name="editTextBackground">@android:drawable/edit_text_holo_light</item>
+        
+        <item name="candidatesTextStyleSpans">@android:string/candidates_style</item>
+        
+        <item name="textCheckMark">@android:drawable/indicator_check_mark_light</item>
+        <item name="textCheckMarkInverse">@android:drawable/indicator_check_mark_dark</item>
+
+        <item name="textAppearanceLargePopupMenu">@android:style/TextAppearance.Holo.Light.Widget.PopupMenu.Large</item>
+        <item name="textAppearanceSmallPopupMenu">@android:style/TextAppearance.Holo.Light.Widget.PopupMenu.Small</item>
+
+        <!-- Button styles -->
+        <item name="buttonStyle">@android:style/Widget.Holo.Light.Button</item>
+
+        <item name="buttonStyleSmall">@android:style/Widget.Holo.Light.Button.Small</item>
+        <item name="buttonStyleInset">@android:style/Widget.Holo.Light.Button.Inset</item>
+
+        <item name="buttonStyleToggle">@android:style/Widget.Holo.Light.Button.Toggle</item>
+
+        <!-- List attributes -->
+        <item name="listPreferredItemHeight">64dip</item>
+        <!-- @hide -->
+        <item name="searchResultListItemHeight">58dip</item>
+        <item name="listDivider">@drawable/divider_horizontal_holo_dark</item>
+        <item name="listSeparatorTextViewStyle">@android:style/Widget.Holo.Light.TextView.ListSeparator</item>   
+
+        <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio</item>
+        <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check</item>
+
+        <item name="listChoiceBackgroundIndicator">@android:drawable/list_selected_background</item>
+
+        <item name="activatedBackgroundIndicator">@android:drawable/activated_background</item>
+
+        <item name="expandableListPreferredItemPaddingLeft">40dip</item>
+        <item name="expandableListPreferredChildPaddingLeft">
+                ?android:attr/expandableListPreferredItemPaddingLeft</item>
+
+        <item name="expandableListPreferredItemIndicatorLeft">3dip</item>
+        <item name="expandableListPreferredItemIndicatorRight">33dip</item>
+        <item name="expandableListPreferredChildIndicatorLeft">
+                ?android:attr/expandableListPreferredItemIndicatorLeft</item>
+        <item name="expandableListPreferredChildIndicatorRight">
+                ?android:attr/expandableListPreferredItemIndicatorRight</item>
+
+        <item name="listDividerAlertDialog">@android:drawable/divider_horizontal_holo_dark</item>
+
+        <!-- Gallery attributes -->
+        <item name="galleryItemBackground">@android:drawable/gallery_item_background</item>
+        
+        <!-- Window attributes -->
+        <item name="windowBackground">@android:drawable/screen_background_holo_light</item>
+        <item name="windowFrame">@null</item>
+        <item name="windowNoTitle">false</item>
+        <item name="windowFullscreen">false</item>
+        <item name="windowIsFloating">false</item>
+        <item name="windowContentOverlay">@android:drawable/title_bar_shadow</item>
+        <item name="windowShowWallpaper">false</item>
+        <item name="windowTitleStyle">@android:style/WindowTitle.Holo</item>
+        <item name="windowTitleSize">25dip</item>
+        <item name="windowTitleBackgroundStyle">@android:style/WindowTitleBackground.Holo</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.Holo.Activity</item>
+        <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
+        <item name="windowActionBar">true</item>
+        <item name="windowActionModeOverlay">false</item>
+
+        <!-- Dialog attributes -->
+        <item name="alertDialogStyle">@android:style/AlertDialog.Holo</item>
+        
+        <!-- Panel attributes -->
+        <item name="panelBackground">@android:drawable/menu_background</item>
+        <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item>
+        <!-- These three attributes do not seems to be used by the framework. Declared public though -->
+        <item name="panelColorBackground">#000</item>
+        <item name="panelColorForeground">?android:attr/textColorPrimary</item>
+        <item name="panelTextAppearance">?android:attr/textAppearance</item>
+
+        <!-- Scrollbar attributes -->
+        <item name="scrollbarFadeDuration">250</item>
+        <item name="scrollbarDefaultDelayBeforeFade">300</item>
+        <item name="scrollbarSize">10dip</item>
+        <item name="scrollbarThumbHorizontal">@android:drawable/scrollbar_handle_horizontal</item>
+        <item name="scrollbarThumbVertical">@android:drawable/scrollbar_handle_vertical</item>
+        <item name="scrollbarTrackHorizontal">@null</item>
+        <item name="scrollbarTrackVertical">@null</item>
+
+        <!-- Text selection handle attributes -->
+        <item name="textSelectHandleLeft">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandleRight">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandle">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandleWindowStyle">@android:style/Widget.Holo.TextSelectHandle</item>
+
+        <!-- Widget styles -->
+        <item name="absListViewStyle">@android:style/Widget.Holo.AbsListView</item>
+        <item name="autoCompleteTextViewStyle">@android:style/Widget.Holo.AutoCompleteTextView</item>
+        <item name="checkboxStyle">@android:style/Widget.Holo.CompoundButton.CheckBox</item>
+        <item name="dropDownListViewStyle">@android:style/Widget.Holo.ListView.DropDown</item>
+        <item name="editTextStyle">@android:style/Widget.Holo.EditText</item>
+        <item name="expandableListViewStyle">@android:style/Widget.Holo.ExpandableListView</item>
+        <item name="expandableListViewWhiteStyle">@android:style/Widget.Holo.ExpandableListView.White</item>
+        <item name="galleryStyle">@android:style/Widget.Holo.Gallery</item>
+        <item name="gestureOverlayViewStyle">@android:style/Widget.Holo.GestureOverlayView</item>
+        <item name="gridViewStyle">@android:style/Widget.Holo.GridView</item>
+        <item name="imageButtonStyle">@android:style/Widget.Holo.ImageButton</item>
+        <item name="imageWellStyle">@android:style/Widget.Holo.ImageWell</item>
+        <item name="listViewStyle">@android:style/Widget.Holo.ListView</item>
+        <item name="listViewWhiteStyle">@android:style/Widget.Holo.ListView.White</item>
+        <item name="popupWindowStyle">@android:style/Widget.Holo.PopupWindow</item>
+        <item name="progressBarStyle">@android:style/Widget.Holo.ProgressBar</item>
+        <item name="progressBarStyleHorizontal">@android:style/Widget.Holo.ProgressBar.Horizontal</item>
+        <item name="progressBarStyleSmall">@android:style/Widget.Holo.ProgressBar.Small</item>
+        <item name="progressBarStyleSmallTitle">@android:style/Widget.Holo.ProgressBar.Small.Title</item>
+        <item name="progressBarStyleLarge">@android:style/Widget.Holo.ProgressBar.Large</item>
+        <item name="progressBarStyleInverse">@android:style/Widget.Holo.ProgressBar.Inverse</item>
+        <item name="progressBarStyleSmallInverse">@android:style/Widget.Holo.ProgressBar.Small.Inverse</item>
+        <item name="progressBarStyleLargeInverse">@android:style/Widget.Holo.ProgressBar.Large.Inverse</item>
+        <item name="seekBarStyle">@android:style/Widget.Holo.SeekBar</item>
+        <item name="ratingBarStyle">@android:style/Widget.Holo.RatingBar</item>
+        <item name="ratingBarStyleIndicator">@android:style/Widget.Holo.RatingBar.Indicator</item>
+        <item name="ratingBarStyleSmall">@android:style/Widget.Holo.RatingBar.Small</item>
+        <item name="radioButtonStyle">@android:style/Widget.Holo.CompoundButton.RadioButton</item>
+        <item name="scrollViewStyle">@android:style/Widget.Holo.ScrollView</item>
+        <item name="horizontalScrollViewStyle">@android:style/Widget.Holo.HorizontalScrollView</item>
+        <item name="spinnerStyle">?android:attr/dropDownSpinnerStyle</item>
+        <item name="dropDownSpinnerStyle">@android:style/Widget.Holo.Spinner.DropDown</item>
+        <item name="starStyle">@android:style/Widget.Holo.CompoundButton.Star</item>
+        <item name="tabWidgetStyle">@android:style/Widget.Holo.TabWidget</item>
+        <item name="textViewStyle">@android:style/Widget.Holo.TextView</item>
+        <item name="webTextViewStyle">@android:style/Widget.Holo.WebTextView</item>
+        <item name="webViewStyle">@android:style/Widget.Holo.WebView</item>
+        <item name="dropDownItemStyle">@android:style/Widget.Holo.DropDownItem</item>
+        <item name="spinnerDropDownItemStyle">@android:style/Widget.Holo.DropDownItem.Spinner</item>
+        <item name="spinnerItemStyle">@android:style/Widget.Holo.TextView.SpinnerItem</item>
+        <item name="dropDownHintAppearance">@android:style/TextAppearance.Holo.Widget.DropDownHint</item>
+        <item name="keyboardViewStyle">@android:style/Widget.Holo.KeyboardView</item>
+        <item name="quickContactBadgeStyleWindowSmall">@android:style/Widget.Holo.QuickContactBadge.WindowSmall</item>
+        <item name="quickContactBadgeStyleWindowMedium">@android:style/Widget.Holo.QuickContactBadge.WindowMedium</item>
+        <item name="quickContactBadgeStyleWindowLarge">@android:style/Widget.Holo.QuickContactBadge.WindowLarge</item>
+        <item name="quickContactBadgeStyleSmallWindowSmall">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowSmall</item>
+        <item name="quickContactBadgeStyleSmallWindowMedium">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowMedium</item>
+        <item name="quickContactBadgeStyleSmallWindowLarge">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowLarge</item>
+        <item name="listPopupWindowStyle">@android:style/Widget.Holo.ListPopupWindow</item>
+        <item name="popupMenuStyle">@android:style/Widget.Holo.PopupMenu</item>
+        
+        <!-- Preference styles -->
+        <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item>
+        <item name="preferenceCategoryStyle">@android:style/Preference.Category</item>
+        <item name="preferenceStyle">@android:style/Preference</item>
+        <item name="preferenceInformationStyle">@android:style/Preference.Information</item>
+        <item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item>
+        <item name="yesNoPreferenceStyle">@android:style/Preference.DialogPreference.YesNoPreference</item>
+        <item name="dialogPreferenceStyle">@android:style/Preference.DialogPreference</item>
+        <item name="editTextPreferenceStyle">@android:style/Preference.DialogPreference.EditTextPreference</item>
+        <item name="ringtonePreferenceStyle">@android:style/Preference.RingtonePreference</item>
+        <item name="preferenceLayoutChild">@android:layout/preference_child</item>
+
+        <!-- Search widget styles -->
+        <item name="searchWidgetCorpusItemBackground">@android:color/search_widget_corpus_item_background</item>
+
+        <!-- Action bar styles -->
+        <item name="actionDropDownStyle">@android:style/Widget.Holo.Spinner.DropDown</item>
+        <item name="actionButtonStyle">@android:style/Widget.Holo.ActionButton</item>
+        <item name="actionOverflowButtonStyle">@android:style/Widget.Holo.ActionButton.Overflow</item>
+        <item name="actionButtonPadding">12dip</item>
+        <item name="actionModeBackground">@android:drawable/cab_holo_light</item>
+        <item name="actionModeCloseDrawable">@android:drawable/cab_ic_close_holo</item>
+        <item name="actionBarTabStyle">@style/Widget.Holo.ActionBarView_TabView</item>
+        <item name="actionBarTabBarStyle">@style/Widget.Holo.ActionBarView_TabBar</item>
+        <item name="actionBarTabTextStyle">@style/Widget.Holo.ActionBarView_TabText</item>
+        <item name="actionModeStyle">@style/Widget.Holo.Light.ActionMode</item>
+        <item name="actionModeCloseButtonStyle">@style/Widget.Holo.ActionButton.CloseMode</item>
+        <item name="actionBarStyle">@android:style/Widget.Holo.ActionBar</item>
+        <item name="actionBarSize">50dip</item>
+
+    </style>
+
+    <!-- Development legacy name; if you're using this, switch. -->
+    <style name="Theme.Light.Holo" parent="Theme.Holo.Light">
+    </style>
+    
+    <!-- Variant of the holographic (dark) theme with no action bar. -->
+    <style name="Theme.Holo.NoActionBar">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+    
+    <!-- Variant of the holographic (dark) theme that has no title bar and fills
+         the entire screen -->
+    <style name="Theme.Holo.NoActionBar.Fullscreen">
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
+
+    <!-- Variant of the holographic light theme with no action bar -->
+    <style name="Theme.Light.Holo.NoActionBar">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <!-- Variant of the holographic light theme that has no title bar and fills
+         the entire screen -->
+    <style name="Theme.Light.Holo.NoActionBar.Fullscreen">
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
+ 
+    <!-- Dialog themes for Holo -->
+
+    <!-- Holo theme for dialog windows and activities, which is used by the
+         {@link android.app.Dialog} class.  This changes the window to be
+         floating (not fill the entire screen), and puts a frame around its
+         contents.  You can set this theme on an activity if you would like to
+         make an activity that looks like a Dialog.
+         This is the default Dialog theme for applications targeting Honeycomb
+         or newer. -->
+    <style name="Theme.Holo.Dialog">
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowTitleStyle">@android:style/DialogWindowTitle.Holo</item>
+        <item name="android:windowBackground">@android:drawable/dialog_full_holo</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.Holo.Dialog</item>
+        <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
+        <item name="android:windowActionBar">false</item>
+
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        
+        <item name="textAppearance">@android:style/TextAppearance.Holo</item>
+        <item name="textAppearanceInverse">@android:style/TextAppearance.Holo.Inverse</item>
+    </style>
+
+    <!-- Variation of Theme.Holo.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.Holo.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>
+
+    <!-- Holo 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.
+         For applications targeting Honeycomb or newer, this is the default
+         AlertDialog theme. -->
+    <style name="Theme.Holo.Dialog.Alert">
+        <item name="windowBackground">@android:color/transparent</item>
+        <item name="windowTitleStyle">@android:style/DialogWindowTitle.Holo</item>
+        <item name="windowContentOverlay">@null</item>
+    </style>
+
 </resources>
diff --git a/core/res/res/xml-land/password_kbd_qwerty.xml b/core/res/res/xml-land/password_kbd_qwerty.xml
index 700c527..fd8bd49 100755
--- a/core/res/res/xml-land/password_kbd_qwerty.xml
+++ b/core/res/res/xml-land/password_kbd_qwerty.xml
@@ -22,7 +22,7 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 
     <Row>
diff --git a/core/res/res/xml-land/password_kbd_qwerty_shifted.xml b/core/res/res/xml-land/password_kbd_qwerty_shifted.xml
index 1e37b6c..9ff6fd7 100755
--- a/core/res/res/xml-land/password_kbd_qwerty_shifted.xml
+++ b/core/res/res/xml-land/password_kbd_qwerty_shifted.xml
@@ -22,7 +22,7 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 
     <Row>
diff --git a/core/res/res/xml-mdpi/password_kbd_qwerty.xml b/core/res/res/xml-mdpi/password_kbd_qwerty.xml
index bae1b42..82a7c75 100755
--- a/core/res/res/xml-mdpi/password_kbd_qwerty.xml
+++ b/core/res/res/xml-mdpi/password_kbd_qwerty.xml
@@ -22,7 +22,7 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 
     <Row>
diff --git a/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml b/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
index 612df9c..9fff3cc 100755
--- a/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
+++ b/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
@@ -22,7 +22,7 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 
     <Row>
diff --git a/core/res/res/xml-xlarge/password_kbd_qwerty.xml b/core/res/res/xml-xlarge/password_kbd_qwerty.xml
new file mode 100755
index 0000000..0a35040
--- /dev/null
+++ b/core/res/res/xml-xlarge/password_kbd_qwerty.xml
@@ -0,0 +1,102 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
+    >
+
+    <Row android:rowEdgeFlags="top">
+        <Key android:keyLabel="1" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="2"/>
+        <Key android:keyLabel="3"/>
+        <Key android:keyLabel="4"/>
+        <Key android:keyLabel="5"/>
+        <Key android:keyLabel="6"/>
+        <Key android:keyLabel="7"/>
+        <Key android:keyLabel="8"/>
+        <Key android:keyLabel="9"/>
+        <Key android:keyLabel="0" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="q" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="w"/>
+        <Key android:keyLabel="e"/>
+        <Key android:keyLabel="r"/>
+        <Key android:keyLabel="t"/>
+        <Key android:keyLabel="y"/>
+        <Key android:keyLabel="u"/>
+        <Key android:keyLabel="i"/>
+        <Key android:keyLabel="o"/>
+        <Key android:keyLabel="p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="a" android:horizontalGap="5%p"
+            android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="s"/>
+        <Key android:keyLabel="d"/>
+        <Key android:keyLabel="f"/>
+        <Key android:keyLabel="g"/>
+        <Key android:keyLabel="h"/>
+        <Key android:keyLabel="j"/>
+        <Key android:keyLabel="k"/>
+        <Key android:keyLabel="l" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+            android:keyWidth="15%p" android:isModifier="true"
+            android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+            android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="z"/>
+        <Key android:keyLabel="x"/>
+        <Key android:keyLabel="c"/>
+        <Key android:keyLabel="v"/>
+        <Key android:keyLabel="b"/>
+        <Key android:keyLabel="n"/>
+        <Key android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+            android:keyWidth="15%p" android:keyEdgeFlags="right"
+            android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+            android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+            android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," />
+        <Key android:keyLabel="-" />
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+            android:iconPreview="@drawable/sym_keyboard_feedback_space"
+            android:keyWidth="20%p"/>
+        <Key android:keyLabel="=" />
+        <Key android:keyLabel="."
+            android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+            android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+            android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml-xlarge/password_kbd_qwerty_shifted.xml b/core/res/res/xml-xlarge/password_kbd_qwerty_shifted.xml
new file mode 100755
index 0000000..9e9db81
--- /dev/null
+++ b/core/res/res/xml-xlarge/password_kbd_qwerty_shifted.xml
@@ -0,0 +1,101 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
+    >
+
+    <Row android:rowEdgeFlags="top">
+        <Key android:keyLabel="\@" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\#"/>
+        <Key android:keyLabel="$"/>
+        <Key android:keyLabel="%"/>
+        <Key android:keyLabel="&amp;"/>
+        <Key android:keyLabel="*"/>
+        <Key android:keyLabel="-"/>
+        <Key android:keyLabel="+"/>
+        <Key android:keyLabel="("/>
+        <Key android:keyLabel=")" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="q" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="w"/>
+        <Key android:keyLabel="e"/>
+        <Key android:keyLabel="r"/>
+        <Key android:keyLabel="t"/>
+        <Key android:keyLabel="y"/>
+        <Key android:keyLabel="u"/>
+        <Key android:keyLabel="i"/>
+        <Key android:keyLabel="o"/>
+        <Key android:keyLabel="p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="a" android:horizontalGap="5%p"
+            android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="s"/>
+        <Key android:keyLabel="d"/>
+        <Key android:keyLabel="f"/>
+        <Key android:keyLabel="g"/>
+        <Key android:keyLabel="h"/>
+        <Key android:keyLabel="j"/>
+        <Key android:keyLabel="k"/>
+        <Key android:keyLabel="l" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+            android:keyWidth="15%p" android:isModifier="true"
+            android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+            android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="z"/>
+        <Key android:keyLabel="x"/>
+        <Key android:keyLabel="c"/>
+        <Key android:keyLabel="v"/>
+        <Key android:keyLabel="b"/>
+        <Key android:keyLabel="n"/>
+        <Key android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+            android:keyWidth="15%p" android:keyEdgeFlags="right"
+            android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+            android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+            android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," />
+        <Key android:keyLabel="_" />
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+            android:iconPreview="@drawable/sym_keyboard_feedback_space"
+            android:keyWidth="20%p"/>
+        <Key android:keyLabel="+" />
+        <Key android:keyLabel="."/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+            android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+            android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml/password_kbd_extension.xml b/core/res/res/xml/password_kbd_extension.xml
index 28b7efe..f3fa57b 100755
--- a/core/res/res/xml/password_kbd_extension.xml
+++ b/core/res/res/xml/password_kbd_extension.xml
@@ -22,7 +22,7 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 
     <Row android:rowEdgeFlags="top">
diff --git a/core/res/res/xml/password_kbd_numeric.xml b/core/res/res/xml/password_kbd_numeric.xml
index e3f1612..2270b8a 100755
--- a/core/res/res/xml/password_kbd_numeric.xml
+++ b/core/res/res/xml/password_kbd_numeric.xml
@@ -19,35 +19,41 @@
 -->
 <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
     android:keyWidth="33.33%p"
-    android:horizontalGap="2px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_numeric"
     >
 
-    <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_popup_template.xml b/core/res/res/xml/password_kbd_popup_template.xml
index 5ddfd3e..9b853e2 100644
--- a/core/res/res/xml/password_kbd_popup_template.xml
+++ b/core/res/res/xml/password_kbd_popup_template.xml
@@ -22,6 +22,6 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 </Keyboard>
diff --git a/core/res/res/xml/password_kbd_qwerty.xml b/core/res/res/xml/password_kbd_qwerty.xml
index 5fa9b8a..0a35040 100755
--- a/core/res/res/xml/password_kbd_qwerty.xml
+++ b/core/res/res/xml/password_kbd_qwerty.xml
@@ -22,7 +22,7 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 
     <Row android:rowEdgeFlags="top">
diff --git a/core/res/res/xml/password_kbd_qwerty_shifted.xml b/core/res/res/xml/password_kbd_qwerty_shifted.xml
index e491aff..9e9db81 100755
--- a/core/res/res/xml/password_kbd_qwerty_shifted.xml
+++ b/core/res/res/xml/password_kbd_qwerty_shifted.xml
@@ -22,7 +22,7 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 
     <Row android:rowEdgeFlags="top">
diff --git a/core/res/res/xml/password_kbd_symbols.xml b/core/res/res/xml/password_kbd_symbols.xml
index 14a7ec8..9a94930 100755
--- a/core/res/res/xml/password_kbd_symbols.xml
+++ b/core/res/res/xml/password_kbd_symbols.xml
@@ -22,10 +22,10 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 
-    <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..a972eb2 100755
--- a/core/res/res/xml/password_kbd_symbols_shift.xml
+++ b/core/res/res/xml/password_kbd_symbols_shift.xml
@@ -22,10 +22,10 @@
     android:keyWidth="10%p"
     android:horizontalGap="0px"
     android:verticalGap="0px"
-    android:keyHeight="@dimen/password_keyboard_key_height"
+    android:keyHeight="@dimen/password_keyboard_key_height_alpha"
     >
 
-    <Row>
+    <Row android:rowEdgeFlags="top">
         <Key android:keyLabel="~" android:keyEdgeFlags="left"/>
         <Key android:keyLabel="`"/>
         <Key android:keyLabel="|"/>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 487a00d..b1e38ee 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1023,7 +1023,13 @@
                 <action android:name="android.accessibilityservice.AccessibilityService" />
             </intent-filter>
         </service>
-        
+
+        <service android:name="android.webkit.AccessibilityInjectorTest$MockAccessibilityService">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService" />
+            </intent-filter>
+        </service>
+
         <activity android:name="android.widget.RadioGroupActivity" android:label="RadioGroupActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
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_im.vcf b/core/tests/coretests/res/raw/v21_im.vcf
deleted file mode 100644
index cc1aabb..0000000
--- a/core/tests/coretests/res/raw/v21_im.vcf
+++ /dev/null
@@ -1,5 +0,0 @@
-BEGIN:VCARD
-VERSION:2.1
-X-ANDROID-CUSTOM:vnd.android.cursor.item/nickname;Nick;1;;;;;;;;;;;;;
-X-GOOGLE-TALK:hhh@gmail.com
-END:VCARD
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_invalid_multiple_line.vcf b/core/tests/coretests/res/raw/v21_invalid_multiple_line.vcf
deleted file mode 100644
index 9c81fd5..0000000
--- a/core/tests/coretests/res/raw/v21_invalid_multiple_line.vcf
+++ /dev/null
@@ -1,7 +0,0 @@
-BEGIN:VCARD
-VERSION:2.1
-N:;Omega;;;
-EMAIL;INTERNET:"Omega"
-  <omega@example.com>
-FN:Omega
-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 f1baf88..0000000
--- a/core/tests/coretests/res/raw/v30_comma_separated.vcf
+++ /dev/null
@@ -1,5 +0,0 @@
-BEGIN:VCARD

-VERSION:3.0

-N;TYPE=PREF,HOME:F;G;M;;

-TEL;TYPE="COMMA,SEPARATED:INSIDE.DQUOTE",PREF:1

-END:VCARD

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

-VERSION:3.0

-N:F;G;M;;

-TEL;TYPE="费":1

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v30_pager.vcf b/core/tests/coretests/res/raw/v30_pager.vcf
deleted file mode 100644
index 98a7f20..0000000
--- a/core/tests/coretests/res/raw/v30_pager.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/res/raw/v40_sort_as.vcf b/core/tests/coretests/res/raw/v40_sort_as.vcf
deleted file mode 100644
index 6f6bc3b..0000000
--- a/core/tests/coretests/res/raw/v40_sort_as.vcf
+++ /dev/null
@@ -1,6 +0,0 @@
-BEGIN:VCARD

-VERSION:4.0

-FN:安藤 ロイド

-N;SORT-AS="あんどう;ろいど":安藤;ロイド;;;

-ORG;TYPE=WORK;SORT-AS="ぐーぐる;けんさくぶもん":グーグル;検索部門

-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/bluetooth/BluetoothStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
index 149685c..d8d9eba 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
@@ -78,50 +78,4 @@
 
         mTestUtils.disable(adapter);
     }
-
-    public void testPair() {
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sHeadsetAddress);
-
-        mTestUtils.enable(adapter);
-        mTestUtils.pair(adapter, device);
-        mTestUtils.unpair(adapter, device);
-        mTestUtils.disable(adapter);
-    }
-
-    public void testConnectA2dp() {
-        int iterations = BluetoothTestRunner.sConnectA2dpIterations;
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sA2dpAddress);
-
-        mTestUtils.enable(adapter);
-        mTestUtils.pair(adapter, device);
-
-        for (int i = 0; i < iterations; i++) {
-            mTestUtils.writeOutput("connectA2dp iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.connectA2dp(adapter, device);
-            mTestUtils.disconnectA2dp(adapter, device);
-        }
-
-        // TODO: Unpair from device if device can accept pairing after unpairing
-        mTestUtils.disable(adapter);
-    }
-
-    public void testConnectHeadset() {
-        int iterations = BluetoothTestRunner.sConnectHeadsetIterations;
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sHeadsetAddress);
-
-        mTestUtils.enable(adapter);
-        mTestUtils.pair(adapter, device);
-
-        for (int i = 0; i < iterations; i++) {
-            mTestUtils.writeOutput("connectHeadset iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.connectHeadset(adapter, device);
-            mTestUtils.disconnectHeadset(adapter, device);
-        }
-
-        // TODO: Unpair from device if device can accept pairing after unpairing
-        mTestUtils.disable(adapter);
-    }
 }
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
index 2e6daa3..cf0ff99 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
@@ -26,11 +26,6 @@
     public static int sEnableIterations = 100;
     public static int sDiscoverableIterations = 1000;
     public static int sScanIterations = 1000;
-    public static int sConnectHeadsetIterations = 100;
-    public static int sConnectA2dpIterations = 100;
-
-    public static String sHeadsetAddress = "";
-    public static String sA2dpAddress = "";
 
     @Override
     public TestSuite getAllTests() {
@@ -74,33 +69,5 @@
                 // Invalid argument, fall back to default value
             }
         }
-
-        val = arguments.getString("connect_a2dp_iterations");
-        if (val != null) {
-            try {
-                sConnectA2dpIterations = Integer.parseInt(val);
-            } catch (NumberFormatException e) {
-                // Invalid argument, fall back to default value
-            }
-        }
-
-        val = arguments.getString("connect_headset_iterations");
-        if (val != null) {
-            try {
-                sConnectHeadsetIterations = Integer.parseInt(val);
-            } catch (NumberFormatException e) {
-                // Invalid argument, fall back to default value
-            }
-        }
-
-        val = arguments.getString("headset_address");
-        if (val != null) {
-            sHeadsetAddress = val;
-        }
-
-        val = arguments.getString("a2dp_address");
-        if (val != null) {
-            sA2dpAddress = val;
-        }
     }
 }
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
index e9311e0..82de509 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
@@ -16,7 +16,6 @@
 
 package android.bluetooth;
 
-import android.bluetooth.BluetoothHeadset.ServiceListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -58,36 +57,6 @@
      */
     private static final int CANCEL_DISCOVERY_TIMEOUT = 5000;
 
-    /**
-     * Timeout for {@link BluetoothDevice#createBond()} in ms.
-     */
-    private static final int PAIR_TIMEOUT = 20000;
-
-    /**
-     * Timeout for {@link BluetoothDevice#removeBond()} in ms.
-     */
-    private static final int UNPAIR_TIMEOUT = 20000;
-
-    /**
-     * Timeout for {@link BluetoothA2dp#connectSink(BluetoothDevice)} in ms.
-     */
-    private static final int CONNECT_A2DP_TIMEOUT = 20000;
-
-    /**
-     * Timeout for {@link BluetoothA2dp#disconnectSink(BluetoothDevice)} in ms.
-     */
-    private static final int DISCONNECT_A2DP_TIMEOUT = 20000;
-
-    /**
-     * Timeout for {@link BluetoothHeadset#connectHeadset(BluetoothDevice)} in ms.
-     */
-    private static final int CONNECT_HEADSET_TIMEOUT = 20000;
-
-    /**
-     * Timeout for {@link BluetoothHeadset#disconnectHeadset(BluetoothDevice)} in ms.
-     */
-    private static final int DISCONNECT_HEADSET_TIMEOUT = 20000;
-
     private static final int DISCOVERY_STARTED_FLAG = 1;
     private static final int DISCOVERY_FINISHED_FLAG = 1 << 1;
     private static final int SCAN_MODE_NONE_FLAG = 1 << 2;
@@ -97,23 +66,6 @@
     private static final int STATE_TURNING_ON_FLAG = 1 << 6;
     private static final int STATE_ON_FLAG = 1 << 7;
     private static final int STATE_TURNING_OFF_FLAG = 1 << 8;
-    private static final int PAIR_STATE_FLAG = 1 << 9;
-    private static final int PROFILE_A2DP_FLAG = 1 << 10;
-    private static final int PROFILE_HEADSET_FLAG = 1 << 11;
-
-    private static final int PAIR_STATE_BONDED = 1;
-    private static final int PAIR_STATE_BONDING = 1 << 1;
-    private static final int PAIR_STATE_NONE = 1 << 2;
-
-    private static final int A2DP_STATE_DISCONNECTED = 1;
-    private static final int A2DP_STATE_CONNECTING = 1 << 1;
-    private static final int A2DP_STATE_CONNECTED = 1 << 2;
-    private static final int A2DP_STATE_DISCONNECTING = 1 << 3;
-    private static final int A2DP_STATE_PLAYING = 1 << 4;
-
-    private static final int HEADSET_STATE_DISCONNECTED = 1;
-    private static final int HEADSET_STATE_CONNECTING = 1 << 1;
-    private static final int HEADSET_STATE_CONNECTED = 1 << 2;
 
     /**
      * Time between polls in ms.
@@ -124,43 +76,11 @@
 
     private BufferedWriter mOutputWriter;
 
-    private BluetoothA2dp mA2dp;
-
-    private BluetoothHeadset mHeadset;
-
     private String mOutputFile;
     private String mTag;
-    private class HeadsetServiceListener implements ServiceListener {
-        private boolean mConnected = false;
-
-        @Override
-        public void onServiceConnected() {
-            synchronized (this) {
-                mConnected = true;
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected() {
-            synchronized (this) {
-                mConnected = false;
-            }
-        }
-
-        public boolean isConnected() {
-            synchronized (this) {
-                return mConnected;
-            }
-        }
-    }
-
-    private HeadsetServiceListener mHeadsetServiceListener = new HeadsetServiceListener();
 
     private class BluetoothReceiver extends BroadcastReceiver {
         private int mFiredFlags = 0;
-        private int mPairFiredFlags = 0;
-        private int mA2dpFiredFlags = 0;
-        private int mHeadsetFiredFlags = 0;
 
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -202,58 +122,6 @@
                             mFiredFlags |= STATE_TURNING_OFF_FLAG;
                             break;
                     }
-                } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
-                    mFiredFlags |= PAIR_STATE_FLAG;
-                    int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
-                    assertNotSame(state, -1);
-                    switch (state) {
-                        case BluetoothDevice.BOND_BONDED:
-                            mPairFiredFlags |= PAIR_STATE_BONDED;
-                            break;
-                        case BluetoothDevice.BOND_BONDING:
-                            mPairFiredFlags |= PAIR_STATE_BONDING;
-                            break;
-                        case BluetoothDevice.BOND_NONE:
-                            mPairFiredFlags |= PAIR_STATE_NONE;
-                            break;
-                    }
-                } else if (BluetoothA2dp.ACTION_SINK_STATE_CHANGED.equals(intent.getAction())) {
-                    mFiredFlags |= PROFILE_A2DP_FLAG;
-                    int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, -1);
-                    assertNotSame(state, -1);
-                    switch (state) {
-                        case BluetoothA2dp.STATE_DISCONNECTED:
-                            mA2dpFiredFlags |= A2DP_STATE_DISCONNECTED;
-                            break;
-                        case BluetoothA2dp.STATE_CONNECTING:
-                            mA2dpFiredFlags |= A2DP_STATE_CONNECTING;
-                            break;
-                        case BluetoothA2dp.STATE_CONNECTED:
-                            mA2dpFiredFlags |= A2DP_STATE_CONNECTED;
-                            break;
-                        case BluetoothA2dp.STATE_DISCONNECTING:
-                            mA2dpFiredFlags |= A2DP_STATE_DISCONNECTING;
-                            break;
-                        case BluetoothA2dp.STATE_PLAYING:
-                            mA2dpFiredFlags |= A2DP_STATE_PLAYING;
-                            break;
-                    }
-                } else if (BluetoothHeadset.ACTION_STATE_CHANGED.equals(intent.getAction())) {
-                    mFiredFlags |= PROFILE_HEADSET_FLAG;
-                    int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                            BluetoothHeadset.STATE_ERROR);
-                    assertNotSame(state, BluetoothHeadset.STATE_ERROR);
-                    switch (state) {
-                        case BluetoothHeadset.STATE_DISCONNECTED:
-                            mHeadsetFiredFlags |= HEADSET_STATE_DISCONNECTED;
-                            break;
-                        case BluetoothHeadset.STATE_CONNECTING:
-                            mHeadsetFiredFlags |= HEADSET_STATE_CONNECTING;
-                            break;
-                        case BluetoothHeadset.STATE_CONNECTED:
-                            mHeadsetFiredFlags |= HEADSET_STATE_CONNECTED;
-                            break;
-                    }
                 }
             }
         }
@@ -264,30 +132,9 @@
             }
         }
 
-        public int getPairFiredFlags() {
-            synchronized (this) {
-                return mPairFiredFlags;
-            }
-        }
-
-        public int getA2dpFiredFlags() {
-            synchronized (this) {
-                return mA2dpFiredFlags;
-            }
-        }
-
-        public int getHeadsetFiredFlags() {
-            synchronized (this) {
-                return mHeadsetFiredFlags;
-            }
-        }
-
         public void resetFiredFlags() {
             synchronized (this) {
                 mFiredFlags = 0;
-                mPairFiredFlags = 0;
-                mA2dpFiredFlags = 0;
-                mHeadsetFiredFlags = 0;
             }
         }
     }
@@ -572,351 +419,6 @@
 
     }
 
-    public void pair(BluetoothAdapter adapter, BluetoothDevice device) {
-        int mask = PAIR_STATE_FLAG;
-        int pairMask = PAIR_STATE_BONDING | PAIR_STATE_BONDED;
-        mReceiver.resetFiredFlags();
-
-        if (!adapter.isEnabled()) {
-            fail("pair() bluetooth not enabled");
-        }
-
-        int state = device.getBondState();
-        switch (state) {
-            case BluetoothDevice.BOND_BONDED:
-                assertTrue(adapter.getBondedDevices().contains(device));
-                return;
-            case BluetoothDevice.BOND_BONDING:
-                // Don't check for received intents since we might have missed them.
-                mask = pairMask = 0;
-                break;
-            case BluetoothDevice.BOND_NONE:
-                assertFalse(adapter.getBondedDevices().contains(device));
-                assertTrue(device.createBond());
-                break;
-            default:
-                fail("pair() invalide state: state=" + state);
-        }
-
-        long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < PAIR_TIMEOUT) {
-            state = device.getBondState();
-            if (state == BluetoothDevice.BOND_BONDED) {
-                assertTrue(adapter.getBondedDevices().contains(device));
-                if ((mReceiver.getFiredFlags() & mask) == mask
-                        && (mReceiver.getPairFiredFlags() & pairMask) == pairMask) {
-                    writeOutput(String.format("pair() completed in %d ms: device=%s",
-                            (System.currentTimeMillis() - s), device));
-                    return;
-                }
-            }
-            sleep(POLL_TIME);
-        }
-
-        int firedFlags = mReceiver.getFiredFlags();
-        int pairFiredFlags = mReceiver.getPairFiredFlags();
-        mReceiver.resetFiredFlags();
-        fail(String.format("pair() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), "
-                + "pairFlags=0x%x (expected 0x%x)", state, BluetoothDevice.BOND_BONDED, firedFlags,
-                mask, pairFiredFlags, pairMask));
-    }
-
-    public void unpair(BluetoothAdapter adapter, BluetoothDevice device) {
-        int mask = PAIR_STATE_FLAG;
-        int pairMask = PAIR_STATE_NONE;
-        mReceiver.resetFiredFlags();
-
-        if (!adapter.isEnabled()) {
-            fail("unpair() bluetooth not enabled");
-        }
-
-        int state = device.getBondState();
-        switch (state) {
-            case BluetoothDevice.BOND_BONDED:
-                assertTrue(adapter.getBondedDevices().contains(device));
-                assertTrue(device.removeBond());
-                break;
-            case BluetoothDevice.BOND_BONDING:
-                assertTrue(device.removeBond());
-                break;
-            case BluetoothDevice.BOND_NONE:
-                assertFalse(adapter.getBondedDevices().contains(device));
-                return;
-            default:
-                fail("unpair() invalid state: state=" + state);
-        }
-
-        assertTrue(device.removeBond());
-
-        long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < UNPAIR_TIMEOUT) {
-            if (device.getBondState() == BluetoothDevice.BOND_NONE) {
-                assertFalse(adapter.getBondedDevices().contains(device));
-                if ((mReceiver.getFiredFlags() & mask) == mask
-                        && (mReceiver.getPairFiredFlags() & pairMask) == pairMask) {
-                    writeOutput(String.format("unpair() completed in %d ms: device=%s",
-                            (System.currentTimeMillis() - s), device));
-                    return;
-                }
-            }
-        }
-
-        int firedFlags = mReceiver.getFiredFlags();
-        int pairFiredFlags = mReceiver.getPairFiredFlags();
-        mReceiver.resetFiredFlags();
-        fail(String.format("unpair() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), "
-                + "pairFlags=0x%x (expected 0x%x)", state, BluetoothDevice.BOND_BONDED, firedFlags,
-                mask, pairFiredFlags, pairMask));
-    }
-
-    public void connectA2dp(BluetoothAdapter adapter, BluetoothDevice device) {
-        int mask = PROFILE_A2DP_FLAG;
-        int a2dpMask1 = A2DP_STATE_CONNECTING | A2DP_STATE_CONNECTED | A2DP_STATE_PLAYING;
-        int a2dpMask2 = a2dpMask1 ^ A2DP_STATE_CONNECTED;
-        int a2dpMask3 = a2dpMask1 ^ A2DP_STATE_PLAYING;
-        mReceiver.resetFiredFlags();
-
-        if (!adapter.isEnabled()) {
-            fail("connectA2dp() bluetooth not enabled");
-        }
-
-        if (!adapter.getBondedDevices().contains(device)) {
-            fail("connectA2dp() device not paired: device=" + device);
-        }
-
-        int state = mA2dp.getSinkState(device);
-        switch (state) {
-            case BluetoothA2dp.STATE_CONNECTED:
-            case BluetoothA2dp.STATE_PLAYING:
-                assertTrue(mA2dp.isSinkConnected(device));
-                return;
-            case BluetoothA2dp.STATE_DISCONNECTING:
-            case BluetoothA2dp.STATE_DISCONNECTED:
-                assertFalse(mA2dp.isSinkConnected(device));
-                assertTrue(mA2dp.connectSink(device));
-                break;
-            case BluetoothA2dp.STATE_CONNECTING:
-                assertFalse(mA2dp.isSinkConnected(device));
-                // Don't check for received intents since we might have missed them.
-                mask = a2dpMask1 = a2dpMask2 = a2dpMask3 = 0;
-                break;
-            default:
-                fail("connectA2dp() invalid state: state=" + state);
-        }
-
-        long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < CONNECT_A2DP_TIMEOUT) {
-            state = mA2dp.getSinkState(device);
-            if (state == BluetoothA2dp.STATE_CONNECTED || state == BluetoothA2dp.STATE_PLAYING) {
-                assertTrue(mA2dp.isSinkConnected(device));
-                // Check whether STATE_CONNECTING and (STATE_CONNECTED or STATE_PLAYING) intents
-                // have fired if we are checking if intents should be fired.
-                int firedFlags = mReceiver.getFiredFlags();
-                int a2dpFiredFlags = mReceiver.getA2dpFiredFlags();
-                if ((mReceiver.getFiredFlags() & mask) == mask
-                        && ((a2dpFiredFlags & a2dpMask1) == a2dpMask1
-                                || (a2dpFiredFlags & a2dpMask2) == a2dpMask2
-                                || (a2dpFiredFlags & a2dpMask3) == a2dpMask3)) {
-                    mReceiver.resetFiredFlags();
-                    writeOutput(String.format("connectA2dp() completed in %d ms: device=%s",
-                            (System.currentTimeMillis() - s), device));
-                    return;
-                }
-            }
-            sleep(POLL_TIME);
-        }
-
-        int firedFlags = mReceiver.getFiredFlags();
-        int a2dpFiredFlags = mReceiver.getA2dpFiredFlags();
-        mReceiver.resetFiredFlags();
-        fail(String.format("connectA2dp() timeout: state=%d (expected %d or %d), "
-                + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x or 0x%x or 0x%x)",
-                state, BluetoothHeadset.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING, firedFlags,
-                mask, a2dpFiredFlags, a2dpMask1, a2dpMask2, a2dpMask3));
-    }
-
-    public void disconnectA2dp(BluetoothAdapter adapter, BluetoothDevice device) {
-        int mask = PROFILE_A2DP_FLAG;
-        int a2dpMask = A2DP_STATE_DISCONNECTING | A2DP_STATE_DISCONNECTED;
-        mReceiver.resetFiredFlags();
-
-        if (!adapter.isEnabled()) {
-            fail("disconnectA2dp() bluetooth not enabled");
-        }
-
-        if (!adapter.getBondedDevices().contains(device)) {
-            fail("disconnectA2dp() device not paired: device=" + device);
-        }
-
-        int state = mA2dp.getSinkState(device);
-        switch (state) {
-            case BluetoothA2dp.STATE_DISCONNECTED:
-                assertFalse(mA2dp.isSinkConnected(device));
-                return;
-            case BluetoothA2dp.STATE_CONNECTED:
-            case BluetoothA2dp.STATE_PLAYING:
-                assertTrue(mA2dp.isSinkConnected(device));
-                assertTrue(mA2dp.disconnectSink(device));
-                break;
-            case BluetoothA2dp.STATE_CONNECTING:
-                assertFalse(mA2dp.isSinkConnected(device));
-                assertTrue(mA2dp.disconnectSink(device));
-                break;
-            case BluetoothA2dp.STATE_DISCONNECTING:
-                assertFalse(mA2dp.isSinkConnected(device));
-                // Don't check for received intents since we might have missed them.
-                mask = a2dpMask = 0;
-                break;
-            default:
-                fail("disconnectA2dp() invalid state: state=" + state);
-        }
-
-        long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < DISCONNECT_A2DP_TIMEOUT) {
-            state = mA2dp.getSinkState(device);
-            if (state == BluetoothA2dp.STATE_DISCONNECTED) {
-                assertFalse(mA2dp.isSinkConnected(device));
-                if ((mReceiver.getFiredFlags() & mask) == mask
-                        && (mReceiver.getA2dpFiredFlags() & a2dpMask) == a2dpMask) {
-                    mReceiver.resetFiredFlags();
-                    writeOutput(String.format("disconnectA2dp() completed in %d ms: device=%s",
-                            (System.currentTimeMillis() - s), device));
-                    return;
-                }
-            }
-            sleep(POLL_TIME);
-        }
-
-        int firedFlags = mReceiver.getFiredFlags();
-        int a2dpFiredFlags = mReceiver.getA2dpFiredFlags();
-        mReceiver.resetFiredFlags();
-        fail(String.format("disconnectA2dp() timeout: state=%d (expected %d), "
-                + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x)", state,
-                BluetoothA2dp.STATE_DISCONNECTED, firedFlags, mask, a2dpFiredFlags, a2dpMask));
-    }
-
-    public void connectHeadset(BluetoothAdapter adapter, BluetoothDevice device) {
-        int mask = PROFILE_HEADSET_FLAG;
-        int headsetMask = HEADSET_STATE_CONNECTING | HEADSET_STATE_CONNECTED;
-        mReceiver.resetFiredFlags();
-
-        if (!adapter.isEnabled()) {
-            fail("connectHeadset() bluetooth not enabled");
-        }
-
-        if (!adapter.getBondedDevices().contains(device)) {
-            fail("connectHeadset() device not paired: device=" + device);
-        }
-
-        while (!mHeadsetServiceListener.isConnected()) {
-            sleep(POLL_TIME);
-        }
-
-        int state = mHeadset.getState(device);
-        switch (state) {
-            case BluetoothHeadset.STATE_CONNECTED:
-                assertTrue(mHeadset.isConnected(device));
-                return;
-            case BluetoothHeadset.STATE_DISCONNECTED:
-                assertFalse(mHeadset.isConnected(device));
-                mHeadset.connectHeadset(device);
-                break;
-            case BluetoothHeadset.STATE_CONNECTING:
-                assertFalse(mHeadset.isConnected(device));
-                // Don't check for received intents since we might have missed them.
-                mask = headsetMask = 0;
-                break;
-            case BluetoothHeadset.STATE_ERROR:
-                fail("connectHeadset() error state");
-                break;
-            default:
-                fail("connectHeadset() invalid state: state=" + state);
-        }
-
-        long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < CONNECT_HEADSET_TIMEOUT) {
-            state = mHeadset.getState(device);
-            if (state == BluetoothHeadset.STATE_CONNECTED) {
-                assertTrue(mHeadset.isConnected(device));
-                if ((mReceiver.getFiredFlags() & mask) == mask
-                        && (mReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) {
-                    mReceiver.resetFiredFlags();
-                    writeOutput(String.format("connectHeadset() completed in %d ms: device=%s",
-                            (System.currentTimeMillis() - s), device));
-                    return;
-                }
-            }
-            sleep(POLL_TIME);
-        }
-
-        int firedFlags = mReceiver.getFiredFlags();
-        int headsetFiredFlags = mReceiver.getHeadsetFiredFlags();
-        mReceiver.resetFiredFlags();
-        fail(String.format("connectHeadset() timeout: state=%d (expected %d), "
-                + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state,
-                BluetoothHeadset.STATE_CONNECTED, firedFlags, mask, headsetFiredFlags,
-                headsetMask));
-    }
-
-    public void disconnectHeadset(BluetoothAdapter adapter, BluetoothDevice device) {
-        int mask = PROFILE_HEADSET_FLAG;
-        int headsetMask = HEADSET_STATE_DISCONNECTED;
-        mReceiver.resetFiredFlags();
-
-        if (!adapter.isEnabled()) {
-            fail("disconnectHeadset() bluetooth not enabled");
-        }
-
-        if (!adapter.getBondedDevices().contains(device)) {
-            fail("disconnectHeadset() device not paired: device=" + device);
-        }
-
-        while (!mHeadsetServiceListener.isConnected()) {
-            sleep(POLL_TIME);
-        }
-
-        int state = mHeadset.getState(device);
-        switch (state) {
-            case BluetoothHeadset.STATE_CONNECTED:
-                mHeadset.disconnectHeadset(device);
-                break;
-            case BluetoothHeadset.STATE_CONNECTING:
-                mHeadset.disconnectHeadset(device);
-                break;
-            case BluetoothHeadset.STATE_DISCONNECTED:
-                return;
-            case BluetoothHeadset.STATE_ERROR:
-                fail("disconnectHeadset() error state");
-                break;
-            default:
-                fail("disconnectHeadset() invalid state: state=" + state);
-        }
-
-        long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < DISCONNECT_HEADSET_TIMEOUT) {
-            state = mHeadset.getState(device);
-            if (state == BluetoothHeadset.STATE_DISCONNECTED) {
-                assertFalse(mHeadset.isConnected(device));
-                if ((mReceiver.getFiredFlags() & mask) == mask
-                        && (mReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) {
-                    mReceiver.resetFiredFlags();
-                    writeOutput(String.format("disconnectHeadset() completed in %d ms: device=%s",
-                            (System.currentTimeMillis() - s), device));
-                    return;
-                }
-            }
-            sleep(POLL_TIME);
-        }
-
-        int firedFlags = mReceiver.getFiredFlags();
-        int headsetFiredFlags = mReceiver.getHeadsetFiredFlags();
-        mReceiver.resetFiredFlags();
-        fail(String.format("disconnectHeadset() timeout: state=%d (expected %d), "
-                + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state,
-                BluetoothHeadset.STATE_DISCONNECTED, firedFlags, mask, headsetFiredFlags,
-                headsetMask));
-    }
-
     public void writeOutput(String s) {
         Log.i(mTag, s);
         if (mOutputWriter == null) {
diff --git a/core/tests/coretests/src/android/content/MemoryFileProvider.java b/core/tests/coretests/src/android/content/MemoryFileProvider.java
index c4bc767..73530d7 100644
--- a/core/tests/coretests/src/android/content/MemoryFileProvider.java
+++ b/core/tests/coretests/src/android/content/MemoryFileProvider.java
@@ -16,16 +16,11 @@
 
 package android.content;
 
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.UriMatcher;
-import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
+import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.net.Uri;
-import android.os.MemoryFile;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
@@ -134,64 +129,34 @@
     }
 
     @Override
-    public AssetFileDescriptor openAssetFile(Uri url, String mode) throws FileNotFoundException {
+    public ParcelFileDescriptor openFile(Uri url, String mode) throws FileNotFoundException {
         int match = sURLMatcher.match(url);
         switch (match) {
             case DATA_ID_BLOB:
                 String sql = "SELECT _blob FROM data WHERE _id=" + url.getPathSegments().get(1);
-                return getBlobColumnAsAssetFile(url, mode, sql);
+                return getBlobColumnAsFile(url, mode, sql);
             case HUGE:
                 try {
-                    MemoryFile memoryFile = new MemoryFile(null, 5000000);
-                    memoryFile.writeBytes(TEST_BLOB, 0, 1000000, TEST_BLOB.length);
-                    memoryFile.deactivate();
-                    return AssetFileDescriptor.fromMemoryFile(memoryFile);
+                    return ParcelFileDescriptor.fromData(TEST_BLOB, null);
                 } catch (IOException ex) {
                     throw new FileNotFoundException("Error reading " + url + ":" + ex.toString());
                 }
             case FILE:
                 File file = getContext().getFileStreamPath(DATA_FILE);
-                ParcelFileDescriptor fd =
-                        ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
-                return new AssetFileDescriptor(fd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
+                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
             default:
                 throw new FileNotFoundException("No files supported by provider at " + url);
         }
     }
 
-    private AssetFileDescriptor getBlobColumnAsAssetFile(Uri url, String mode, String sql)
+    private ParcelFileDescriptor getBlobColumnAsFile(Uri url, String mode, String sql)
             throws FileNotFoundException {
         if (!"r".equals(mode)) {
             throw new FileNotFoundException("Mode " + mode + " not supported for " + url);
         }
-        try {
-            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-            MemoryFile file = simpleQueryForBlobMemoryFile(db, sql);
-            if (file == null) throw new FileNotFoundException("No such entry: " + url);
-            AssetFileDescriptor afd = AssetFileDescriptor.fromMemoryFile(file);
-            file.deactivate();
-            // need to dup and then close? openFileHelper() doesn't do that though
-            return afd;
-        } catch (IOException ex) {
-            throw new FileNotFoundException("Error reading " + url + ":" + ex.toString());
-        }
-    }
 
-    private MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql) throws IOException {
-        Cursor cursor = db.rawQuery(sql, null);
-        try {
-            if (!cursor.moveToFirst()) {
-                return null;
-            }
-            byte[] bytes = cursor.getBlob(0);
-            MemoryFile file = new MemoryFile(null, bytes.length);
-            file.writeBytes(bytes, 0, 0, bytes.length);
-            return file;
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        return DatabaseUtils.blobFileDescriptorForQuery(db, sql, null);
     }
 
     @Override
diff --git a/core/tests/coretests/src/android/content/MemoryFileProviderTest.java b/core/tests/coretests/src/android/content/MemoryFileProviderTest.java
index 62b4e7e..bbe7c10 100644
--- a/core/tests/coretests/src/android/content/MemoryFileProviderTest.java
+++ b/core/tests/coretests/src/android/content/MemoryFileProviderTest.java
@@ -56,7 +56,6 @@
             Uri uri = Uri.parse("content://android.content.MemoryFileProvider/huge");
             InputStream in = resolver.openInputStream(uri);
             assertNotNull("Failed to open stream number " + i, in);
-            assertEquals(1000000, in.skip(1000000));
             byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length];
             int count = in.read(buf);
             assertEquals(buf.length, count);
diff --git a/core/tests/coretests/src/android/content/SyncQueueTest.java b/core/tests/coretests/src/android/content/SyncQueueTest.java
deleted file mode 100644
index 1da59d1..0000000
--- a/core/tests/coretests/src/android/content/SyncQueueTest.java
+++ /dev/null
@@ -1,164 +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;
-
-import android.test.AndroidTestCase;
-import android.test.RenamingDelegatingContext;
-import android.test.mock.MockContext;
-import android.test.mock.MockContentResolver;
-import android.accounts.Account;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-public class SyncQueueTest extends AndroidTestCase {
-    private static final Account ACCOUNT1 = new Account("test.account1", "test.type1");
-    private static final Account ACCOUNT2 = new Account("test.account2", "test.type2");
-    private static final String AUTHORITY1 = "test.authority1";
-    private static final String AUTHORITY2 = "test.authority2";
-    private static final String AUTHORITY3 = "test.authority3";
-
-    private SyncStorageEngine mSettings;
-    private SyncQueue mSyncQueue;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        MockContentResolver mockResolver = new MockContentResolver();
-        mSettings = SyncStorageEngine.newTestInstance(new TestContext(mockResolver, getContext()));
-        mSyncQueue = new SyncQueue(mSettings);
-    }
-
-    public void testSyncQueueOrder() throws Exception {
-        final SyncOperation op1 = new SyncOperation(
-                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
-        final SyncOperation op2 = new SyncOperation(
-                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
-        final SyncOperation op3 = new SyncOperation(
-                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
-        final SyncOperation op4 = new SyncOperation(
-                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("4"), 60);
-        final SyncOperation op5 = new SyncOperation(
-                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
-        final SyncOperation op6 = new SyncOperation(
-                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
-        op6.expedited = true;
-
-        mSyncQueue.add(op1);
-        mSyncQueue.add(op2);
-        mSyncQueue.add(op3);
-        mSyncQueue.add(op4);
-        mSyncQueue.add(op5);
-        mSyncQueue.add(op6);
-
-        long now = SystemClock.elapsedRealtime() + 200;
-
-        assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op6);
-
-        assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op1);
-
-        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op4);
-
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op5);
-
-        assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op2);
-
-        assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op3);
-    }
-
-    public void testOrderWithBackoff() throws Exception {
-        final SyncOperation op1 = new SyncOperation(
-                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
-        final SyncOperation op2 = new SyncOperation(
-                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
-        final SyncOperation op3 = new SyncOperation(
-                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
-        final SyncOperation op4 = new SyncOperation(
-                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY3, newTestBundle("4"), 60);
-        final SyncOperation op5 = new SyncOperation(
-                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
-        final SyncOperation op6 = new SyncOperation(
-                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
-        op6.expedited = true;
-
-        mSyncQueue.add(op1);
-        mSyncQueue.add(op2);
-        mSyncQueue.add(op3);
-        mSyncQueue.add(op4);
-        mSyncQueue.add(op5);
-        mSyncQueue.add(op6);
-
-        long now = SystemClock.elapsedRealtime() + 200;
-
-        assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op6);
-
-        assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op1);
-
-        mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, now + 200, 5);
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-
-        mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
-        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
-
-        mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, now + 200);
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-
-        mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, 0);
-        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op4);
-
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op5);
-
-        assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op2);
-
-        assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op3);
-    }
-
-    Bundle newTestBundle(String val) {
-        Bundle bundle = new Bundle();
-        bundle.putString("test", val);
-        return bundle;
-    }
-
-    static class TestContext extends ContextWrapper {
-        ContentResolver mResolver;
-
-        public TestContext(ContentResolver resolver, Context realContext) {
-            super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
-            mResolver = resolver;
-        }
-
-        @Override
-        public void enforceCallingOrSelfPermission(String permission, String message) {
-        }
-
-        @Override
-        public ContentResolver getContentResolver() {
-            return mResolver;
-        }
-    }
-}
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/DatabaseErrorHandlerTest.java b/core/tests/coretests/src/android/database/DatabaseErrorHandlerTest.java
new file mode 100644
index 0000000..48d25b9
--- /dev/null
+++ b/core/tests/coretests/src/android/database/DatabaseErrorHandlerTest.java
@@ -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.
+ */
+
+package android.database;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+public class DatabaseErrorHandlerTest extends AndroidTestCase {
+
+    private SQLiteDatabase mDatabase;
+    private File mDatabaseFile;
+    private static final String DB_NAME = "database_test.db";
+    private File dbDir;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+        mDatabaseFile = new File(dbDir, DB_NAME);
+        if (mDatabaseFile.exists()) {
+            mDatabaseFile.delete();
+        }
+        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null,
+                new MyDatabaseCorruptionHandler());
+        assertNotNull(mDatabase);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDatabase.close();
+        mDatabaseFile.delete();
+        super.tearDown();
+    }
+
+    public void testNoCorruptionCase() {
+        new MyDatabaseCorruptionHandler().onCorruption(mDatabase);
+        // database file should still exist
+        assertTrue(mDatabaseFile.exists());
+    }
+
+    public void testDatabaseIsCorrupt() throws IOException {
+        mDatabase.execSQL("create table t (i int);");
+        // write junk into the database file
+        BufferedWriter writer = new BufferedWriter(new FileWriter(mDatabaseFile.getPath()));
+        writer.write("blah");
+        writer.close();
+        assertTrue(mDatabaseFile.exists());
+        // since the database file is now corrupt, doing any sql on this database connection
+        // should trigger call to MyDatabaseCorruptionHandler.onCorruption
+        try {
+            mDatabase.execSQL("select * from t;");
+            fail("expected exception");
+        } catch (SQLiteException e) {
+            // expected
+        }
+        // after corruption handler is called, the database file should be free of
+        // database corruption
+        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null,
+                new MyDatabaseCorruptionHandler());
+        assertTrue(db.isDatabaseIntegrityOk());
+    }
+
+    /**
+     * An example implementation of {@link DatabaseErrorHandler} to demonstrate
+     * database corruption handler which checks to make sure database is indeed
+     * corrupt before deleting the file.
+     */
+    public class MyDatabaseCorruptionHandler implements DatabaseErrorHandler {
+        public void onCorruption(SQLiteDatabase dbObj) {
+            boolean databaseOk = dbObj.isDatabaseIntegrityOk();
+            // close the database
+            try {
+                dbObj.close();
+            } catch (SQLiteException e) {
+                /* ignore */
+            }
+            if (databaseOk) {
+                // database is just fine. no need to delete the database file
+                Log.e("MyDatabaseCorruptionHandler", "no corruption in the database: " +
+                        mDatabaseFile.getPath());
+            } else {
+                // database is corrupt. delete the database file
+                Log.e("MyDatabaseCorruptionHandler", "deleting the database file: " +
+                        mDatabaseFile.getPath());
+                new File(dbDir, DB_NAME).delete();
+            }
+        }
+    }
+}
\ No newline at end of file
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/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..f6b1d04
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+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());
+    }
+
+    /**
+     * this test could take a while to execute. so, designate it as LargetTest
+     */
+    @LargeTest
+    public void testFillWindow() {
+        // create schema
+        final String testTable = "testV";
+        mDatabase.beginTransaction();
+        mDatabase.execSQL("CREATE TABLE " + testTable + " (col1 int, desc text not null);");
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+
+        // populate the table with data
+        // create a big string that will almost fit a page but not quite.
+        // since sqlite wants to make sure each row is in a page, this string will allocate
+        // a new database page for each row.
+        StringBuilder buff = new StringBuilder();
+        for (int i = 0; i < 500; i++) {
+            buff.append(i % 10 + "");
+        }
+        ContentValues values = new ContentValues();
+        values.put("desc", buff.toString());
+
+        // insert more than 1MB of data in the table. this should ensure that the entire tabledata
+        // will need more than one CursorWindow
+        int N = 5000;
+        Set<Integer> rows = new HashSet<Integer>();
+        mDatabase.beginTransaction();
+        for (int j = 0; j < N; j++) {
+            values.put("col1", j);
+            mDatabase.insert(testTable, null, values);
+            rows.add(j); // store in a hashtable so we can verify the results from cursor later on
+        }
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+        assertEquals(N, rows.size());
+        Cursor c1 = mDatabase.rawQuery("select * from " + testTable, null);
+        assertEquals(N, c1.getCount());
+        c1.close();
+
+        // scroll through ALL data in the table using a cursor. should cause multiple calls to
+        // native_fill_window (and re-fills of the CursorWindow object)
+        Cursor c = mDatabase.query(testTable, new String[]{"col1", "desc"},
+                null, null, null, null, null);
+        int i = 0;
+        while (c.moveToNext()) {
+            int val = c.getInt(0);
+            assertTrue(rows.contains(val));
+            assertTrue(rows.remove(val));
+        }
+        // did I see all the rows in the table?
+        assertTrue(rows.isEmpty());
+
+        // change data and make sure the cursor picks up new data & count
+        rows = new HashSet<Integer>();
+        mDatabase.beginTransaction();
+        int M = N + 1000;
+        for (int j = 0; j < M; j++) {
+            rows.add(j);
+            if (j < N) {
+                continue;
+            }
+            values.put("col1", j);
+            mDatabase.insert(testTable, null, values);
+        }
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+        assertEquals(M, rows.size());
+        c.requery();
+        i = 0;
+        while (c.moveToNext()) {
+            int val = c.getInt(0);
+            assertTrue(rows.contains(val));
+            assertTrue(rows.remove(val));
+        }
+        // did I see all data from the modified table
+        assertTrue(rows.isEmpty());
+
+        // move cursor back to 1st row and scroll to about halfway in the result set
+        // and then delete 75% of data - and then do requery
+        c.moveToFirst();
+        int K = N / 2;
+        for (int p = 0; p < K && c.moveToNext(); p++) {
+            // nothing to do - just scrolling to about half-point in the resultset
+        }
+        mDatabase.beginTransaction();
+        mDatabase.delete(testTable, "col1 < ?", new String[]{ (3 * M / 4) + ""});
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+        c.requery();
+        assertEquals(M / 4, c.getCount());
+        while (c.moveToNext()) {
+            // just move the cursor to next row - to make sure it can go through the entire
+            // resultset without any problems
+        }
+        c.close();
+    }
+}
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..39258ae
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -0,0 +1,926 @@
+/*
+ * 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.DatabaseErrorHandler;
+import android.database.DatabaseUtils;
+import android.database.DefaultDatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+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;
+    private static final String DB_NAME = "database_test.db";
+
+    @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, DB_NAME);
+        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();
+    }
+
+    /**
+     * 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.mConnectionPool.setMaxPoolSize(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 transaction - 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 committing the data, do rollback and make sure the data seen by the query
+     * within the transaction is now gone.
+     */
+    @SmallTest
+    public void testTransactionAndWalInterplay3() {
+        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();
+    }
+
+    /**
+     * http://b/issue?id=2943028
+     * SQLiteOpenHelper maintains a Singleton even if it is in bad state.
+     */
+    @SmallTest
+    public void testCloseAndReopen() {
+        mDatabase.close();
+        TestOpenHelper helper = new TestOpenHelper(getContext(), DB_NAME, null,
+                CURRENT_DATABASE_VERSION, new DefaultDatabaseErrorHandler());
+        mDatabase = helper.getWritableDatabase();
+        createTableAndClearCache();
+        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+        Cursor c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
+        assertEquals(1, c.getCount());
+        c.close();
+        mDatabase.close();
+        assertFalse(mDatabase.isOpen());
+        mDatabase = helper.getReadableDatabase();
+        assertTrue(mDatabase.isOpen());
+        c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
+        assertEquals(1, c.getCount());
+        c.close();
+    }
+    private class TestOpenHelper extends SQLiteOpenHelper {
+        public TestOpenHelper(Context context, String name, CursorFactory factory, int version,
+                DatabaseErrorHandler errorHandler) {
+            super(context, name, factory, version, errorHandler);
+        }
+        @Override public void onCreate(SQLiteDatabase db) {}
+        @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
+    }
+}
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/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
new file mode 100644
index 0000000..955336a
--- /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.
+     */
+    @LargeTest
+    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/database/sqlite/SQLiteUnfinalizedExceptionTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java
new file mode 100644
index 0000000..cd2005d
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 java.io.File;
+
+public class SQLiteUnfinalizedExceptionTest 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, "UnfinalizedExceptionTest.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();
+    }
+
+    @SmallTest
+    public void testUnfinalizedExceptionNotExcpected() {
+        mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
+        // the above statement should be in SQLiteDatabase.mPrograms
+        // and should automatically be finalized when database is closed
+        mDatabase.lock();
+        try {
+            mDatabase.closeDatabase();
+        } finally {
+            mDatabase.unlock();
+        }
+    }
+
+    @SmallTest
+    public void testUnfinalizedException() {
+        mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
+        mDatabase.lock();
+        mDatabase.closePendingStatements(); // clears the above from finalizer queue in mdatabase
+        mDatabase.unlock();
+        ClassToTestSqlCompilationAndCaching.create(mDatabase, "select * from "  + TABLE_NAME);
+        // since the above is NOT closed, closing database should fail
+        mDatabase.lock();
+        try {
+            mDatabase.closeDatabase();
+            fail("exception expected");
+        } catch (SQLiteUnfinalizedObjectsException e) {
+            // expected
+        } finally {
+            mDatabase.unlock();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/net/LinkSocketTest.java b/core/tests/coretests/src/android/net/LinkSocketTest.java
new file mode 100644
index 0000000..af77d63
--- /dev/null
+++ b/core/tests/coretests/src/android/net/LinkSocketTest.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.net;
+
+import android.net.LinkSocket;
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
+/**
+ * Test LinkSocket
+ */
+public class LinkSocketTest extends TestCase {
+
+    @SmallTest
+    public void testBasic() throws Exception {
+        LinkSocket ls;
+
+        ls = new LinkSocket();
+        ls.close();
+    }
+
+    @SmallTest
+    public void testLinkCapabilities() throws Exception {
+        LinkCapabilities lc;
+
+        lc = new LinkCapabilities();
+        assertEquals(0, lc.size());
+        assertEquals(true, lc.isEmpty());
+    }
+}
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index a5fda20..c8ad60d 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -24,6 +24,9 @@
 
 import java.io.File;
 import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
 
 public class UriTest extends TestCase {
 
@@ -52,20 +55,20 @@
 
     private void parcelAndUnparcel(Uri u) {
         Parcel p = Parcel.obtain();
-	try {
-		Uri.writeToParcel(p, u);
-		p.setDataPosition(0);
-		assertEquals(u, Uri.CREATOR.createFromParcel(p));
+        try {
+            Uri.writeToParcel(p, u);
+            p.setDataPosition(0);
+            assertEquals(u, Uri.CREATOR.createFromParcel(p));
 
-		p.setDataPosition(0);
-		u = u.buildUpon().build();        
-		Uri.writeToParcel(p, u);
-		p.setDataPosition(0);
-		assertEquals(u, Uri.CREATOR.createFromParcel(p));
-	}
-	finally {
-		p.recycle();
-	}
+            p.setDataPosition(0);
+            u = u.buildUpon().build();
+            Uri.writeToParcel(p, u);
+            p.setDataPosition(0);
+            assertEquals(u, Uri.CREATOR.createFromParcel(p));
+        }
+        finally {
+            p.recycle();
+        }
     }
 
     @SmallTest
@@ -282,6 +285,14 @@
         assertEquals("d", uri.getQueryParameter("c"));
     }
 
+    // http://b/2337042
+    @SmallTest
+    public void testHostWithTrailingDot() {
+        Uri uri = Uri.parse("http://google.com./b/c/g");
+        assertEquals("google.com.", uri.getHost());
+        assertEquals("/b/c/g", uri.getPath());
+    }
+
     @SmallTest
     public void testSchemeOnly() {
         Uri uri = Uri.parse("empty:");
@@ -603,4 +614,122 @@
         assertEquals("", uri.getQueryParameter("b"));
         assertEquals("", uri.getQueryParameter("c"));
     }
+
+    public void testGetQueryParameterEmptyKey() {
+        Uri uri = Uri.parse("http://www.google.com/?=b");
+        assertEquals("b", uri.getQueryParameter(""));
+    }
+
+    public void testGetQueryParameterEmptyKey2() {
+      Uri uri = Uri.parse("http://www.google.com/?a=b&&c=d");
+      assertEquals("", uri.getQueryParameter(""));
+    }
+
+    public void testGetQueryParameterEmptyKey3() {
+      Uri uri = Uri.parse("http://www.google.com?");
+      assertEquals("", uri.getQueryParameter(""));
+    }
+
+    public void testGetQueryParameterEmptyKey4() {
+      Uri uri = Uri.parse("http://www.google.com?a=b&");
+      assertEquals("", uri.getQueryParameter(""));
+    }
+
+    public void testGetQueryParametersEmptyKey() {
+        Uri uri = Uri.parse("http://www.google.com/?=b&");
+        List<String> values = uri.getQueryParameters("");
+        assertEquals(2, values.size());
+        assertEquals("b", values.get(0));
+        assertEquals("", values.get(1));
+    }
+
+    public void testGetQueryParametersEmptyKey2() {
+        Uri uri = Uri.parse("http://www.google.com?");
+        List<String> values = uri.getQueryParameters("");
+        assertEquals(1, values.size());
+        assertEquals("", values.get(0));
+    }
+
+    public void testGetQueryParametersEmptyKey3() {
+      Uri uri = Uri.parse("http://www.google.com/?a=b&&c=d");
+      List<String> values = uri.getQueryParameters("");
+      assertEquals(1, values.size());
+      assertEquals("", values.get(0));
+    }
+
+    public void testGetQueryParameterNames() {
+        Uri uri = Uri.parse("http://test?a=1");
+        Set<String> names = uri.getQueryParameterNames();
+        assertEquals(1, names.size());
+        assertEquals("a", names.iterator().next());
+    }
+
+    public void testGetQueryParameterNamesEmptyKey() {
+        Uri uri = Uri.parse("http://www.google.com/?a=x&&c=z");
+        Set<String> names = uri.getQueryParameterNames();
+        Iterator<String> iter = names.iterator();
+        assertEquals(3, names.size());
+        assertEquals("a", iter.next());
+        assertEquals("", iter.next());
+        assertEquals("c", iter.next());
+    }
+
+    public void testGetQueryParameterNamesEmptyKey2() {
+        Uri uri = Uri.parse("http://www.google.com/?a=x&=d&c=z");
+        Set<String> names = uri.getQueryParameterNames();
+        Iterator<String> iter = names.iterator();
+        assertEquals(3, names.size());
+        assertEquals("a", iter.next());
+        assertEquals("", iter.next());
+        assertEquals("c", iter.next());
+    }
+
+    public void testGetQueryParameterNamesEmptyValues() {
+        Uri uri = Uri.parse("http://www.google.com/?a=foo&b=&c=");
+        Set<String> names = uri.getQueryParameterNames();
+        Iterator<String> iter = names.iterator();
+        assertEquals(3, names.size());
+        assertEquals("a", iter.next());
+        assertEquals("b", iter.next());
+        assertEquals("c", iter.next());
+    }
+
+    public void testGetQueryParameterNamesEdgeCases() {
+        Uri uri = Uri.parse("http://foo?a=bar&b=bar&c=&&d=baz&e&f&g=buzz&&&a&b=bar&h");
+        Set<String> names = uri.getQueryParameterNames();
+        Iterator<String> iter = names.iterator();
+        assertEquals(9, names.size());
+        assertEquals("a", iter.next());
+        assertEquals("b", iter.next());
+        assertEquals("c", iter.next());
+        assertEquals("", iter.next());
+        assertEquals("d", iter.next());
+        assertEquals("e", iter.next());
+        assertEquals("f", iter.next());
+        assertEquals("g", iter.next());
+        assertEquals("h", iter.next());
+    }
+
+    public void testGetQueryParameterNamesEscapedKeys() {
+        Uri uri = Uri.parse("http://www.google.com/?a%20b=foo&c%20d=");
+        Set<String> names = uri.getQueryParameterNames();
+        assertEquals(2, names.size());
+        Iterator<String> iter = names.iterator();
+        assertEquals("a b", iter.next());
+        assertEquals("c d", iter.next());
+    }
+
+    public void testGetQueryParameterEscapedKeys() {
+        Uri uri = Uri.parse("http://www.google.com/?a%20b=foo&c%20d=");
+        String value = uri.getQueryParameter("a b");
+        assertEquals("foo", value);
+    }
+    
+    public void testClearQueryParameters() {
+        Uri uri = Uri.parse("http://www.google.com/?a=x&b=y&c=z").buildUpon()
+            .clearQuery().appendQueryParameter("foo", "bar").build();
+        Set<String> names = uri.getQueryParameterNames();
+        assertEquals(1, names.size());
+        assertEquals("foo", names.iterator().next());
+    }
 }
diff --git a/core/tests/coretests/src/android/net/WebAddressTest.java b/core/tests/coretests/src/android/net/WebAddressTest.java
new file mode 100644
index 0000000..f0af35d
--- /dev/null
+++ b/core/tests/coretests/src/android/net/WebAddressTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.net.WebAddress;
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
+public class WebAddressTest extends TestCase {
+
+    // http://b/2337042
+    @SmallTest
+    public void testHostWithTrailingDot() {
+        WebAddress webAddress = new WebAddress("http://google.com./b/c/g");
+        assertEquals("google.com.", webAddress.mHost);
+        assertEquals("/b/c/g", webAddress.mPath);
+    }
+
+    // http://b/1011602
+    @SmallTest
+    public void testPathWithoutLeadingSlash() {
+        WebAddress webAddress = new WebAddress("http://www.myspace.com?si=1");
+        assertEquals("www.myspace.com", webAddress.mHost);
+        assertEquals("/?si=1", webAddress.mPath);
+    }
+}
diff --git a/core/tests/coretests/src/android/os/MemoryFileTest.java b/core/tests/coretests/src/android/os/MemoryFileTest.java
index 009a0e2..e627bb4 100644
--- a/core/tests/coretests/src/android/os/MemoryFileTest.java
+++ b/core/tests/coretests/src/android/os/MemoryFileTest.java
@@ -237,51 +237,6 @@
         }
     }
 
-    @SmallTest
-    public void testIsMemoryFile() throws Exception {
-        MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
-        FileDescriptor fd = file.getFileDescriptor();
-        assertNotNull(fd);
-        assertTrue(fd.valid());
-        assertTrue(MemoryFile.isMemoryFile(fd));
-        file.close();
-
-        assertFalse(MemoryFile.isMemoryFile(FileDescriptor.in));
-        assertFalse(MemoryFile.isMemoryFile(FileDescriptor.out));
-        assertFalse(MemoryFile.isMemoryFile(FileDescriptor.err));
-
-        File tempFile = File.createTempFile("MemoryFileTest",".tmp", getContext().getFilesDir());
-        assertNotNull(file);
-        FileOutputStream out = null;
-        try {
-            out = new FileOutputStream(tempFile);
-            FileDescriptor fileFd = out.getFD();
-            assertNotNull(fileFd);
-            assertFalse(MemoryFile.isMemoryFile(fileFd));
-        } finally {
-            if (out != null) {
-                out.close();
-            }
-            tempFile.delete();
-        }
-    }
-
-    @SmallTest
-    public void testFileDescriptor() throws Exception {
-        MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
-        MemoryFile ref = new MemoryFile(file.getFileDescriptor(), file.length(), "r");
-        byte[] buffer;
-
-        // write to original, read from reference
-        file.writeBytes(testString, 0, 2000, testString.length);
-        buffer = new byte[testString.length];
-        ref.readBytes(buffer, 2000, 0, testString.length);
-        compareBuffers(testString, buffer, testString.length);
-
-        file.close();
-        ref.close();  // Doesn't actually do anything, since the file descriptor is not dup(2):ed
-    }
-
     private static final byte[] testString = new byte[] {
         3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3, 2, 7, 9, 5, 0, 2, 8, 8, 4, 1, 9, 7, 1, 6, 9, 3, 9, 9, 3, 7, 5, 1, 0, 5, 8, 2, 0, 9, 7, 4, 9, 4, 4, 5, 9, 2, 3, 0, 7, 8, 1, 6, 4,
         0, 6, 2, 8, 6, 2, 0, 8, 9, 9, 8, 6, 2, 8, 0, 3, 4, 8, 2, 5, 3, 4, 2, 1, 1, 7, 0, 6, 7, 9, 8, 2, 1, 4, 8, 0, 8, 6, 5, 1, 3, 2, 8, 2, 3, 0, 6, 6, 4, 7, 0, 9, 3, 8, 4, 4, 6, 0, 9, 5, 5, 0, 5, 8, 2, 2, 3, 1, 7, 2,
diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java
index b7c2d1f..f82bfce 100644
--- a/core/tests/coretests/src/android/os/MessageQueueTest.java
+++ b/core/tests/coretests/src/android/os/MessageQueueTest.java
@@ -41,6 +41,10 @@
         }
 
         public void handleMessage(Message msg) {
+            if (!msg.isInUse()) {
+                failure(new RuntimeException(
+                        "msg.isInuse is false, should always be true, #" + msg.what));
+            }
             if (mCount <= mLastMessage) {
                 if (msg.what != mCount) {
                     failure(new RuntimeException(
@@ -99,5 +103,174 @@
 
         tester.doTest(1000);
     }
-}
 
+    private static class TestFieldIntegrityHandler extends TestHandlerThread {
+        Handler mHandler;
+        int mLastMessage;
+        int mCount;
+
+        public TestFieldIntegrityHandler() {
+        }
+
+        public void go() {
+            mHandler = new Handler() {
+                public void handleMessage(Message msg) {
+                    TestFieldIntegrityHandler.this.handleMessage(msg);
+                }
+            };
+        }
+
+        public void handleMessage(Message msg) {
+            if (!msg.isInUse()) {
+                failure(new RuntimeException(
+                        "msg.isInuse is false, should always be true, #" + msg.what));
+            }
+            if (mCount <= mLastMessage) {
+                if (msg.what != mCount) {
+                    failure(new RuntimeException(
+                            "Expected message #" + mCount
+                                    + ", received #" + msg.what));
+                } else if (mCount == mLastMessage) {
+                    success();
+                }
+                mCount++;
+            } else {
+                failure(new RuntimeException(
+                        "Message received after done, #" + msg.what));
+            }
+        }
+    }
+
+    @MediumTest
+    public void testFieldIntegrity() throws Exception {
+
+        TestHandlerThread tester = new TestFieldIntegrityHandler() {
+            Bundle mBundle;
+
+            public void go() {
+                super.go();
+                mLastMessage = 1;
+                mCount = 0;
+                mHandler.sendMessage(mHandler.obtainMessage(0));
+            }
+
+            public void handleMessage(Message msg) {
+                super.handleMessage(msg);
+                if (msg.what == 0) {
+                    msg.flags = -1;
+                    msg.what = 1;
+                    msg.arg1 = 456;
+                    msg.arg2 = 789;
+                    msg.obj = this;
+                    msg.replyTo = null;
+                    mBundle = new Bundle();
+                    msg.data = mBundle;
+                    msg.data.putString("key", "value");
+
+                    Message newMsg = mHandler.obtainMessage();
+                    newMsg.copyFrom(msg);
+                    if (newMsg.isInUse() != false) {
+                        failure(new RuntimeException(
+                                "newMsg.isInUse is true should be false after copyFrom"));
+                    }
+                    if (newMsg.flags != 0) {
+                        failure(new RuntimeException(String.format(
+                        "newMsg.flags is %d should be 0 after copyFrom", newMsg.flags)));
+                    }
+                    if (newMsg.what != 1) {
+                        failure(new RuntimeException(String.format(
+                                "newMsg.what is %d should be %d after copyFrom", newMsg.what, 1)));
+                    }
+                    if (newMsg.arg1 != 456) {
+                        failure(new RuntimeException(String.format(
+                                "newMsg.arg1 is %d should be %d after copyFrom", msg.arg1, 456)));
+                    }
+                    if (newMsg.arg2 != 789) {
+                        failure(new RuntimeException(String.format(
+                                "newMsg.arg2 is %d should be %d after copyFrom", msg.arg2, 789)));
+                    }
+                    if (newMsg.obj != this) {
+                        failure(new RuntimeException(
+                                "newMsg.obj should be 'this' after copyFrom"));
+                    }
+                    if (newMsg.replyTo != null) {
+                        failure(new RuntimeException(
+                                "newMsg.replyTo should be null after copyFrom"));
+                    }
+                    if (newMsg.data == mBundle) {
+                        failure(new RuntimeException(
+                                "newMsg.data should NOT be mBundle after copyFrom"));
+                    }
+                    if (!newMsg.data.getString("key").equals(mBundle.getString("key"))) {
+                        failure(new RuntimeException(String.format(
+                                "newMsg.data.getString(\"key\") is %s and does not equal" +
+                                " mBundle.getString(\"key\") which is %s after copyFrom",
+                                newMsg.data.getString("key"),  mBundle.getString("key"))));
+                    }
+                    if (newMsg.when != 0) {
+                        failure(new RuntimeException(String.format(
+                                "newMsg.when is %d should be 0 after copyFrom", newMsg.when)));
+                    }
+                    if (newMsg.target != mHandler) {
+                        failure(new RuntimeException(
+                                "newMsg.target is NOT mHandler after copyFrom"));
+                    }
+                    if (newMsg.callback != null) {
+                        failure(new RuntimeException(
+                                "newMsg.callback is NOT null after copyFrom"));
+                    }
+
+                    mHandler.sendMessage(newMsg);
+                } else if (msg.what == 1) {
+                    if (msg.isInUse() != true) {
+                        failure(new RuntimeException(String.format(
+                                "msg.isInUse is false should be true after when processing %d",
+                                msg.what)));
+                    }
+                    if (msg.arg1 != 456) {
+                        failure(new RuntimeException(String.format(
+                                "msg.arg1 is %d should be %d when processing # %d",
+                                msg.arg1, 456, msg.what)));
+                    }
+                    if (msg.arg2 != 789) {
+                        failure(new RuntimeException(String.format(
+                                "msg.arg2 is %d should be %d when processing # %d",
+                                msg.arg2, 789, msg.what)));
+                    }
+                    if (msg.obj != this) {
+                        failure(new RuntimeException(String.format(
+                                "msg.obj should be 'this' when processing # %d", msg.what)));
+                    }
+                    if (msg.replyTo != null) {
+                        failure(new RuntimeException(String.format(
+                                "msg.replyTo should be null when processing # %d", msg.what)));
+                    }
+                    if (!msg.data.getString("key").equals(mBundle.getString("key"))) {
+                        failure(new RuntimeException(String.format(
+                                "msg.data.getString(\"key\") is %s and does not equal" +
+                                " mBundle.getString(\"key\") which is %s when processing # %d",
+                                msg.data.getString("key"),  mBundle.getString("key"), msg.what)));
+                    }
+                    if (msg.when != 0) {
+                        failure(new RuntimeException(String.format(
+                                "msg.when is %d should be 0 when processing # %d",
+                                msg.when, msg.what)));
+                    }
+                    if (msg.target != null) {
+                        failure(new RuntimeException(String.format(
+                                "msg.target is NOT null when processing # %d", msg.what)));
+                    }
+                    if (msg.callback != null) {
+                        failure(new RuntimeException(String.format(
+                                "msg.callback is NOT null when processing # %d", msg.what)));
+                    }
+                } else {
+                    failure(new RuntimeException(String.format(
+                            "Unexpected msg.what is %d" + msg.what)));
+                }
+            }
+        };
+
+        tester.doTest(1000);
+    }
+}
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 2bec462..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
+++ /dev/null
@@ -1,1223 +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.test_utils.ContactEntry;
-import android.pim.vcard.test_utils.PropertyNodesVerifierElem;
-import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
-import android.pim.vcard.test_utils.VCardTestsBase;
-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 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) {
-        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.DISPLAY_NAME, "DISPLAY NAME");
-
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("N",
-                        "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                        + "AppropriatePrefix;AppropriateSuffix",
-                        Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                                "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
-                .addExpectedNodeWithOrder("FN", "DISPLAY NAME");
-    }
-
-    public void testStructuredNameBasicV21() {
-        testStructuredNameBasic(V21);
-    }
-
-    public void testStructuredNameBasicV30() {
-        testStructuredNameBasic(V30);
-    }
-
-    public void testStructuredNameBasicV40() {
-        testStructuredNameBasic(V40);
-    }
-
-    /**
-     * 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) {
-        mVerifier.initForExportTest(vcardType);
-        final 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.DISPLAY_NAME, "DoNotEmitDisplayName1");
-
-        // 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.DISPLAY_NAME, "AppropriateDisplayName")
-                .put(StructuredName.IS_PRIMARY, 1);
-
-        // With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first.
-        // vCard 2.1 does not specify anything about the number of N properties. We choose not
-        // emitting this property.
-        // vCard 3.0 does (There must be one N property)
-        // vCard 4.0 (rev13) does (cardinality (0, 1)).
-        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.DISPLAY_NAME, "DoNotEmitDisplayName2")
-                .put(StructuredName.IS_PRIMARY, 1);
-
-       mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("N",
-                        "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                        + "AppropriatePrefix;AppropriateSuffix",
-                        Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                                "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
-                .addExpectedNodeWithOrder("FN", "AppropriateDisplayName");
-    }
-
-    public void testStructuredNameUsePrimaryV21() {
-        testStructuredNameUsePrimaryCommon(V21);
-    }
-
-    public void testStructuredNameUsePrimaryV30() {
-        testStructuredNameUsePrimaryCommon(V30);
-    }
-
-    public void testStructuredNameUsePrimaryV40() {
-        testStructuredNameUsePrimaryCommon(V40);
-    }
-
-    /**
-     * Tests that only "super primary" StructuredName is emitted.
-     * See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}.
-     */
-    private void testStructuredNameUseSuperPrimaryCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        final 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.DISPLAY_NAME, "DoNotEmitDisplay1");
-
-        // 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.DISPLAY_NAME, "DoNotEmitDisplay2")
-                .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.DISPLAY_NAME, "AppropriateDisplayName")
-                .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.DISPLAY_NAME, "DoNotEmitDisplay3")
-                .put(StructuredName.IS_PRIMARY, 1);
-
-        final PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem();
-        elem.addExpectedNodeWithOrder("N",
-                "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                + "AppropriatePrefix;AppropriateSuffix",
-                Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                        "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"));
-
-        elem.addExpectedNodeWithOrder("FN", "AppropriateDisplayName");
-    }
-
-    public void testStructuredNameUseSuperPrimaryV21() {
-        testStructuredNameUseSuperPrimaryCommon(V21);
-    }
-
-    public void testStructuredNameUseSuperPrimaryV30() {
-        testStructuredNameUseSuperPrimaryCommon(V30);
-    }
-
-    public void testStructuredNameUseSuperPrimaryV40() {
-        testStructuredNameUseSuperPrimaryCommon(V40);
-    }
-
-    /**
-     * Tests phonetic names field are handled correctly.
-     *
-     * vCard 2.1 does not have any field corresponding to them.
-     * vCard 3.0 has SORT-STRING property, which does not support multiple values inside it.
-     * vCard 4.0 (rev13) has SORT-AS parameter, which has three values (family, given, middle)
-     * inside it.
-     */
-    private void testStructuredNamePhoneticNameCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        final ContactEntry entry = mVerifier.addInputEntry();
-        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.DISPLAY_NAME, "AppropriateDisplayName")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
-
-        final PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem();
-        if (VCardConfig.isVersion40(vcardType)) {
-            final ContentValues contentValues = new ContentValues();
-            contentValues.put("SORT-AS",
-                    "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName");
-            // vCard 4.0 (rev13) now uses SORT-AS parameter, which is not compatible with
-            // either 2.1 nor 3.0.
-            elem.addExpectedNodeWithOrder("N",
-                    "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                    + "AppropriatePrefix;AppropriateSuffix",
-                    Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                            "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"),
-                    contentValues);
-        } else {
-            elem.addExpectedNodeWithOrder("N",
-                    "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                    + "AppropriatePrefix;AppropriateSuffix",
-                    Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                            "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"));
-            if (VCardConfig.isVersion30(vcardType)) {
-                elem.addExpectedNode("SORT-STRING",
-                        "AppropriatePhoneticGiven AppropriatePhoneticMiddle"
-                        + " AppropriatePhoneticFamily");
-            }
-        }
-
-        elem.addExpectedNodeWithOrder("FN", "AppropriateDisplayName")
-            .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
-            .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
-            .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
-    }
-
-    public void testStructuredNamePhoneticNameV21() {
-        testStructuredNamePhoneticNameCommon(V21);
-    }
-
-    public void testStructuredNamePhoneticNameV30() {
-        testStructuredNamePhoneticNameCommon(V30);
-    }
-
-    public void testStructuredNamePhoneticNameV40() {
-        testStructuredNamePhoneticNameCommon(V40);
-    }
-
-    // TODO: need to add test cases confirming escaping, empty values, etc.
-
-    /**
-     * Confirms all the other sides of the handling is correctly interpreted at one time.
-     *
-     * A kind of regression test for StructuredName handling.
-     */
-    private void testStructuredNameComplicatedCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        final 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);
-
-        final PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem();
-        if (VCardConfig.isVersion40(vcardType)) {
-            final ContentValues contentValues = new ContentValues();
-            contentValues.put("SORT-AS",
-                    "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName");
-            // vCard 4.0 (rev13) now uses SORT-AS parameter, which is not compatible with
-            // either 2.1 nor 3.0.
-            elem.addExpectedNodeWithOrder("N",
-                    "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                    + "AppropriatePrefix;AppropriateSuffix",
-                    Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                            "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"),
-                    contentValues);
-        } else {
-            elem.addExpectedNodeWithOrder("N",
-                    "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                    + "AppropriatePrefix;AppropriateSuffix",
-                    Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                            "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"));
-            if (VCardConfig.isVersion30(vcardType)) {
-                elem.addExpectedNode("SORT-STRING",
-                        "AppropriatePhoneticGiven AppropriatePhoneticMiddle"
-                        + " AppropriatePhoneticFamily");
-            }
-        }
-
-        elem.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");
-    }
-
-    public void testStructuredNameComplicatedV21() {
-        testStructuredNameComplicatedCommon(V21);
-    }
-
-    public void testStructuredNameComplicatedV30() {
-        testStructuredNameComplicatedCommon(V30);
-    }
-
-    public void testStructuredNameComplicatedV40() {
-        testStructuredNameComplicatedCommon(V40);
-    }
-
-    public void testNickNameV30() {
-        mVerifier.initForExportTest(V30);
-        mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
-                .put(Nickname.NAME, "Nicky");
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-            .addExpectedNodeWithOrder("NICKNAME", "Nicky");
-    }
-
-    public void testNickNameV40() {
-        mVerifier.initForExportTest(V40);
-        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 testPhoneBasicV40() {
-        testPhoneBasicCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testPhoneVariousTypeSupportV40() {
-        testPhoneVariousTypeSupport(V40);
-    }
-
-    /**
-     * 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);
-    }
-
-    public void testPhonePrefHandlingV40() {
-        testPhonePrefHandlingCommon(V40);
-    }
-
-    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();
-        if (VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType)) {
-            // vCard 3.0 accepts "invalid". Also stop using toUpper()
-            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("invalid"));
-        } else {
-            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);
-    }
-
-    public void testPhoneTypeHandlingV40() {
-        testMiscPhoneTypeHandling(V40);
-    }
-
-    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);
-    }
-
-    public void testEmailBasicV40() {
-        testEmailBasicCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testEmailVariousTypeSupportV40() {
-        testEmailVariousTypeSupportCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testEmailPrefHandlingV40() {
-        testEmailPrefHandlingCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testPostalAddressV40() {
-        testPostalAddressCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testPostalAddressNonNeighborhoodV40() {
-        testPostalAddressNonNeighborhood(V40);
-    }
-
-    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);
-    }
-
-    public void testPostalAddressNonCityV40() {
-        testPostalAddressNonCity(V40);
-    }
-
-    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);
-    }
-
-    public void testPostalOnlyWithFormattedAddressV40() {
-        testPostalOnlyWithFormattedAddressCommon(V40);
-    }
-
-    /**
-     * 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);
-    }
-
-    public void testPostalWithBothStructuredAndFormattedV40() {
-        testPostalWithBothStructuredAndFormattedCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testOrganizationV40() {
-        testOrganizationCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testImBasicV40() {
-        testImVariousTypeSupportCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testImPrefHandlingV40() {
-        testImPrefHandlingCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testWebsiteV40() {
-        testWebsiteCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testEventV40() {
-        testEventCommon(V40);
-    }
-
-    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);
-    }
-
-    public void testNoteV40() {
-        testNoteCommon(V40);
-    }
-
-    private void testPhotoCommon(int vcardType) {
-        final boolean useB =
-            (VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType));
-        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", (useB ? "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);
-    }
-
-    public void testPhotoV40() {
-        testPhotoCommon(V40);
-    }
-
-    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.
-                .put(StructuredName.DISPLAY_NAME, "display");
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.IS_PRIMARY, 1)
-                .put(StructuredName.FAMILY_NAME, "family2")  // This entry is what we want.
-                .put(StructuredName.DISPLAY_NAME, "display");
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.IS_PRIMARY, 1)
-                .put(StructuredName.FAMILY_NAME, "family3")
-                .put(StructuredName.DISPLAY_NAME, "display");
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "family4")
-                .put(StructuredName.DISPLAY_NAME, "display");
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("N", Arrays.asList("family2", "", "", "", ""))
-                .addExpectedNode("FN", "display");
-    }
-
-    public void testPickUpNonEmptyContentValuesV21() {
-        testPickUpNonEmptyContentValuesCommon(V21);
-    }
-
-    public void testPickUpNonEmptyContentValuesV30() {
-        testPickUpNonEmptyContentValuesCommon(V30);
-    }
-
-    public void testPickUpNonEmptyContentValuesV40() {
-        testPickUpNonEmptyContentValuesCommon(V40);
-    }
-
-    public void testUseMultiByteTypeV30() {
-        mVerifier.initForExportTest(V30);
-        final ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "\u96FB\u8A71")
-                .put(Phone.NUMBER, "1");
-        mVerifier.addLineVerifierElem()
-                .addExpected("N:")
-                .addExpected("FN:")
-                .addExpected("TEL;TYPE=\u96FB\u8A71:1");
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "1", new TypeSet("\u96FB\u8A71"));
-    }
-}
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 09e2914..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
+++ /dev/null
@@ -1,1108 +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 com.android.frameworks.coretests.R;
-
-import android.content.ContentValues;
-import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
-import android.pim.vcard.test_utils.VCardTestsBase;
-import android.pim.vcard.test_utils.ContentValuesVerifier;
-import android.pim.vcard.test_utils.ContentValuesVerifierElem;
-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.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.provider.ContactsContract.Data;
-
-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.addPropertyNodesVerifierElemWithoutVersion()  // no "VERSION:2.1" line.
-                .addExpectedNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", ""));
-    }
-
-    public void testV21SimpleCase1_Type_Generic() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, 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, 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, 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("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("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 testInvalidMultipleLineV21() {
-        mVerifier.initForImportTest(V21, R.raw.v21_invalid_multiple_line);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.GIVEN_NAME, "Omega")
-                .put(StructuredName.DISPLAY_NAME, "Omega");
-        elem.addExpected(Email.CONTENT_ITEM_TYPE)
-                .put(Email.TYPE, Email.TYPE_CUSTOM)
-                .put(Email.LABEL, "INTERNET")
-                .put(Email.ADDRESS, "\"Omega\" <omega@example.com>");
-    }
-
-    public void testV30Simple_Parsing() {
-        mVerifier.initForImportTest(V30, R.raw.v30_simple);
-        mVerifier.addPropertyNodesVerifierElem()
-                .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, R.raw.v21_japanese_1);
-        mVerifier.addPropertyNodesVerifierElem()
-                .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 com.android.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC}.
-     */
-    public void testV21Japanese1_Type_Generic_Utf8() {
-        testV21Japanese1Common(
-                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC, false);
-    }
-
-    /**
-     * Verifies vCard with Japanese can be parsed correctly with
-     * {@link com.android.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
-     */
-    public void testV21Japanese1_Type_Japanese_Sjis() {
-        testV21Japanese1Common(
-                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
-    }
-
-    /**
-     * Verifies vCard with Japanese can be parsed correctly with
-     * {@link com.android.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
-     * since vCard 2.1 specifies the charset of each line if it contains non-Ascii.
-     */
-    public void testV21Japanese1_Type_Japanese_Utf8() {
-        testV21Japanese1Common(
-                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
-    }
-
-    public void testV21Japanese2_Parsing() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_2);
-        mVerifier.addPropertyNodesVerifierElem()
-                .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, R.raw.v21_multiple_entry);
-        mVerifier.addPropertyNodesVerifierElem()
-                .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("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("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, 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("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");
-        elem.addExpected(Event.CONTENT_ITEM_TYPE)
-                .put(Event.TYPE, Event.TYPE_ANNIVERSARY)
-                .put(Event.START_DATE, "20091010");
-    }
-
-    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_pager);
-        mVerifier.addPropertyNodesVerifierElem()
-                .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_pager);
-        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");
-    }
-
-    public void testMultiBytePropV30_Parse() {
-        mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
-                .addExpectedNodeWithOrder("TEL", "1", new TypeSet("\u8D39"));
-    }
-
-    public void testMultiBytePropV30() {
-        mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param);
-        final 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_CUSTOM)
-                .put(Phone.LABEL, "\u8D39")
-                .put(Phone.NUMBER, "1");
-    }
-
-    public void testCommaSeparatedParamsV30_Parse() {
-        mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""),
-                        new TypeSet("PREF", "HOME"))
-                .addExpectedNodeWithOrder("TEL", "1",
-                        new TypeSet("COMMA,SEPARATED:INSIDE.DQUOTE", "PREF"));
-    }
-
-    public void testSortAsV40_Parse() {
-        mVerifier.initForImportTest(V40, R.raw.v40_sort_as);
-
-        final ContentValues contentValuesForSortAsN = new ContentValues();
-        contentValuesForSortAsN.put("SORT-AS",
-                "\u3042\u3093\u3069\u3046;\u308D\u3044\u3069");
-        final ContentValues contentValuesForSortAsOrg = new ContentValues();
-        contentValuesForSortAsOrg.put("SORT-AS",
-                "\u3050\u30FC\u3050\u308B;\u3051\u3093\u3055\u304F\u3076\u3082\u3093");
-
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9")
-                .addExpectedNodeWithOrder("N",
-                        Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9", "", "", ""),
-                        contentValuesForSortAsN)
-                .addExpectedNodeWithOrder("ORG",
-                        Arrays.asList("\u30B0\u30FC\u30B0\u30EB", "\u691C\u7D22\u90E8\u9580"),
-                        contentValuesForSortAsOrg, new TypeSet("WORK"));
-    }
-
-    public void testSortAsV40() {
-        mVerifier.initForImportTest(V40, R.raw.v40_sort_as);
-        final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4")
-                .put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9")
-                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3042\u3093\u3069\u3046")
-                .put(StructuredName.PHONETIC_GIVEN_NAME,
-                        "\u308D\u3044\u3069");
-        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.TYPE, Organization.TYPE_WORK)
-                .put(Organization.COMPANY, "\u30B0\u30FC\u30B0\u30EB")
-                .put(Organization.DEPARTMENT, "\u691C\u7D22\u90E8\u9580")
-                .put(Organization.PHONETIC_NAME,
-                        "\u3050\u30FC\u3050\u308B\u3051\u3093\u3055\u304F\u3076\u3082\u3093");
-    }
-
-    public void testIMV21_Parse() {
-        mVerifier.initForImportTest(V21, R.raw.v21_im);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("X-ANDROID-CUSTOM",
-                        Arrays.asList("vnd.android.cursor.item/nickname", "Nick", "1",
-                                "", "", "", "", "", "", "", "", "", "", "", "", ""))  // 13
-                .addExpectedNodeWithOrder("X-GOOGLE-TALK", "hhh@gmail.com");
-    }
-
-    public void testIMV21() {
-        mVerifier.initForImportTest(V21, R.raw.v21_im);
-        final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(Nickname.CONTENT_ITEM_TYPE)
-                .put(Nickname.NAME, "Nick")
-                .put(Nickname.TYPE, "1");
-        elem.addExpected(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK)
-                .put(Im.TYPE, Im.TYPE_HOME)
-                .put(Im.DATA, "hhh@gmail.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 8a99419..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
+++ /dev/null
@@ -1,435 +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.test_utils.ContactEntry;
-import android.pim.vcard.test_utils.ContentValuesBuilder;
-import android.pim.vcard.test_utils.PropertyNodesVerifierElem;
-import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
-import android.pim.vcard.test_utils.VCardTestsBase;
-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 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.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null);
-        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);
-    }
-
-    public void testNameUtf8V30() {
-        testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE);
-    }
-
-    public void testNameShiftJis() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
-                .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, "Shift_JIS");
-        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, String charset) {
-        mVerifier.initForExportTest(vcardType, charset);
-        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 =
-            ("SHIFT_JIS".equalsIgnoreCase(charset) ?
-                    (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndSJis :
-                        mContentValuesForSJis) :
-                    (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null));
-        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.isVersion21(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, null);
-    }
-
-    public void testPhoneticNameForJapaneseV21Sjis() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
-    }
-
-    public void testPhoneticNameForJapaneseV30Utf8() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, null);
-    }
-
-    public void testPhoneticNameForJapaneseV30SJis() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
-    }
-
-    public void testPhoneticNameForMobileV21_1() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
-                .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, "Shift_JIS");
-        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, String charset) {
-        mVerifier.initForExportTest(vcardType, charset);
-        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 = ("UTF-8".equalsIgnoreCase(charset) ?
-                (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndSJis :
-                    mContentValuesForSJis) :
-                (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 :
-                    mContentValuesForUtf8));
-
-        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, "Shift_JIS");
-    }
-
-    /**
-     * 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, "Shift_JIS");
-        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, "Shift_JIS");
-        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, "Shift_JIS");
-        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, "Shift_JIS");
-        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);
-    }
-
-    public void testJapanesePhoneNumberV30() {
-        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE);
-    }
-
-    public void testJapanesePhoneNumberDoCoMo() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
-        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, "Shift_JIS");
-        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);
-        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/VCardTestRunner.java b/core/tests/coretests/src/android/pim/vcard/VCardTestRunner.java
deleted file mode 100644
index a3a4393..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardTestRunner.java
+++ /dev/null
@@ -1,42 +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.pim.vcard;
-
-import android.test.InstrumentationTestRunner;
-import android.test.InstrumentationTestSuite;
-
-import junit.framework.TestSuite;
-
-/**
- * Usage: adb shell am instrument -w com.android.vcard.tests/.VCardTestRunnerTestRunner
- */
-public class VCardTestRunner extends InstrumentationTestRunner {
-    @Override
-    public TestSuite getAllTests() {
-        TestSuite suite = new InstrumentationTestSuite(this);
-        suite.addTestSuite(VCardUtilsTests.class);
-        suite.addTestSuite(VCardTestUtilsTests.class);
-        suite.addTestSuite(VCardImporterTests.class);
-        suite.addTestSuite(VCardExporterTests.class);
-        suite.addTestSuite(VCardJapanizationTests.class);
-        return suite;
-    }
-
-    @Override
-    public ClassLoader getLoader() {
-        return VCardTestRunner.class.getClassLoader();
-    }
-}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardTestUtilsTests.java
deleted file mode 100644
index 3ff5cf7..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardTestUtilsTests.java
+++ /dev/null
@@ -1,98 +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.pim.vcard;
-
-import com.android.frameworks.coretests.R;
-
-import android.pim.vcard.test_utils.VCardVerifier;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.test.AndroidTestCase;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.TestCase;
-
-import java.util.Arrays;
-
-/**
- * Tests confirming utilities for vCard tests work fine.
- *
- * Now that the foundation classes for vCard test cases became too complicated to
- * rely on without testing itself.
- */
-public class VCardTestUtilsTests extends AndroidTestCase {
-    public void testShouldFailAtPropertyNodeVerification() {
-        boolean failureDetected = false;
-        try {
-            final VCardVerifier verifier = new VCardVerifier(this);
-            verifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_backslash);
-            verifier.addPropertyNodesVerifierElem()
-                    .addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;--",  // wrong
-                            Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
-                    .addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
-            verifier.verify();
-        } catch (AssertionFailedError e) {
-            failureDetected = true;
-        }
-        if (!failureDetected) {
-            TestCase.fail("Test case that should fail actually succeeded.");
-        }
-    }
-
-    public void testShouldFailAtContentValueVerification() {
-        boolean failureDetected = false;
-        try {
-            final VCardVerifier verifier = new VCardVerifier(this);
-            verifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_backslash);
-            verifier.addContentValuesVerifierElem()
-                    .addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                            .put(StructuredName.GIVEN_NAME, "A;B\\")
-                            .put(StructuredName.MIDDLE_NAME, "C\\;")
-                            .put(StructuredName.PREFIX, "D")
-                            .put(StructuredName.SUFFIX, ":E");
-            // DISPLAY_NAME is missing.
-            verifier.verify();
-        } catch (AssertionFailedError e) {
-            failureDetected = true;
-        }
-        if (!failureDetected) {
-            TestCase.fail("Test case that should fail actually succeeded.");
-        }
-    }
-
-    public void testShouldFailAtLineVerification() {
-        boolean failureDetected = false;
-        try {
-            final VCardVerifier verifier = new VCardVerifier(this);
-            verifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_GENERIC);
-            verifier.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}>]");
-            verifier.addLineVerifierElem()
-                    .addExpected("TEL:1")  // wrong
-                    .addExpected("FN:[<{Unescaped:Asciis}>]");
-            verifier.verify();
-        } catch (AssertionFailedError e) {
-            failureDetected = true;
-        }
-        if (!failureDetected) {
-            TestCase.fail("Test case that should fail actually succeeded.");
-        }
-    }
-
-}
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 251fab2..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java
+++ /dev/null
@@ -1,118 +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.text.TextUtils;
-
-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)));
-        }
-    }
-
-    public void testToStringAvailableAsV30ParamValue() {
-        // Smoke tests.
-        assertEquals("HOME", VCardUtils.toStringAsV30ParamValue("HOME"));
-        assertEquals("TEL", VCardUtils.toStringAsV30ParamValue("TEL"));
-        assertEquals("PAGER", VCardUtils.toStringAsV30ParamValue("PAGER"));
-
-        assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue("")));
-        assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue(null)));
-        assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue(" \t")));
-
-        // non-Ascii must be allowed
-        assertEquals("\u4E8B\u52D9\u6240",
-                VCardUtils.toStringAsV30ParamValue("\u4E8B\u52D9\u6240"));
-        // Reported as bug report.
-        assertEquals("\u8D39", VCardUtils.toStringAsV30ParamValue("\u8D39"));
-        assertEquals("\"comma,separated\"",
-                VCardUtils.toStringAsV30ParamValue("comma,separated"));
-        assertEquals("\"colon:aware\"",
-                VCardUtils.toStringAsV30ParamValue("colon:aware"));
-        // CTL characters.
-        assertEquals("CTLExample",
-                VCardUtils.toStringAsV30ParamValue("CTL\u0001Example"));
-        assertTrue(TextUtils.isEmpty(
-                VCardUtils.toStringAsV30ParamValue("\u0001\u0002\u0003")));
-        // DQUOTE must be removed.
-        assertEquals("quoted",
-                VCardUtils.toStringAsV30ParamValue("\"quoted\""));
-        // DQUOTE must be removed basically, but we should detect a space, which
-        // require us to use DQUOTE again.
-        // Right-side has one more illegal dquote to test quote-handle code thoroughly.
-        assertEquals("\"Already quoted\"",
-                VCardUtils.toStringAsV30ParamValue("\"Already quoted\"\""));
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java
deleted file mode 100644
index 843750e..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java
+++ /dev/null
@@ -1,43 +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.pim.vcard.test_utils;
-
-import android.content.ContentValues;
-import android.provider.ContactsContract.Data;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * <p>
- * The class representing one contact, which should contain multiple ContentValues like
- * StructuredName, Email, etc.
- * </p>
- */
-public final class ContactEntry {
-    private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
-
-    public ContentValuesBuilder addContentValues(String mimeType) {
-        ContentValues contentValues = new ContentValues();
-        contentValues.put(Data.MIMETYPE, mimeType);
-        mContentValuesList.add(contentValues);
-        return new ContentValuesBuilder(contentValues);
-    }
-
-    public List<ContentValues> getList() {
-        return mContentValuesList;
-    }
-}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
deleted file mode 100644
index bdcccf9..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
+++ /dev/null
@@ -1,82 +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.test_utils;
-
-import android.content.ContentValues;
-
-/**
- * ContentValues-like class which enables users to chain put() methods and restricts
- * the other methods.
- */
-public 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/test_utils/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
deleted file mode 100644
index d0613c6..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
+++ /dev/null
@@ -1,54 +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.test_utils;
-
-import android.pim.vcard.VCardEntry;
-import android.pim.vcard.VCardEntryHandler;
-import android.test.AndroidTestCase;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ContentValuesVerifier implements VCardEntryHandler {
-    private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
-        new ArrayList<ContentValuesVerifierElem>();
-    private int mIndex;
-
-    public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) {
-        ContentValuesVerifierElem elem = new ContentValuesVerifierElem(androidTestCase);
-        mContentValuesVerifierElemList.add(elem);
-        return elem;
-    }
-
-    public void onStart() {
-        for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
-            elem.onParsingStart();
-        }
-    }
-
-    public void onEntryCreated(VCardEntry entry) {
-        AndroidTestCase.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/test_utils/ContentValuesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
deleted file mode 100644
index 03c18e1..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
+++ /dev/null
@@ -1,87 +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.test_utils;
-
-import android.content.ContentValues;
-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.VCardUtils;
-import android.pim.vcard.exception.VCardException;
-import android.provider.ContactsContract.Data;
-import android.test.AndroidTestCase;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public 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 = VCardUtils.getAppropriateParser(vcardType);
-        final VCardEntryConstructor builder = new VCardEntryConstructor(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/test_utils/ExportTestProvider.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
deleted file mode 100644
index e095258..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
+++ /dev/null
@@ -1,174 +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.pim.vcard.test_utils;
-
-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.pim.vcard.VCardComposer;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.test.AndroidTestCase;
-import android.test.mock.MockContentProvider;
-import android.test.mock.MockCursor;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-public class ExportTestProvider extends MockContentProvider {
-    final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
-
-    private static class MockEntityIterator implements EntityIterator {
-        List<Entity> mEntityList;
-        Iterator<Entity> mIterator;
-
-        public MockEntityIterator(List<ContentValues> contentValuesList) {
-            mEntityList = new ArrayList<Entity>();
-            Entity entity = new Entity(new ContentValues());
-            for (ContentValues contentValues : contentValuesList) {
-                    entity.addSubValue(Data.CONTENT_URI, contentValues);
-            }
-            mEntityList.add(entity);
-            mIterator = mEntityList.iterator();
-        }
-
-        public boolean hasNext() {
-            return mIterator.hasNext();
-        }
-
-        public Entity next() {
-            return mIterator.next();
-        }
-
-        public void remove() {
-            throw new UnsupportedOperationException("remove not supported");
-        }
-
-        public void reset() {
-            mIterator = mEntityList.iterator();
-        }
-
-        public void close() {
-        }
-    }
-
-    public ExportTestProvider(AndroidTestCase androidTestCase) {
-    }
-
-    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>
-     */
-    public EntityIterator queryEntities(Uri uri,
-            String selection, String[] selectionArgs, String sortOrder) {
-        TestCase.assertTrue(uri != null);
-        TestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
-        final String authority = uri.getAuthority();
-        TestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
-        TestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection));
-        TestCase.assertEquals(1, selectionArgs.length);
-        final int id = Integer.parseInt(selectionArgs[0]);
-        TestCase.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) {
-        TestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
-        // In this test, following arguments are not supported.
-        TestCase.assertNull(selection);
-        TestCase.assertNull(selectionArgs);
-        TestCase.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) {
-                TestCase.assertEquals(Contacts._ID, columnName);
-                return 0;
-            }
-
-            @Override
-            public int getInt(int columnIndex) {
-                TestCase.assertEquals(0, columnIndex);
-                TestCase.assertTrue(mCurrentPosition >= 0
-                        && mCurrentPosition < mContactEntryList.size());
-                return mCurrentPosition;
-            }
-
-            @Override
-            public String getString(int columnIndex) {
-                return String.valueOf(getInt(columnIndex));
-            }
-
-            @Override
-            public void close() {
-            }
-        };
-    }
-}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java
deleted file mode 100644
index 606d91a..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.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.test_utils;
-
-import android.pim.vcard.VCardComposer;
-import android.provider.ContactsContract.RawContacts;
-import android.test.AndroidTestCase;
-import android.test.mock.MockContentResolver;
-
-public class ExportTestResolver extends MockContentResolver {
-    private final ExportTestProvider mProvider;
-    public ExportTestResolver(AndroidTestCase androidTestCase) {
-        mProvider = new ExportTestProvider(androidTestCase);
-        addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
-        addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
-    }
-
-    public ContactEntry addInputContactEntry() {
-        return mProvider.buildInputEntry();
-    }
-
-    public ExportTestProvider getProvider() {
-        return mProvider;
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
deleted file mode 100644
index df89491..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
+++ /dev/null
@@ -1,274 +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.pim.vcard.test_utils;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentValues;
-import android.net.Uri;
-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.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.test.AndroidTestCase;
-import android.test.mock.MockContentProvider;
-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.Map.Entry;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-public 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;
-
-    public ImportTestProvider(AndroidTestCase androidTestCase) {
-        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)) {
-            TestCase.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) {
-            TestCase.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)) {
-                TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
-                TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
-            } else if (uri.equals(Data.CONTENT_URI)) {
-                final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
-                if (!sKnownMimeTypeSet.contains(mimeType)) {
-                    TestCase.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()) {
-                    TestCase.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)) {
-                        TestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
-                        checked = true;
-                        break;
-                    }
-                }
-                if (!checked) {
-                    final StringBuilder builder = new StringBuilder();
-                    builder.append("\n");
-                    builder.append("Unexpected: ");
-                    builder.append(convertToEasilyReadableString(actualContentValues));
-                    builder.append("\n");
-                    builder.append("Expected  : ");
-                    for (ContentValues expectedContentValues : contentValuesCollection) {
-                        builder.append(convertToEasilyReadableString(expectedContentValues));
-                    }
-                    TestCase.fail(builder.toString());
-                }
-            } else {
-                TestCase.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();
-            TestCase.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 {
-                TestCase.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(
-            final ContentValues expected, final 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;
-            }
-            // Type mismatch usuall happens as importer doesn't care the type of each value.
-            // For example, variable type might be Integer when importing the type of TEL,
-            // while variable type would be String when importing the type of RELATION.
-            final Object actualValue = actual.get(key);
-            if (value instanceof byte[]) {
-                if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
-                    byte[] e = (byte[])value;
-                    byte[] a = (byte[])actualValue;
-                    return false;
-                }
-            } else if (!value.equals(actualValue) &&
-                    !value.toString().equals(actualValue.toString())) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java
deleted file mode 100644
index 1822afe..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java
+++ /dev/null
@@ -1,56 +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.test_utils;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentValues;
-import android.provider.ContactsContract.RawContacts;
-import android.test.AndroidTestCase;
-import android.test.mock.MockContentResolver;
-
-import java.util.ArrayList;
-
-public class ImportTestResolver extends MockContentResolver {
-    private final ImportTestProvider mProvider;
-
-    public ImportTestResolver(AndroidTestCase androidTestCase) {
-        mProvider = new ImportTestProvider(androidTestCase);
-    }
-
-    @Override
-    public ContentProviderResult[] applyBatch(String authority,
-            ArrayList<ContentProviderOperation> operations) {
-        equalsString(authority, RawContacts.CONTENT_URI.toString());
-        return mProvider.applyBatch(operations);
-    }
-
-    public void addExpectedContentValues(ContentValues expectedContentValues) {
-        mProvider.addExpectedContentValues(expectedContentValues);
-    }
-
-    public void verify() {
-        mProvider.verify();
-    }
-
-    private static boolean equalsString(String a, String b) {
-        if (a == null || a.length() == 0) {
-            return b == null || b.length() == 0;
-        } else {
-            return a.equals(b);
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
deleted file mode 100644
index e314ac5..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
+++ /dev/null
@@ -1,66 +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.test_utils;
-
-import android.content.Context;
-import android.pim.vcard.VCardComposer;
-import android.test.AndroidTestCase;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-
-public class LineVerifier implements VCardComposer.OneEntryHandler {
-    private final AndroidTestCase mAndroidTestCase;
-    private final ArrayList<LineVerifierElem> mLineVerifierElemList;
-    private int mVCardType;
-    private int index;
-
-    public LineVerifier(AndroidTestCase androidTestCase, int vcardType) {
-        mAndroidTestCase = androidTestCase;
-        mLineVerifierElemList = new ArrayList<LineVerifierElem>();
-        mVCardType = vcardType;
-    }
-
-    public LineVerifierElem addLineVerifierElem() {
-        LineVerifierElem lineVerifier = new LineVerifierElem(mAndroidTestCase, mVCardType);
-        mLineVerifierElemList.add(lineVerifier);
-        return lineVerifier;
-    }
-
-    public void verify(String vcard) {
-        if (index >= mLineVerifierElemList.size()) {
-            TestCase.fail("Insufficient number of LineVerifier (" + index + ")");
-        }
-
-        final 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/test_utils/LineVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
deleted file mode 100644
index e23a9cd..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
+++ /dev/null
@@ -1,104 +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.test_utils;
-
-import android.pim.vcard.VCardConfig;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class LineVerifierElem {
-    private final List<String> mExpectedLineList = new ArrayList<String>();
-    private final int mVCardType;
-
-    public LineVerifierElem(AndroidTestCase androidTestCase, int vcardType) {
-        mVCardType = 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) {
-                    TestCase.fail("Multiple \"BEGIN:VCARD\" line found");
-                } else {
-                    beginExists = true;
-                    continue;
-                }
-            } else if ("END:VCARD".equalsIgnoreCase(line)) {
-                if (endExists) {
-                    TestCase.fail("Multiple \"END:VCARD\" line found");
-                } else {
-                    endExists = true;
-                    continue;
-                }
-            } else if ((VCardConfig.isVersion21(mVCardType) ? "VERSION:2.1" :
-                (VCardConfig.isVersion30(mVCardType) ? "VERSION:3.0" :
-                    "VERSION:4.0")).equalsIgnoreCase(line)) {
-                if (versionExists) {
-                    TestCase.fail("Multiple VERSION line + found");
-                } else {
-                    versionExists = true;
-                    continue;
-                }
-            }
-
-            if (!beginExists) {
-                TestCase.fail("Property other than BEGIN came before BEGIN property: " + line);
-            } else if (endExists) {
-                TestCase.fail("Property other than END came after END property: " + line);
-            }
-
-            final int index = mExpectedLineList.indexOf(line);
-            if (index >= 0) {
-                mExpectedLineList.remove(index);
-            } else {
-                TestCase.fail("Unexpected line: " + line);
-            }
-        }
-
-        if (!mExpectedLineList.isEmpty()) {
-            StringBuffer buffer = new StringBuffer();
-            for (String expectedLine : mExpectedLineList) {
-                buffer.append(expectedLine);
-                buffer.append("\n");
-            }
-
-            TestCase.fail("Expected line(s) not found:" + buffer.toString());
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
deleted file mode 100644
index de7ad8e..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
+++ /dev/null
@@ -1,204 +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.test_utils;
-
-import android.content.ContentValues;
-import android.pim.vcard.VCardEntry;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * <p>
- * The class representing one property (e.g. "N;ENCODING=UTF-8:family:given:middle:prefix:suffix").
- * </p>
- * <p>
- * Previously used in main vCard handling code but now exists only for testing.
- * </p>
- * <p>
- * Especially useful for testing parser code (VCardParser), since all properties can be
- * checked via this class unlike {@link VCardEntry}, which only emits the result of
- * interpretation of the content of each vCard. We cannot know whether vCard parser or
- * {@link VCardEntry} is wrong without this class.
- * </p>
- */
-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/test_utils/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java
deleted file mode 100644
index 4229284..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java
+++ /dev/null
@@ -1,82 +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.test_utils;
-
-import android.pim.vcard.VCardParser;
-import android.pim.vcard.VCardUtils;
-import android.pim.vcard.exception.VCardException;
-import android.test.AndroidTestCase;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-public class PropertyNodesVerifier extends VNodeBuilder {
-    private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
-    private final AndroidTestCase mAndroidTestCase;
-    private int mIndex;
-
-    public PropertyNodesVerifier(AndroidTestCase testCase) {
-        super();
-        mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
-        mAndroidTestCase = testCase;
-    }
-
-    public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
-        PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
-        mPropertyNodesVerifierElemList.add(elem);
-        return elem;
-    }
-
-    public void verify(int resId, int vcardType) throws IOException, VCardException {
-        verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vcardType);
-    }
-
-    public void verify(int resId, int vcardType, final VCardParser parser)
-            throws IOException, VCardException {
-        verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
-                vcardType, parser);
-    }
-
-    public void verify(InputStream is, int vcardType) throws IOException, VCardException {
-        final VCardParser parser = VCardUtils.getAppropriateParser(vcardType);
-        verify(is, vcardType, parser);
-    }
-
-    public void verify(InputStream is, int vcardType, final VCardParser parser)
-            throws IOException, VCardException {
-        try {
-            parser.parse(is, this);
-        } finally {
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                }
-            }
-        }
-    }
-
-    @Override
-    public void endEntry() {
-        super.endEntry();
-        AndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
-        AndroidTestCase.assertTrue(mIndex < vNodeList.size());
-        mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
-        mIndex++;
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
deleted file mode 100644
index 44c6abc..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
+++ /dev/null
@@ -1,330 +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.pim.vcard.test_utils;
-
-import android.content.ContentValues;
-import android.test.AndroidTestCase;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * 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.
- */
-public 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;
-
-    public PropertyNodesVerifierElem(AndroidTestCase androidTestCase) {
-        mOrderedNodeMap = new HashMap<String, List<PropertyNode>>();
-        mUnorderedNodeList = new ArrayList<PropertyNode>();
-    }
-
-    // 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,
-            List<String> propValueList, ContentValues paramMap, TypeSet paramMap_TYPE) {
-        return addExpectedNodeWithOrder(propName, null, propValueList, null, paramMap,
-                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, ContentValues paramMap) {
-        return addExpectedNodeWithOrder(propName, propValue, propValueList, null, paramMap,
-                null, 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);
-        }
-        final 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()) {
-            final 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);
-                }
-            }
-            TestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
-                    + " was not found.");
-        }
-    }
-
-    private void verifyNode(final String propName, final PropertyNode actualNode) {
-        final List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
-        final int size = (expectedNodeList != null ? expectedNodeList.size() : 0);
-        if (size > 0) {
-            for (int i = 0; i < size; i++) {
-                final PropertyNode expectedNode = expectedNodeList.get(i);
-                final 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.
-                    TestCase.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.
-                    TestCase.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");
-        }
-        TestCase.fail("Property \"" + propName + "\" has wrong value.\n"
-                + builder.toString()
-                + "  actual: " + actualNode.toString());
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/VCardTestsBase.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardTestsBase.java
deleted file mode 100644
index e7413ab..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/VCardTestsBase.java
+++ /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.
- */
-package android.pim.vcard.test_utils;
-
-import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
-import android.test.AndroidTestCase;
-
-/**
- * BaseClass for vCard unit tests with utility classes.
- * Please do not add each unit test here.
- */
-public class VCardTestsBase extends AndroidTestCase {
-    public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC;
-    public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC;
-    public static final int V40 = VCardConfig.VCARD_TYPE_V40_GENERIC;
-
-    // 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();
-        // Not using constants in vCard code since it may be wrong.
-        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/test_utils/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
deleted file mode 100644
index 7379a5b..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
+++ /dev/null
@@ -1,370 +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.test_utils;
-
-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.VCardUtils;
-import android.pim.vcard.exception.VCardException;
-import android.test.AndroidTestCase;
-import android.test.mock.MockContext;
-import android.text.TextUtils;
-import android.util.Log;
-
-import junit.framework.TestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-/**
- * <p>
- * The class lets users checks that given expected vCard data are same as given actual vCard data.
- * Able to verify both vCard importer/exporter.
- * </p>
- * <p>
- * First a user has to initialize the object by calling either
- * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}.
- * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported.
- * </p>
- */
-public class VCardVerifier {
-    private static final String LOG_TAG = "VCardVerifier";
-
-    private static class CustomMockContext extends MockContext {
-        final ContentResolver mResolver;
-        public CustomMockContext(ContentResolver resolver) {
-            mResolver = resolver;
-        }
-
-        @Override
-        public ContentResolver getContentResolver() {
-            return mResolver;
-        }
-    }
-
-    private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
-        public boolean onInit(Context context) {
-            return true;
-        }
-        public boolean onEntryCreated(String vcard) {
-            verifyOneVCardForExport(vcard);
-            return true;
-        }
-        public void onTerminate() {
-        }
-    }
-
-    private final AndroidTestCase mAndroidTestCase;
-    private final VCardVerifierInternal mVCardVerifierInternal;
-    private int mVCardType;
-    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;
-    private String mCharset;
-
-    // Called by VCardTestsBase
-    public VCardVerifier(AndroidTestCase androidTestCase) {
-        mAndroidTestCase = androidTestCase;
-        mVCardVerifierInternal = new VCardVerifierInternal();
-        mExportTestResolver = null;
-        mInputStream = null;
-        mInitialized = false;
-        mVerified = false;
-    }
-
-    // Should be called at the beginning of each import test.
-    public void initForImportTest(int vcardType, int resId) {
-        if (mInitialized) {
-            AndroidTestCase.fail("Already initialized");
-        }
-        mVCardType = vcardType;
-        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
-        setInputResourceId(resId);
-        mInitialized = true;
-    }
-
-    // Should be called at the beginning of each export test.
-    public void initForExportTest(int vcardType) {
-        initForExportTest(vcardType, "UTF-8");
-    }
-
-    public void initForExportTest(int vcardType, String charset) {
-        if (mInitialized) {
-            AndroidTestCase.fail("Already initialized");
-        }
-        mExportTestResolver = new ExportTestResolver(mAndroidTestCase);
-        mVCardType = vcardType;
-        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
-        mInitialized = true;
-        if (TextUtils.isEmpty(charset)) {
-            mCharset = "UTF-8";
-        } else {
-            mCharset = charset;
-        }
-    }
-
-    private void setInputResourceId(int resId) {
-        InputStream inputStream = mAndroidTestCase.getContext().getResources().openRawResource(resId);
-        if (inputStream == null) {
-            AndroidTestCase.fail("Wrong resId: " + resId);
-        }
-        setInputStream(inputStream);
-    }
-
-    private void setInputStream(InputStream inputStream) {
-        if (mExportTestResolver != null) {
-            AndroidTestCase.fail("addInputEntry() is called.");
-        } else if (mInputStream != null) {
-            AndroidTestCase.fail("InputStream is already set");
-        }
-        mInputStream = inputStream;
-    }
-
-    public ContactEntry addInputEntry() {
-        if (!mInitialized) {
-            AndroidTestCase.fail("Not initialized");
-        }
-        if (mInputStream != null) {
-            AndroidTestCase.fail("setInputStream is called");
-        }
-        return mExportTestResolver.addInputContactEntry();
-    }
-
-    public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithoutVersion() {
-        if (!mInitialized) {
-            AndroidTestCase.fail("Not initialized");
-        }
-        if (mPropertyNodesVerifier == null) {
-            mPropertyNodesVerifier = new PropertyNodesVerifier(mAndroidTestCase);
-        }
-        return mPropertyNodesVerifier.addPropertyNodesVerifierElem();
-    }
-    
-    public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
-        final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElemWithoutVersion();
-        final String versionString;
-        if (VCardConfig.isVersion21(mVCardType)) {
-            versionString = "2.1";
-        } else if (VCardConfig.isVersion30(mVCardType)) {
-            versionString = "3.0";
-        } else if (VCardConfig.isVersion40(mVCardType)) {
-            versionString = "4.0";
-        } else {
-            throw new RuntimeException("Unexpected vcard type during a unit test");
-        }
-        elem.addExpectedNodeWithOrder("VERSION", versionString);
-
-        return elem;
-    }
-
-    public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() {
-        if (!mInitialized) {
-            AndroidTestCase.fail("Not initialized");
-        }
-        final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
-        if (VCardConfig.isVersion40(mVCardType)) {
-            elem.addExpectedNodeWithOrder("FN", "");
-        } else if (VCardConfig.isVersion30(mVCardType)) {
-            elem.addExpectedNodeWithOrder("N", "");
-            elem.addExpectedNodeWithOrder("FN", "");
-        } else if (mIsDoCoMo) {
-            elem.addExpectedNodeWithOrder("N", "");
-        }
-        return elem;
-    }
-
-    public LineVerifierElem addLineVerifierElem() {
-        if (!mInitialized) {
-            AndroidTestCase.fail("Not initialized");
-        }
-        if (mLineVerifier == null) {
-            mLineVerifier = new LineVerifier(mAndroidTestCase, mVCardType);
-        }
-        return mLineVerifier.addLineVerifierElem();
-    }
-
-    public ContentValuesVerifierElem addContentValuesVerifierElem() {
-        if (!mInitialized) {
-            AndroidTestCase.fail("Not initialized");
-        }
-        if (mContentValuesVerifier == null) {
-            mContentValuesVerifier = new ContentValuesVerifier();
-        }
-
-        return mContentValuesVerifier.addElem(mAndroidTestCase);
-    }
-
-    /**
-     * Sets up sub-verifiers correctly and try parse given vCard as InputStream.
-     * Errors around InputStream must be handled outside this method.
-     *
-     * Used both from {@link #verifyForImportTest()} and from {@link #verifyForExportTest()}.
-     */
-    private void verifyWithInputStream(InputStream is) throws IOException {
-        final VCardInterpreter interpreter;
-        if (mContentValuesVerifier != null) {
-            final VCardEntryConstructor constructor = new VCardEntryConstructor(mVCardType);
-            constructor.addEntryHandler(mContentValuesVerifier);
-            if (mPropertyNodesVerifier != null) {
-                interpreter = new VCardInterpreterCollection(Arrays.asList(
-                        mPropertyNodesVerifier, constructor));
-            } else {
-                interpreter = constructor;
-            }
-        } else {
-            if (mPropertyNodesVerifier != null) {
-                interpreter = mPropertyNodesVerifier;
-            } else {
-                interpreter = null;
-            }
-        }
-
-        try {
-            // Note: we must not specify charset toward vCard parsers. This code checks whether
-            // those parsers are able to encode given binary without any extra information for
-            // charset.
-            final VCardParser parser = VCardUtils.getAppropriateParser(mVCardType);
-            parser.parse(is, interpreter);
-        } catch (VCardException e) {
-            AndroidTestCase.fail("Unexpected VCardException: " + e.getMessage());
-        }
-    }
-
-    private void verifyOneVCardForExport(final String vcard) {
-        Log.d(LOG_TAG, vcard);
-        InputStream is = null;
-        try {
-            is = new ByteArrayInputStream(vcard.getBytes(mCharset));
-            verifyWithInputStream(is);
-        } catch (IOException e) {
-            AndroidTestCase.fail("Unexpected IOException: " + e.getMessage());
-        } finally {
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                    AndroidTestCase.fail("Unexpected IOException: " + e.getMessage());
-                }
-            }
-        }
-    }
-
-    public void verify() {
-        if (!mInitialized) {
-            TestCase.fail("Not initialized.");
-        }
-        if (mVerified) {
-            TestCase.fail("verify() was called twice.");
-        }
-
-        if (mInputStream != null) {
-            if (mExportTestResolver != null){
-                TestCase.fail("There are two input sources.");
-            }
-            verifyForImportTest();
-        } else if (mExportTestResolver != null){
-            verifyForExportTest();
-        } else {
-            TestCase.fail("No input is determined");
-        }
-        mVerified = true;
-    }
-
-    private void verifyForImportTest() {
-        if (mLineVerifier != null) {
-            AndroidTestCase.fail("Not supported now.");
-        }
-
-        try {
-            verifyWithInputStream(mInputStream);
-        } catch (IOException e) {
-            AndroidTestCase.fail("IOException was thrown: " + e.getMessage());
-        } finally {
-            if (mInputStream != null) {
-                try {
-                    mInputStream.close();
-                } catch (IOException e) {
-                }
-            }
-        }
-    }
-
-    public static EntityIterator mockGetEntityIteratorMethod(
-            final ContentResolver resolver,
-            final Uri uri, final String selection,
-            final String[] selectionArgs, final String sortOrder) {
-        if (ExportTestResolver.class.equals(resolver.getClass())) {
-            return ((ExportTestResolver)resolver).getProvider().queryEntities(
-                    uri, selection, selectionArgs, sortOrder);
-        }
-
-        Log.e(LOG_TAG, "Unexpected provider given.");
-        return null;
-    }
-
-    private Method getMockGetEntityIteratorMethod()
-            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, mCharset);
-        composer.addHandler(mLineVerifier);
-        composer.addHandler(mVCardVerifierInternal);
-        if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
-            AndroidTestCase.fail("init() failed. Reason: " + composer.getErrorReason());
-        }
-        AndroidTestCase.assertFalse(composer.isAfterLast());
-        try {
-            while (!composer.isAfterLast()) {
-                try {
-                    final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
-                    AndroidTestCase.assertNotNull(mockGetEntityIteratorMethod);
-                    AndroidTestCase.assertTrue(
-                            composer.createOneEntry(mockGetEntityIteratorMethod));
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    AndroidTestCase.fail(e.toString());
-                }
-            }
-        } finally {
-            composer.terminate();
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
deleted file mode 100644
index b890e2c..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/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.test_utils;
-
-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/test_utils/VNodeBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
deleted file mode 100644
index 8837034..0000000
--- a/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
+++ /dev/null
@@ -1,240 +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.test_utils;
-
-import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardInterpreter;
-import android.pim.vcard.VCardUtils;
-import android.util.Base64;
-import android.util.CharsetUtils;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * <p>
- * The class storing the parse result to custom datastruct:
- * {@link VNode}, and {@link PropertyNode}.
- * Maybe several vcard instance, so use vNodeList to store.
- * </p>
- * <p>
- * This is called VNode, not VCardNode, since it was used for expressing vCalendar (iCal).
- * </p>
- */
-public class VNodeBuilder implements VCardInterpreter {
-    static private String LOG_TAG = "VNodeBuilder"; 
-    
-    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_IMPORT_CHARSET, false);
-    }
-
-    public VNodeBuilder(String targetCharset, boolean strictLineBreakParsing) {
-        mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
-        if (targetCharset != null) {
-            mTargetCharset = targetCharset;
-        } else {
-            mTargetCharset = VCardConfig.DEFAULT_IMPORT_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;
-    }
-
-    public void propertyGroup(String group) {
-        mCurrentPropNode.propGroupSet.add(group);
-    }
-    
-    public void propertyParamType(String type) {
-        mCurrentParamType = type;
-    }
-
-    public void propertyParamValue(String value) {
-        if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
-            value = VCardUtils.convertStringCharset(value,
-                    VCardConfig.DEFAULT_INTERMEDIATE_CHARSET,
-                    VCardConfig.DEFAULT_IMPORT_CHARSET);
-        }
-
-        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.decode(value.getBytes(), Base64.NO_WRAP);
-                return value;
-            } else if (encoding.equals("QUOTED-PRINTABLE")) {
-                return VCardUtils.parseQuotedPrintable(
-                        value, mStrictLineBreakParsing, mSourceCharset, targetCharset);
-            }
-            // 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(){
-        throw new RuntimeException("Not supported");
-    }
-}
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 370ae78..b82e698 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -144,4 +144,51 @@
 
         assertEquals(null, Settings.Bookmarks.getIntentForShortcut(r, '*'));
     }
+
+    @MediumTest
+    public void testParseProviderList() {
+        ContentResolver r = getContext().getContentResolver();
+
+        // Make sure we get out what we put in.
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                "test1,test2,test3");
+        assertEquals(Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED),
+                "test1,test2,test3");
+
+        // Test adding a value
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                "");
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+test1");
+        assertEquals("test1",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test adding a second value
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+test2");
+        assertEquals("test1,test2",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test adding a third value
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+test3");
+        assertEquals("test1,test2,test3",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test deleting the first value in a 3 item list
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test1");
+        assertEquals("test2,test3",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test deleting the middle value in a 3 item list
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                "test1,test2,test3");
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test2");
+        assertEquals("test1,test3",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test deleting the last value in a 3 item list
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                "test1,test2,test3");
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test3");
+        assertEquals("test1,test2",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+     }
 }
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index 8e7e63e..fb0f0c1 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -22,7 +22,7 @@
 import junit.framework.TestCase;
 
 /**
- * Tests StaticLayout bidi implementation.
+ * Quick check of native bidi implementation.
  */
 public class StaticLayoutBidiTest extends TestCase {
     
@@ -41,73 +41,47 @@
     
     //@SmallTest
     public void testAllLtr() {
-        expectBidi(REQ_DL, "a test", "000000", L);
+        expectNativeBidi(REQ_DL, "a test", "000000", L);
     }
     
     //@SmallTest
     public void testLtrRtl() {
-        expectBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
+        expectNativeBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
     }
     
     //@SmallTest
     public void testAllRtl() {
-        expectBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
+        expectNativeBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
     }
     
     //@SmallTest
     public void testRtlLtr() {
-        expectBidi(REQ_DL,  ALEF + BET + GIMEL + " abc", "1111000", R);
+        expectNativeBidi(REQ_DL,  ALEF + BET + GIMEL + " abc", "1111222", R);
     }
     
     //@SmallTest
     public void testRAllLtr() {
-        expectBidi(REQ_R, "a test", "000000", R);
+        expectNativeBidi(REQ_R, "a test", "222222", R);
     }
     
     //@SmallTest
     public void testRLtrRtl() {
-        expectBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "0001111", R);
+        expectNativeBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "2221111", R);
     }
     
     //@SmallTest
     public void testLAllRtl() {
-        expectBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
+        expectNativeBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
     }
     
     //@SmallTest
     public void testLRtlLtr() {
-        expectBidi(REQ_L,  ALEF + BET + GIMEL + " abc", "1110000", L);
-    }
-    
-    private void expectBidi(int dir, String text, 
-            String expectedLevels, int expectedDir) {
-        char[] chs = text.toCharArray();
-        int n = chs.length;
-        byte[] chInfo = new byte[n];
-        
-        int resultDir = StaticLayout.bidi(dir, chs, chInfo, n, false);
-        
-        {
-            StringBuilder sb = new StringBuilder("info:");
-            for (int i = 0; i < n; ++i) {
-                sb.append(" ").append(String.valueOf(chInfo[i]));
-            }
-            Log.i("BIDI", sb.toString());
-        }
-        
-        char[] resultLevelChars = new char[n];
-        for (int i = 0; i < n; ++i) {
-            resultLevelChars[i] = (char)('0' + chInfo[i]);
-        }
-        String resultLevels = new String(resultLevelChars);
-        assertEquals("direction", expectedDir, resultDir);
-        assertEquals("levels", expectedLevels, resultLevels);
+        expectNativeBidi(REQ_DL,  ALEF + BET + GIMEL + " abc", "1111222", R);
     }
     
     //@SmallTest
     public void testNativeBidi() {
-        // native bidi returns levels, not simply directions
-        expectNativeBidi(REQ_DL,  ALEF + BET + GIMEL + " abc", "1111222", R);
+        expectNativeBidi(REQ_L,  ALEF + BET + GIMEL + " abc", "1110000", L);
     }
     
     private void expectNativeBidi(int dir, String text, 
diff --git a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
new file mode 100644
index 0000000..4fde849
--- /dev/null
+++ b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.text;
+
+import android.text.Layout.Directions;
+import android.text.StaticLayoutTest.LayoutBuilder;
+
+import java.util.Arrays;
+import java.util.Formatter;
+
+import junit.framework.TestCase;
+
+public class StaticLayoutDirectionsTest extends TestCase {
+    private static final char ALEF = '\u05d0';
+
+    private static Directions dirs(int ... dirs) {
+        return new Directions(dirs);
+    }
+
+    // constants from Layout that are package-protected
+    private static final int RUN_LENGTH_MASK = 0x03ffffff;
+    private static final int RUN_LEVEL_SHIFT = 26;
+    private static final int RUN_LEVEL_MASK = 0x3f;
+    private static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
+
+    private static final Directions DIRS_ALL_LEFT_TO_RIGHT =
+        new Directions(new int[] { 0, RUN_LENGTH_MASK });
+    private static final Directions DIRS_ALL_RIGHT_TO_LEFT =
+        new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
+
+    private static final int LVL1_1 = 1 | (1 << RUN_LEVEL_SHIFT);
+    private static final int LVL2_1 = 1 | (2 << RUN_LEVEL_SHIFT);
+    private static final int LVL2_2 = 2 | (2 << RUN_LEVEL_SHIFT);
+
+    private static String[] texts = {
+        "",
+        " ",
+        "a",
+        "a1",
+        "aA",
+        "a1b",
+        "a1A",
+        "aA1",
+        "aAb",
+        "aA1B",
+        "aA1B2",
+
+        // rtl
+        "A",
+        "A1",
+        "Aa",
+        "A1B",
+        "A1a",
+        "Aa1",
+        "AaB"
+    };
+
+    // Expected directions are an array of start/length+level pairs,
+    // in visual order from the leading margin.
+    private static Directions[] expected = {
+        DIRS_ALL_LEFT_TO_RIGHT,
+        DIRS_ALL_LEFT_TO_RIGHT,
+        DIRS_ALL_LEFT_TO_RIGHT,
+        DIRS_ALL_LEFT_TO_RIGHT,
+        dirs(0, 1, 1, LVL1_1),
+        DIRS_ALL_LEFT_TO_RIGHT,
+        dirs(0, 2, 2, LVL1_1),
+        dirs(0, 1, 2, LVL2_1, 1, LVL1_1),
+        dirs(0, 1, 1, LVL1_1, 2, 1),
+        dirs(0, 1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
+        dirs(0, 1, 4, LVL2_1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
+
+        // rtl
+        DIRS_ALL_RIGHT_TO_LEFT,
+        dirs(0, LVL1_1, 1, LVL2_1),
+        dirs(0, LVL1_1, 1, LVL2_1),
+        dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
+        dirs(0, LVL1_1, 1, LVL2_2),
+        dirs(0, LVL1_1, 1, LVL2_2),
+        dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
+    };
+
+    private static String pseudoBidiToReal(String src) {
+        char[] chars = src.toCharArray();
+        for (int j = 0; j < chars.length; ++j) {
+            char c = chars[j];
+            if (c >= 'A' && c <= 'D') {
+                chars[j] = (char)(ALEF + c - 'A');
+            }
+        }
+
+        return new String(chars, 0, chars.length);
+    }
+
+    // @SmallTest
+    public void testDirections() {
+        StringBuilder buf = new StringBuilder("\n");
+        Formatter f = new Formatter(buf);
+
+        LayoutBuilder b = StaticLayoutTest.builder();
+        for (int i = 0; i < texts.length; ++i) {
+            b.setText(pseudoBidiToReal(texts[i]));
+            checkDirections(b.build(), i, b.text, expected, f);
+        }
+        if (buf.length() > 1) {
+            fail(buf.toString());
+        }
+    }
+
+    // @SmallTest
+    public void testTrailingWhitespace() {
+        LayoutBuilder b = StaticLayoutTest.builder();
+        b.setText(pseudoBidiToReal("Ab   c"));
+        float width = b.paint.measureText(b.text, 0, 5);  // exclude 'c'
+        b.setWidth(Math.round(width));
+        Layout l = b.build();
+        if (l.getLineCount() != 2) {
+            throw new RuntimeException("expected 2 lines, got: " + l.getLineCount());
+        }
+        Directions result = l.getLineDirections(0);
+        Directions expected = dirs(0, LVL1_1, 1, LVL2_1, 2, 3 | (1 << Layout.RUN_LEVEL_SHIFT));
+        expectDirections("split line", expected, result);
+    }
+
+    public void testNextToRightOf() {
+        LayoutBuilder b = StaticLayoutTest.builder();
+        b.setText(pseudoBidiToReal("aA1B2"));
+        // visual a2B1A positions 04321
+        // 0: |a2B1A, strong is sol, after -> 0
+        // 1: a|2B1A, strong is a, after ->, 1
+        // 2: a2|B1A, strong is B, after -> 4
+        // 3: a2B|1A, strong is B, before -> 3
+        // 4: a2B1|A, strong is A, after -> 2
+        // 5: a2B1A|, strong is eol, before -> 5
+        int[] expected = { 0, 1, 4, 3, 2, 5 };
+        Layout l = b.build();
+        int n = 0;
+        for (int i = 1; i < expected.length; ++i) {
+            int t = l.getOffsetToRightOf(n);
+            if (t != expected[i]) {
+                fail("offset[" + i + "] to right of: " + n + " expected: " +
+                        expected[i] + " got: " + t);
+            }
+            n = t;
+        }
+    }
+
+    public void testNextToLeftOf() {
+        LayoutBuilder b = StaticLayoutTest.builder();
+        b.setText(pseudoBidiToReal("aA1B2"));
+        int[] expected = { 0, 1, 4, 3, 2, 5 };
+        Layout l = b.build();
+        int n = 5;
+        for (int i = expected.length - 1; --i >= 0;) {
+            int t = l.getOffsetToLeftOf(n);
+            if (t != expected[i]) {
+                fail("offset[" + i + "] to left of: " + n + " expected: " +
+                        expected[i] + " got: " + t);
+            }
+            n = t;
+        }
+    }
+
+    // utility, not really a test
+    /*
+    public void testMeasureText1() {
+        LayoutBuilder b = StaticLayoutTest.builder();
+        String text = "ABC"; // "abAB"
+        b.setText(pseudoBidiToReal(text));
+        Layout l = b.build();
+        Directions directions = l.getLineDirections(0);
+
+        TextPaint workPaint = new TextPaint();
+
+        int dir = -1; // LEFT_TO_RIGHT
+        boolean trailing = true;
+        boolean alt = true;
+        do {
+            dir = -dir;
+            do {
+                trailing = !trailing;
+                for (int offset = 0, end = b.text.length(); offset <= end; ++offset) {
+                    float width = Layout.measureText(b.paint,
+                            workPaint,
+                            b.text,
+                            0, offset, end,
+                            dir, directions,
+                            trailing, false,
+                            null);
+                    Log.i("BIDI", "dir: " + dir + " trail: " + trailing +
+                            " offset: " + offset + " width: " + width);
+                }
+            } while (!trailing);
+        } while (dir > 0);
+    }
+    */
+
+    // utility for displaying arrays in hex
+    private static String hexArray(int[] array) {
+        StringBuilder sb = new StringBuilder();
+        sb.append('{');
+        for (int i : array) {
+            if (sb.length() > 1) {
+                sb.append(", ");
+            }
+            sb.append(Integer.toHexString(i));
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+
+    private void checkDirections(Layout l, int i, String text,
+            Directions[] expectedDirs, Formatter f) {
+        Directions expected = expectedDirs[i];
+        Directions result = l.getLineDirections(0);
+        if (!Arrays.equals(expected.mDirections, result.mDirections)) {
+            f.format("%n[%2d] '%s', %s != %s", i, text,
+                    hexArray(expected.mDirections),
+                    hexArray(result.mDirections));
+        }
+    }
+
+    private void expectDirections(String msg, Directions expected, Directions result) {
+        if (!Arrays.equals(expected.mDirections, result.mDirections)) {
+            fail("expected: " + hexArray(expected.mDirections) +
+                    " got: " + hexArray(result.mDirections));
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 1f58a2c..d554a50 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -228,11 +228,11 @@
         }
     }
 
-    private static LayoutBuilder builder() {
+    /* package */ static LayoutBuilder builder() {
         return new LayoutBuilder();
     }
 
-    private static class LayoutBuilder {
+    /* package */ static class LayoutBuilder {
         String text = "This is a test";
         TextPaint paint = new TextPaint(); // default
         int width = 100;
diff --git a/core/tests/coretests/src/android/util/ExpandableListScenario.java b/core/tests/coretests/src/android/util/ExpandableListScenario.java
deleted file mode 100644
index 4a12b0d..0000000
--- a/core/tests/coretests/src/android/util/ExpandableListScenario.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * Utility base class for creating various Expandable List scenarios.
- * <p>
- * WARNING: A lot of the features are mixed between ListView's expected position
- * (flat list position) and an ExpandableListView's expected position.  You must add/change
- * features as you need them.
- * 
- * @see ListScenario
- */
-public abstract class ExpandableListScenario extends ListScenario {
-    protected ExpandableListAdapter mAdapter; 
-    protected List<MyGroup> mGroups;
-    
-    @Override
-    protected ListView createListView() {
-        return new ExpandableListView(this);
-    }
-
-    @Override
-    protected Params createParams() {
-        return new ExpandableParams();
-    }
-
-    @Override
-    protected void setAdapter(ListView listView) {
-        ((ExpandableListView) listView).setAdapter(mAdapter = createAdapter());
-    }
-    
-    protected ExpandableListAdapter createAdapter() {
-        return new MyAdapter();
-    }
-    
-    @Override
-    protected void readAndValidateParams(Params params) {
-        ExpandableParams expandableParams = (ExpandableParams) params;
-        
-        int[] numChildren = expandableParams.mNumChildren;
-        
-        mGroups = new ArrayList<MyGroup>(numChildren.length);
-        for (int i = 0; i < numChildren.length; i++) {
-            mGroups.add(new MyGroup(numChildren[i]));
-        }
-        
-        expandableParams.superSetNumItems();
-        
-        super.readAndValidateParams(params);
-    }
-
-    /**
-     * Get the ExpandableListView widget.
-     * @return The main widget.
-     */
-    public ExpandableListView getExpandableListView() {
-        return (ExpandableListView) super.getListView();
-    }
-
-    public static class ExpandableParams extends Params {
-        private int[] mNumChildren;
-        
-        /**
-         * Sets the number of children per group.
-         *  
-         * @param numChildrenPerGroup The number of children per group.
-         */
-        public ExpandableParams setNumChildren(int[] numChildren) {
-            mNumChildren = numChildren;
-            return this;
-        }
-
-        /**
-         * Sets the number of items on the superclass based on the number of
-         * groups and children per group.
-         */
-        private ExpandableParams superSetNumItems() {
-            int numItems = 0;
-            
-            if (mNumChildren != null) {
-                for (int i = mNumChildren.length - 1; i >= 0; i--) {
-                    numItems += mNumChildren[i];
-                }
-            }
-            
-            super.setNumItems(numItems);
-            
-            return this;
-        }
-        
-        @Override
-        public Params setNumItems(int numItems) {
-            throw new IllegalStateException("Use setNumGroups and setNumChildren instead.");
-        }
-
-        @Override
-        public ExpandableParams setFadingEdgeScreenSizeFactor(double fadingEdgeScreenSizeFactor) {
-            return (ExpandableParams) super.setFadingEdgeScreenSizeFactor(fadingEdgeScreenSizeFactor);
-        }
-
-        @Override
-        public ExpandableParams setItemScreenSizeFactor(double itemScreenSizeFactor) {
-            return (ExpandableParams) super.setItemScreenSizeFactor(itemScreenSizeFactor);
-        }
-
-        @Override
-        public ExpandableParams setItemsFocusable(boolean itemsFocusable) {
-            return (ExpandableParams) super.setItemsFocusable(itemsFocusable);
-        }
-
-        @Override
-        public ExpandableParams setMustFillScreen(boolean fillScreen) {
-            return (ExpandableParams) super.setMustFillScreen(fillScreen);
-        }
-
-        @Override
-        public ExpandableParams setPositionScreenSizeFactorOverride(int position, double itemScreenSizeFactor) {
-            return (ExpandableParams) super.setPositionScreenSizeFactorOverride(position, itemScreenSizeFactor);
-        }
-
-        @Override
-        public ExpandableParams setPositionUnselectable(int position) {
-            return (ExpandableParams) super.setPositionUnselectable(position);
-        }
-
-        @Override
-        public ExpandableParams setStackFromBottom(boolean stackFromBottom) {
-            return (ExpandableParams) super.setStackFromBottom(stackFromBottom);
-        }
-
-        @Override
-        public ExpandableParams setStartingSelectionPosition(int startingSelectionPosition) {
-            return (ExpandableParams) super.setStartingSelectionPosition(startingSelectionPosition);
-        }
-
-        @Override
-        public ExpandableParams setConnectAdapter(boolean connectAdapter) {
-            return (ExpandableParams) super.setConnectAdapter(connectAdapter);
-        }
-    }
-
-    /**
-     * Gets a string for the value of some item.
-     * @param packedPosition The position of the item.
-     * @return The string.
-     */
-    public final String getValueAtPosition(long packedPosition) {
-        final int type = ExpandableListView.getPackedPositionType(packedPosition);
-        
-        if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
-            return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
-                    .children.get(ExpandableListView.getPackedPositionChild(packedPosition))
-                    .name;
-        } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
-            return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
-                    .name;
-        } else {
-            throw new IllegalStateException("packedPosition is not a valid position.");
-        }
-    }
-
-    /**
-     * Whether a particular position is out of bounds.
-     * 
-     * @param packedPosition The packed position.
-     * @return Whether it's out of bounds.
-     */
-    private boolean isOutOfBounds(long packedPosition) {
-        final int type = ExpandableListView.getPackedPositionType(packedPosition);
-        
-        if (type == ExpandableListView.PACKED_POSITION_TYPE_NULL) {
-            throw new IllegalStateException("packedPosition is not a valid position.");
-        }
-
-        final int group = ExpandableListView.getPackedPositionGroup(packedPosition); 
-        if (group >= mGroups.size() || group < 0) {
-            return true;
-        }
-        
-        if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
-            final int child = ExpandableListView.getPackedPositionChild(packedPosition); 
-            if (child >= mGroups.get(group).children.size() || child < 0) {
-                return true;
-            }
-        }
-        
-        return false;
-    }
-    
-    /**
-     * Gets a view for the packed position, possibly reusing the convertView.
-     * 
-     * @param packedPosition The position to get a view for.
-     * @param convertView Optional view to convert.
-     * @param parent The future parent.
-     * @return A view.
-     */
-    private View getView(long packedPosition, View convertView, ViewGroup parent) {
-        if (isOutOfBounds(packedPosition)) {
-            throw new IllegalStateException("position out of range for adapter!");
-        }
-        
-        final ExpandableListView elv = getExpandableListView();
-        final int flPos = elv.getFlatListPosition(packedPosition); 
-        
-        if (convertView != null) {
-            ((TextView) convertView).setText(getValueAtPosition(packedPosition));
-            convertView.setId(flPos);
-            return convertView;
-        }
-
-        int desiredHeight = getHeightForPosition(flPos);
-        return createView(packedPosition, flPos, parent, desiredHeight);
-    }
-    
-    /**
-     * Create a view for a group or child position.
-     * 
-     * @param packedPosition The packed position (has type, group pos, and optionally child pos).
-     * @param flPos The flat list position (the position that the ListView goes by).
-     * @param parent The parent view.
-     * @param desiredHeight The desired height.
-     * @return A view.
-     */
-    protected View createView(long packedPosition, int flPos, ViewGroup parent, int desiredHeight) {
-        TextView result = new TextView(parent.getContext());
-        result.setHeight(desiredHeight);
-        result.setText(getValueAtPosition(packedPosition));
-        final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
-        result.setLayoutParams(lp);
-        result.setGravity(Gravity.CENTER_VERTICAL);
-        result.setPadding(36, 0, 0, 0);
-        result.setId(flPos);
-        return result;
-    }
-    
-    /**
-     * Returns a group index containing either the number of children or at
-     * least one child.
-     * 
-     * @param numChildren The group must have this amount, or -1 if using
-     *            atLeastOneChild.
-     * @param atLeastOneChild The group must have at least one child, or false
-     *            if using numChildren.
-     * @return A group index with the requirements.
-     */
-    public int findGroupWithNumChildren(int numChildren, boolean atLeastOneChild) {
-        final ExpandableListAdapter adapter = mAdapter;
-        
-        for (int i = adapter.getGroupCount() - 1; i >= 0; i--) {
-            final int curNumChildren = adapter.getChildrenCount(i);
-            
-            if (numChildren == curNumChildren || atLeastOneChild && curNumChildren > 0) {
-                return i;
-            }
-        }
-        
-        return -1;
-    }
-    
-    public List<MyGroup> getGroups() {
-        return mGroups;
-    }
-    
-    public ExpandableListAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    /**
-     * Simple expandable list adapter.
-     */
-    protected class MyAdapter extends BaseExpandableListAdapter {
-        public Object getChild(int groupPosition, int childPosition) {
-            return getValueAtPosition(ExpandableListView.getPackedPositionForChild(groupPosition,
-                    childPosition));
-        }
-
-        public long getChildId(int groupPosition, int childPosition) {
-            return mGroups.get(groupPosition).children.get(childPosition).id;
-        }
-
-        public int getChildrenCount(int groupPosition) {
-            return mGroups.get(groupPosition).children.size();
-        }
-
-        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
-                View convertView, ViewGroup parent) {
-            return getView(ExpandableListView.getPackedPositionForChild(groupPosition,
-                    childPosition), convertView, parent);
-        }
-
-        public Object getGroup(int groupPosition) {
-            return getValueAtPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
-        }
-
-        public int getGroupCount() {
-            return mGroups.size();
-        }
-
-        public long getGroupId(int groupPosition) {
-            return mGroups.get(groupPosition).id;
-        }
-
-        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
-                ViewGroup parent) {
-            return getView(ExpandableListView.getPackedPositionForGroup(groupPosition),
-                    convertView, parent);
-        }
-
-        public boolean isChildSelectable(int groupPosition, int childPosition) {
-            return true;
-        }
-
-        public boolean hasStableIds() {
-            return true;
-        }
-        
-    }
-
-    public static class MyGroup {
-        private static long mNextId = 1000;
-        
-        String name;
-        long id = mNextId++;
-        List<MyChild> children;
-        
-        public MyGroup(int numChildren) {
-            name = "Group " + id;
-            children = new ArrayList<MyChild>(numChildren);
-            for (int i = 0; i < numChildren; i++) {
-                children.add(new MyChild());
-            }
-        }
-    }
-    
-    public static class MyChild {
-        private static long mNextId = 2000;
-        
-        String name;
-        long id = mNextId++;
-        
-        public MyChild() {
-            name = "Child " + id;
-        }
-    }
-    
-    @Override
-    protected final void init(Params params) {
-        init((ExpandableParams) params);
-    }
-
-    /**
-     * @see ListScenario#init
-     */
-    protected abstract void init(ExpandableParams params);
-}
diff --git a/core/tests/coretests/src/android/util/JsonReaderTest.java b/core/tests/coretests/src/android/util/JsonReaderTest.java
new file mode 100644
index 0000000..ced9310
--- /dev/null
+++ b/core/tests/coretests/src/android/util/JsonReaderTest.java
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+public final class JsonReaderTest extends TestCase {
+
+    public void testReadArray() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true, true]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(true, reader.nextBoolean());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testReadEmptyArray() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[]"));
+        reader.beginArray();
+        assertFalse(reader.hasNext());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testReadObject() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader(
+                "{\"a\": \"android\", \"b\": \"banana\"}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals("android", reader.nextString());
+        assertEquals("b", reader.nextName());
+        assertEquals("banana", reader.nextString());
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testReadEmptyObject() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{}"));
+        reader.beginObject();
+        assertFalse(reader.hasNext());
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testSkipObject() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader(
+                "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        reader.skipValue();
+        assertEquals("b", reader.nextName());
+        reader.skipValue();
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testHelloWorld() throws IOException {
+        String json = "{\n" +
+                "   \"hello\": true,\n" +
+                "   \"foo\": [\"world\"]\n" +
+                "}";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginObject();
+        assertEquals("hello", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+        assertEquals("foo", reader.nextName());
+        reader.beginArray();
+        assertEquals("world", reader.nextString());
+        reader.endArray();
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testNulls() {
+        try {
+            new JsonReader(null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    public void testEmptyString() throws IOException {
+        try {
+            new JsonReader(new StringReader("")).beginArray();
+        } catch (IOException expected) {
+        }
+        try {
+            new JsonReader(new StringReader("")).beginObject();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testNoTopLevelObject() throws IOException {
+        try {
+            new JsonReader(new StringReader("true")).nextBoolean();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testCharacterUnescaping() throws IOException {
+        String json = "[\"a\","
+                + "\"a\\\"\","
+                + "\"\\\"\","
+                + "\":\","
+                + "\",\","
+                + "\"\\b\","
+                + "\"\\f\","
+                + "\"\\n\","
+                + "\"\\r\","
+                + "\"\\t\","
+                + "\" \","
+                + "\"\\\\\","
+                + "\"{\","
+                + "\"}\","
+                + "\"[\","
+                + "\"]\","
+                + "\"\\u0000\","
+                + "\"\\u0019\","
+                + "\"\\u20AC\""
+                + "]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        assertEquals("a", reader.nextString());
+        assertEquals("a\"", reader.nextString());
+        assertEquals("\"", reader.nextString());
+        assertEquals(":", reader.nextString());
+        assertEquals(",", reader.nextString());
+        assertEquals("\b", reader.nextString());
+        assertEquals("\f", reader.nextString());
+        assertEquals("\n", reader.nextString());
+        assertEquals("\r", reader.nextString());
+        assertEquals("\t", reader.nextString());
+        assertEquals(" ", reader.nextString());
+        assertEquals("\\", reader.nextString());
+        assertEquals("{", reader.nextString());
+        assertEquals("}", reader.nextString());
+        assertEquals("[", reader.nextString());
+        assertEquals("]", reader.nextString());
+        assertEquals("\0", reader.nextString());
+        assertEquals("\u0019", reader.nextString());
+        assertEquals("\u20AC", reader.nextString());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testIntegersWithFractionalPartSpecified() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[1.0,1.0,1.0]"));
+        reader.beginArray();
+        assertEquals(1.0, reader.nextDouble());
+        assertEquals(1, reader.nextInt());
+        assertEquals(1L, reader.nextLong());
+    }
+
+    public void testDoubles() throws IOException {
+        String json = "[-0.0,"
+                + "1.0,"
+                + "1.7976931348623157E308,"
+                + "4.9E-324,"
+                + "0.0,"
+                + "-0.5,"
+                + "2.2250738585072014E-308,"
+                + "3.141592653589793,"
+                + "2.718281828459045]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        assertEquals(-0.0, reader.nextDouble());
+        assertEquals(1.0, reader.nextDouble());
+        assertEquals(1.7976931348623157E308, reader.nextDouble());
+        assertEquals(4.9E-324, reader.nextDouble());
+        assertEquals(0.0, reader.nextDouble());
+        assertEquals(-0.5, reader.nextDouble());
+        assertEquals(2.2250738585072014E-308, reader.nextDouble());
+        assertEquals(3.141592653589793, reader.nextDouble());
+        assertEquals(2.718281828459045, reader.nextDouble());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testNonFiniteDoubles() throws IOException {
+        String json = "[NaN]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        try {
+            reader.nextDouble();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+    }
+
+    public void testLongs() throws IOException {
+        String json = "[0,0,0,"
+                + "1,1,1,"
+                + "-1,-1,-1,"
+                + "-9223372036854775808,"
+                + "9223372036854775807]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        assertEquals(0L, reader.nextLong());
+        assertEquals(0, reader.nextInt());
+        assertEquals(0.0, reader.nextDouble());
+        assertEquals(1L, reader.nextLong());
+        assertEquals(1, reader.nextInt());
+        assertEquals(1.0, reader.nextDouble());
+        assertEquals(-1L, reader.nextLong());
+        assertEquals(-1, reader.nextInt());
+        assertEquals(-1.0, reader.nextDouble());
+        try {
+            reader.nextInt();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        assertEquals(Long.MIN_VALUE, reader.nextLong());
+        try {
+            reader.nextInt();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        assertEquals(Long.MAX_VALUE, reader.nextLong());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    /**
+     * This test fails because there's no double for 9223372036854775806, and
+     * our long parsing uses Double.parseDouble() for fractional values.
+     */
+    public void testHighPrecisionLong() throws IOException {
+        String json = "[9223372036854775806.000]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        assertEquals(9223372036854775806L, reader.nextLong());
+        reader.endArray();
+    }
+
+    public void testNumberWithOctalPrefix() throws IOException {
+        String json = "[01]";
+        JsonReader reader = new JsonReader(new StringReader(json));
+        reader.beginArray();
+        try {
+            reader.nextInt();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        try {
+            reader.nextLong();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        try {
+            reader.nextDouble();
+            fail();
+        } catch (NumberFormatException expected) {
+        }
+        assertEquals("01", reader.nextString());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testBooleans() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true,false]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(false, reader.nextBoolean());
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testMixedCaseLiterals() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[True,TruE,False,FALSE,NULL,nulL]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(false, reader.nextBoolean());
+        assertEquals(false, reader.nextBoolean());
+        reader.nextNull();
+        reader.nextNull();
+        reader.endArray();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+    }
+
+    public void testMissingValue() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextString();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testPrematureEndOfInput() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":true,"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+        try {
+            reader.nextName();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testPrematurelyClosed() throws IOException {
+        try {
+            JsonReader reader = new JsonReader(new StringReader("{\"a\":[]}"));
+            reader.beginObject();
+            reader.close();
+            reader.nextName();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+
+        try {
+            JsonReader reader = new JsonReader(new StringReader("{\"a\":[]}"));
+            reader.close();
+            reader.beginObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+
+        try {
+            JsonReader reader = new JsonReader(new StringReader("{\"a\":true}"));
+            reader.beginObject();
+            reader.nextName();
+            reader.peek();
+            reader.close();
+            reader.nextBoolean();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testNextFailuresDoNotAdvance() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":true}"));
+        reader.beginObject();
+        try {
+            reader.nextString();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextName();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.beginArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.endArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.beginObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.endObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        assertEquals(true, reader.nextBoolean());
+        try {
+            reader.nextString();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.nextName();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.beginArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            reader.endArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        reader.endObject();
+        assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+        reader.close();
+    }
+
+    public void testStringNullIsNotNull() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[\"null\"]"));
+        reader.beginArray();
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testNullLiteralIsNotAString() throws IOException {
+       JsonReader reader = new JsonReader(new StringReader("[null]"));
+        reader.beginArray();
+        try {
+            reader.nextString();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testStrictNameValueSeparator() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("{\"a\"=>true}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientNameValueSeparator() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+
+        reader = new JsonReader(new StringReader("{\"a\"=>true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+    }
+
+    public void testStrictComments() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[// comment \n true]"));
+        reader.beginArray();
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[# comment \n true]"));
+        reader.beginArray();
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[/* comment */ true]"));
+        reader.beginArray();
+        try {
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientComments() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[// comment \n true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+
+        reader = new JsonReader(new StringReader("[# comment \n true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+
+        reader = new JsonReader(new StringReader("[/* comment */ true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+    }
+
+    public void testStrictUnquotedNames() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{a:true}"));
+        reader.beginObject();
+        try {
+            reader.nextName();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientUnquotedNames() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{a:true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+    }
+
+    public void testStrictSingleQuotedNames() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{'a':true}"));
+        reader.beginObject();
+        try {
+            reader.nextName();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientSingleQuotedNames() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{'a':true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+    }
+
+    public void testStrictUnquotedStrings() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[a]"));
+        reader.beginArray();
+        try {
+            reader.nextString();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientUnquotedStrings() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[a]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals("a", reader.nextString());
+    }
+
+    public void testStrictSingleQuotedStrings() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("['a']"));
+        reader.beginArray();
+        try {
+            reader.nextString();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientSingleQuotedStrings() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("['a']"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals("a", reader.nextString());
+    }
+
+    public void testStrictSemicolonDelimitedArray() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true;true]"));
+        reader.beginArray();
+        try {
+            reader.nextBoolean();
+            reader.nextBoolean();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientSemicolonDelimitedArray() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true;true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        assertEquals(true, reader.nextBoolean());
+    }
+
+    public void testStrictSemicolonDelimitedNameValuePair() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}"));
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        try {
+            reader.nextBoolean();
+            reader.nextName();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientSemicolonDelimitedNameValuePair() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}"));
+        reader.setLenient(true);
+        reader.beginObject();
+        assertEquals("a", reader.nextName());
+        assertEquals(true, reader.nextBoolean());
+        assertEquals("b", reader.nextName());
+    }
+
+    public void testStrictUnnecessaryArraySeparators() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true,,true]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[,true]"));
+        reader.beginArray();
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[true,]"));
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IOException expected) {
+        }
+
+        reader = new JsonReader(new StringReader("[,]"));
+        reader.beginArray();
+        try {
+            reader.nextNull();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testLenientUnnecessaryArraySeparators() throws IOException {
+        JsonReader reader = new JsonReader(new StringReader("[true,,true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        reader.nextNull();
+        assertEquals(true, reader.nextBoolean());
+        reader.endArray();
+
+        reader = new JsonReader(new StringReader("[,true]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        reader.nextNull();
+        assertEquals(true, reader.nextBoolean());
+        reader.endArray();
+
+        reader = new JsonReader(new StringReader("[true,]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        assertEquals(true, reader.nextBoolean());
+        reader.nextNull();
+        reader.endArray();
+
+        reader = new JsonReader(new StringReader("[,]"));
+        reader.setLenient(true);
+        reader.beginArray();
+        reader.nextNull();
+        reader.nextNull();
+        reader.endArray();
+    }
+}
diff --git a/core/tests/coretests/src/android/util/JsonWriterTest.java b/core/tests/coretests/src/android/util/JsonWriterTest.java
new file mode 100644
index 0000000..fa84023
--- /dev/null
+++ b/core/tests/coretests/src/android/util/JsonWriterTest.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+public final class JsonWriterTest extends TestCase {
+
+    public void testWrongTopLevelType() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        try {
+            jsonWriter.value("a");
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testTwoNames() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a");
+        try {
+            jsonWriter.name("a");
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testNameWithoutValue() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a");
+        try {
+            jsonWriter.endObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testValueWithoutName() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        try {
+            jsonWriter.value(true);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testMultipleTopLevelValues() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray().endArray();
+        try {
+            jsonWriter.beginArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testBadNestingObject() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.beginObject();
+        try {
+            jsonWriter.endArray();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testBadNestingArray() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.beginArray();
+        try {
+            jsonWriter.endObject();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testNullName() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        try {
+            jsonWriter.name(null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    public void testNullStringValue() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a");
+        jsonWriter.value(null);
+        jsonWriter.endObject();
+        assertEquals("{\"a\":null}", stringWriter.toString());
+    }
+
+    public void testNonFiniteDoubles() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        try {
+            jsonWriter.value(Double.NaN);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            jsonWriter.value(Double.NEGATIVE_INFINITY);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            jsonWriter.value(Double.POSITIVE_INFINITY);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testDoubles() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.value(-0.0);
+        jsonWriter.value(1.0);
+        jsonWriter.value(Double.MAX_VALUE);
+        jsonWriter.value(Double.MIN_VALUE);
+        jsonWriter.value(0.0);
+        jsonWriter.value(-0.5);
+        jsonWriter.value(Double.MIN_NORMAL);
+        jsonWriter.value(Math.PI);
+        jsonWriter.value(Math.E);
+        jsonWriter.endArray();
+        jsonWriter.close();
+        assertEquals("[-0.0,"
+                + "1.0,"
+                + "1.7976931348623157E308,"
+                + "4.9E-324,"
+                + "0.0,"
+                + "-0.5,"
+                + "2.2250738585072014E-308,"
+                + "3.141592653589793,"
+                + "2.718281828459045]", stringWriter.toString());
+    }
+
+    public void testLongs() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.value(0);
+        jsonWriter.value(1);
+        jsonWriter.value(-1);
+        jsonWriter.value(Long.MIN_VALUE);
+        jsonWriter.value(Long.MAX_VALUE);
+        jsonWriter.endArray();
+        jsonWriter.close();
+        assertEquals("[0,"
+                + "1,"
+                + "-1,"
+                + "-9223372036854775808,"
+                + "9223372036854775807]", stringWriter.toString());
+    }
+
+    public void testBooleans() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.value(true);
+        jsonWriter.value(false);
+        jsonWriter.endArray();
+        assertEquals("[true,false]", stringWriter.toString());
+    }
+
+    public void testNulls() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.nullValue();
+        jsonWriter.endArray();
+        assertEquals("[null]", stringWriter.toString());
+    }
+
+    public void testStrings() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.value("a");
+        jsonWriter.value("a\"");
+        jsonWriter.value("\"");
+        jsonWriter.value(":");
+        jsonWriter.value(",");
+        jsonWriter.value("\b");
+        jsonWriter.value("\f");
+        jsonWriter.value("\n");
+        jsonWriter.value("\r");
+        jsonWriter.value("\t");
+        jsonWriter.value(" ");
+        jsonWriter.value("\\");
+        jsonWriter.value("{");
+        jsonWriter.value("}");
+        jsonWriter.value("[");
+        jsonWriter.value("]");
+        jsonWriter.value("\0");
+        jsonWriter.value("\u0019");
+        jsonWriter.endArray();
+        assertEquals("[\"a\","
+                + "\"a\\\"\","
+                + "\"\\\"\","
+                + "\":\","
+                + "\",\","
+                + "\"\\b\","
+                + "\"\\f\","
+                + "\"\\n\","
+                + "\"\\r\","
+                + "\"\\t\","
+                + "\" \","
+                + "\"\\\\\","
+                + "\"{\","
+                + "\"}\","
+                + "\"[\","
+                + "\"]\","
+                + "\"\\u0000\","
+                + "\"\\u0019\"]", stringWriter.toString());
+    }
+
+    public void testEmptyArray() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.endArray();
+        assertEquals("[]", stringWriter.toString());
+    }
+
+    public void testEmptyObject() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.endObject();
+        assertEquals("{}", stringWriter.toString());
+    }
+
+    public void testObjectsInArrays() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        jsonWriter.beginObject();
+        jsonWriter.name("a").value(5);
+        jsonWriter.name("b").value(false);
+        jsonWriter.endObject();
+        jsonWriter.beginObject();
+        jsonWriter.name("c").value(6);
+        jsonWriter.name("d").value(true);
+        jsonWriter.endObject();
+        jsonWriter.endArray();
+        assertEquals("[{\"a\":5,\"b\":false},"
+                + "{\"c\":6,\"d\":true}]", stringWriter.toString());
+    }
+
+    public void testArraysInObjects() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a");
+        jsonWriter.beginArray();
+        jsonWriter.value(5);
+        jsonWriter.value(false);
+        jsonWriter.endArray();
+        jsonWriter.name("b");
+        jsonWriter.beginArray();
+        jsonWriter.value(6);
+        jsonWriter.value(true);
+        jsonWriter.endArray();
+        jsonWriter.endObject();
+        assertEquals("{\"a\":[5,false],"
+                + "\"b\":[6,true]}", stringWriter.toString());
+    }
+
+    public void testDeepNestingArrays() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        for (int i = 0; i < 20; i++) {
+            jsonWriter.beginArray();
+        }
+        for (int i = 0; i < 20; i++) {
+            jsonWriter.endArray();
+        }
+        assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringWriter.toString());
+    }
+
+    public void testDeepNestingObjects() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        for (int i = 0; i < 20; i++) {
+            jsonWriter.name("a");
+            jsonWriter.beginObject();
+        }
+        for (int i = 0; i < 20; i++) {
+            jsonWriter.endObject();
+        }
+        jsonWriter.endObject();
+        assertEquals("{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":"
+                + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{"
+                + "}}}}}}}}}}}}}}}}}}}}}", stringWriter.toString());
+    }
+
+    public void testRepeatedName() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginObject();
+        jsonWriter.name("a").value(true);
+        jsonWriter.name("a").value(false);
+        jsonWriter.endObject();
+        // JsonWriter doesn't attempt to detect duplicate names
+        assertEquals("{\"a\":true,\"a\":false}", stringWriter.toString());
+    }
+
+    public void testPrettyPrintObject() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.setIndent("   ");
+
+        jsonWriter.beginObject();
+        jsonWriter.name("a").value(true);
+        jsonWriter.name("b").value(false);
+        jsonWriter.name("c").value(5.0);
+        jsonWriter.name("e").nullValue();
+        jsonWriter.name("f").beginArray();
+        jsonWriter.value(6.0);
+        jsonWriter.value(7.0);
+        jsonWriter.endArray();
+        jsonWriter.name("g").beginObject();
+        jsonWriter.name("h").value(8.0);
+        jsonWriter.name("i").value(9.0);
+        jsonWriter.endObject();
+        jsonWriter.endObject();
+
+        String expected = "{\n"
+                + "   \"a\": true,\n"
+                + "   \"b\": false,\n"
+                + "   \"c\": 5.0,\n"
+                + "   \"e\": null,\n"
+                + "   \"f\": [\n"
+                + "      6.0,\n"
+                + "      7.0\n"
+                + "   ],\n"
+                + "   \"g\": {\n"
+                + "      \"h\": 8.0,\n"
+                + "      \"i\": 9.0\n"
+                + "   }\n"
+                + "}";
+        assertEquals(expected, stringWriter.toString());
+    }
+
+    public void testPrettyPrintArray() throws IOException {
+        StringWriter stringWriter = new StringWriter();
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.setIndent("   ");
+
+        jsonWriter.beginArray();
+        jsonWriter.value(true);
+        jsonWriter.value(false);
+        jsonWriter.value(5.0);
+        jsonWriter.nullValue();
+        jsonWriter.beginObject();
+        jsonWriter.name("a").value(6.0);
+        jsonWriter.name("b").value(7.0);
+        jsonWriter.endObject();
+        jsonWriter.beginArray();
+        jsonWriter.value(8.0);
+        jsonWriter.value(9.0);
+        jsonWriter.endArray();
+        jsonWriter.endArray();
+
+        String expected = "[\n"
+                + "   true,\n"
+                + "   false,\n"
+                + "   5.0,\n"
+                + "   null,\n"
+                + "   {\n"
+                + "      \"a\": 6.0,\n"
+                + "      \"b\": 7.0\n"
+                + "   },\n"
+                + "   [\n"
+                + "      8.0,\n"
+                + "      9.0\n"
+                + "   ]\n"
+                + "]";
+        assertEquals(expected, stringWriter.toString());
+    }
+}
diff --git a/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/AccessibilityInjectorTest.java b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java
new file mode 100644
index 0000000..9c9d9fe
--- /dev/null
+++ b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java
@@ -0,0 +1,945 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+/**
+ * This is a test for the behavior of the {@link AccessibilityInjector}
+ * which is used by {@link WebView} to provide basic accessibility support
+ * in case JavaScript is disabled.
+ * </p>
+ * Note: This test works against the generated {@link AccessibilityEvent}s
+ *       to so it also checks if the test for announcing navigation axis and
+ *       status messages as appropriate.
+ */
+public class AccessibilityInjectorTest extends AndroidTestCase {
+
+    /** The timeout to wait for the expected selection. */
+    private static final long TIMEOUT_WAIT_FOR_SELECTION_STRING = 1000;
+
+    /** The timeout to wait for accessibility and the mock service to be enabled. */
+    private static final long TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE = 500;
+
+    /** The count of tests to detect when to shut down the service. */
+    private static final int TEST_CASE_COUNT = 8;
+
+    /** The meta state for pressed left ALT. */
+    private static final int META_STATE_ALT_LEFT_ON = KeyEvent.META_ALT_ON
+            | KeyEvent.META_ALT_LEFT_ON;
+
+    /** The value for not specified selection string since null is a valid value. */
+    private static final String SELECTION_STRING_UNKNOWN = "Unknown";
+
+    /** Lock for locking the test. */
+    private static final Object sTestLock = new Object();
+
+    /** Handle to the test for use by the mock service. */
+    private static AccessibilityInjectorTest sInstance;
+
+    /** Flag indicating if the accessibility service is ready to receive events. */
+    private static boolean sIsAccessibilityServiceReady;
+
+    /** The count of executed tests to detect when to toggle accessibility and the service. */
+    private static int sExecutedTestCount;
+
+    /** Worker thread with a handler to perform non test thread processing. */
+    private Worker mWorker;
+
+    /** Handle to the {@link WebView} to load data in. */
+    private WebView mWebView;
+
+    /** The received selection string for assertion checking. */
+    private static String sReceivedSelectionString = SELECTION_STRING_UNKNOWN;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mWorker = new Worker();
+        sInstance = this;
+        if (sExecutedTestCount == 0) {
+            // until JUnit4 comes to play with @BeforeTest
+            disableAccessibilityAndMockAccessibilityService();
+            enableAccessibilityAndMockAccessibilityService();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mWorker != null) {
+            mWorker.stop();
+        }
+        if (sExecutedTestCount == TEST_CASE_COUNT) {
+            // until JUnit4 comes to play with @AfterTest
+            disableAccessibilityAndMockAccessibilityService();
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Tests navigation by character.
+     */
+    @LargeTest
+    public void testNavigationByCharacter() throws Exception {
+        // a bit ugly but helps detect beginning and end of all tests so accessibility
+        // and the mock service are not toggled on every test (expensive)
+        sExecutedTestCount++;
+
+        String html =
+            "<html>" +
+               "<head>" +
+               "</head>" +
+               "<body>" +
+                   "<p>" +
+                      "a <b>b</b> c" +
+                   "</p>" +
+                   "<p>" +
+                     "d" +
+                     "<input>e</input>" +
+                   "</p>" +
+               "</body>" +
+             "</html>";
+
+        WebView webView = createWebVewWithHtml(html);
+
+        // change navigation axis to word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("1"); // expect the word navigation axis
+
+        // change navigation axis to character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("0"); // expect the character navigation axis
+
+        // go to the first character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("a");
+
+        // go to the second character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<b>b</b>");
+
+        // go to the third character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("c");
+
+        // go to the fourth character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("d");
+
+        // go to the fifth character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("e");
+
+        // try to go past the last character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString(null);
+
+        // go to the fourth character (reverse)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("d");
+
+        // go to the third character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("c");
+
+        // go to the second character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<b>b</b>");
+
+        // go to the first character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("a");
+
+        // try to go before the first character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString(null);
+
+        // go to the second character (reverse again)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<b>b</b>");
+    }
+
+    /**
+     * Tests navigation by word.
+     */
+    @LargeTest
+    public void testNavigationByWord() throws Exception {
+        // a bit ugly but helps detect beginning and end of all tests so accessibility
+        // and the mock service are not toggled on every test (expensive)
+        sExecutedTestCount++;
+
+        String html =
+            "<html>" +
+               "<head>" +
+               "</head>" +
+               "<body>" +
+                   "<p>" +
+                      "This is <b>a</b> sentence" +
+                   "</p>" +
+                   "<p>" +
+                     " scattered " +
+                     "<input>all</input>" +
+                     " over " +
+                   "</p>" +
+                   "<div>" +
+                     "<button>the place.</button>" +
+                   "</div>" +
+               "</body>" +
+             "</html>";
+
+        WebView webView = createWebVewWithHtml(html);
+
+        // change navigation axis to word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("1"); // expect the word navigation axis
+
+        // go to the first word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("This");
+
+        // go to the second word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("is");
+
+        // go to the third word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<b>a</b>");
+
+        // go to the fourth word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("sentence");
+
+        // go to the fifth word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("scattered");
+
+        // go to the sixth word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("all");
+
+        // go to the seventh word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("over");
+
+        // go to the eight word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("the");
+
+        // go to the ninth word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("place");
+
+        // NOTE: WebKit selection returns the dot as a word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString(".");
+
+        // try to go past the last word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString(null);
+
+        // go to the last word (reverse)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("place");
+
+        // go to the eight word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("the");
+
+        // go to the seventh word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("over");
+
+        // go to the sixth word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("all");
+
+        // go to the fifth word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("scattered");
+
+        // go to the fourth word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("sentence");
+
+        // go to the third word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<b>a</b>");
+
+        // go to the second word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("is");
+
+        // go to the first word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("This");
+
+        // try to go before the first word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString(null);
+
+        // go to the second word (reverse again)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("is");
+    }
+
+    /**
+     * Tests navigation by sentence.
+     */
+    @LargeTest
+    public void testNavigationBySentence() throws Exception {
+        // a bit ugly but helps detect beginning and end of all tests so accessibility
+        // and the mock service are not toggled on every test (expensive)
+        sExecutedTestCount++;
+
+        String html =
+            "<html>" +
+              "<head>" +
+              "</head>" +
+              "<body>" +
+                "<div>" +
+                  "<p>" +
+                    "This is the first sentence of the first paragraph and has an <b>inline bold tag</b>." +
+                    "This is the second sentence of the first paragraph." +
+                  "</p>" +
+                  "<h1>This is a heading</h1>" +
+                  "<p>" +
+                    "This is the first sentence of the second paragraph." +
+                    "This is the second sentence of the second paragraph." +
+                  "</p>" +
+                "</div>" +
+              "</body>" +
+            "</html>";
+
+        WebView webView = createWebVewWithHtml(html);
+
+        // Sentence axis is the default
+
+        // go to the first sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("This is the first sentence of the first paragraph and has an "
+                + "<b>inline bold tag</b>.");
+
+        // go to the second sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("This is the second sentence of the first paragraph.");
+
+        // go to the third sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("This is a heading");
+
+        // go to the fourth sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("This is the first sentence of the second paragraph.");
+
+        // go to the fifth sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("This is the second sentence of the second paragraph.");
+
+        // try to go past the last sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString(null);
+
+        // go to the fourth sentence (reverse)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("This is the first sentence of the second paragraph.");
+
+        // go to the third sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("This is a heading");
+
+        // go to the second sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("This is the second sentence of the first paragraph.");
+
+        // go to the first sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("This is the first sentence of the first paragraph and has an "
+                + "<b>inline bold tag</b>.");
+
+        // try to go before the first sentence
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString(null);
+
+        // go to the second sentence (reverse again)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("This is the second sentence of the first paragraph.");
+    }
+
+    /**
+     * Tests navigation by heading.
+     */
+    @LargeTest
+    public void testNavigationByHeading() throws Exception {
+        // a bit ugly but helps detect beginning and end of all tests so accessibility
+        // and the mock service are not toggled on every test (expensive)
+        sExecutedTestCount++;
+
+        String html =
+            "<!DOCTYPE html>" +
+            "<html>" +
+              "<head>" +
+              "</head>" +
+              "<body>" +
+                "<h1>Heading one</h1>" +
+                "<p>" +
+                  "This is some text" +
+                "</p>" +
+                "<h2>Heading two</h2>" +
+                "<p>" +
+                  "This is some text" +
+                "</p>" +
+                "<h3>Heading three</h3>" +
+                "<p>" +
+                  "This is some text" +
+                "</p>" +
+                "<h4>Heading four</h4>" +
+                "<p>" +
+                  "This is some text" +
+                "</p>" +
+                "<h5>Heading five</h5>" +
+                "<p>" +
+                  "This is some text" +
+                "</p>" +
+                "<h6>Heading six</h6>" +
+              "</body>" +
+            "</html>";
+
+        WebView webView = createWebVewWithHtml(html);
+
+        // change navigation axis to heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("3"); // expect the heading navigation axis
+
+        // go to the first heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<h1>Heading one</h1>");
+
+        // go to the second heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<h2>Heading two</h2>");
+
+        // go to the third heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<h3>Heading three</h3>");
+
+        // go to the fourth heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<h4>Heading four</h4>");
+
+        // go to the fifth heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<h5>Heading five</h5>");
+
+        // go to the sixth heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<h6>Heading six</h6>");
+
+        // try to go past the last heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString(null);
+
+        // go to the fifth heading (reverse)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<h5>Heading five</h5>");
+
+        // go to the fourth heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<h4>Heading four</h4>");
+
+        // go to the third heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<h3>Heading three</h3>");
+
+        // go to the second heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<h2>Heading two</h2>");
+
+        // go to the first heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<h1>Heading one</h1>");
+
+        // try to go before the first heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString(null);
+
+        // go to the second heading (reverse again)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<h2>Heading two</h2>");
+    }
+
+    /**
+     * Tests navigation by sibling.
+     */
+    @LargeTest
+    public void testNavigationBySibing() throws Exception {
+        // a bit ugly but helps detect beginning and end of all tests so accessibility
+        // and the mock service are not toggled on every test (expensive)
+        sExecutedTestCount++;
+
+        String html =
+            "<!DOCTYPE html>" +
+            "<html>" +
+              "<head>" +
+              "</head>" +
+              "<body>" +
+                "<h1>Heading one</h1>" +
+                "<p>" +
+                  "This is some text" +
+                "</p>" +
+                "<div>" +
+                  "<button>Input</button>" +
+                "</div>" +
+              "</body>" +
+            "</html>";
+
+        WebView webView = createWebVewWithHtml(html);
+
+        // change navigation axis to heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("3"); // expect the heading navigation axis
+
+        // change navigation axis to sibling
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("4"); // expect the sibling navigation axis
+
+        // change navigation axis to parent/first child
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("5"); // expect the parent/first child navigation axis
+
+        // go to the first child of the body
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<h1>Heading one</h1>");
+
+        // change navigation axis to sibling
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("4"); // expect the sibling navigation axis
+
+        // go to the next sibling
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<p>This is some text</p>");
+
+        // go to the next sibling
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<div><button>Input</button></div>");
+
+        // try to go past the last sibling
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString(null);
+
+        // go to the previous sibling (reverse)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<p>This is some text</p>");
+
+        // go to the previous sibling
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<h1>Heading one</h1>");
+
+        // try to go before the previous sibling
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString(null);
+
+        // go to the next sibling (reverse again)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<p>This is some text</p>");
+    }
+
+    /**
+     * Tests navigation by parent/first child.
+     */
+    @LargeTest
+    public void testNavigationByParentFirstChild() throws Exception {
+        // a bit ugly but helps detect beginning and end of all tests so accessibility
+        // and the mock service are not toggled on every test (expensive)
+        sExecutedTestCount++;
+
+        String html =
+            "<!DOCTYPE html>" +
+            "<html>" +
+              "<head>" +
+              "</head>" +
+              "<body>" +
+                "<div>" +
+                  "<button>Input</button>" +
+                "</div>" +
+              "</body>" +
+            "</html>";
+
+        WebView webView = createWebVewWithHtml(html);
+
+        // change navigation axis to document
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("6"); // expect the document navigation axis
+
+        // change navigation axis to parent/first child
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("5"); // expect the parent/first child navigation axis
+
+        // go to the first child
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<div><button>Input</button></div>");
+
+        // go to the first child
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<button>Input</button>");
+
+        // try to go to the first child of a leaf element
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString(null);
+
+        // go to the parent (reverse)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<div><button>Input</button></div>");
+
+        // go to the parent
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<body><div><button>Input</button></div></body>");
+
+        // try to go to the body parent
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString(null);
+
+        // go to the first child (reverse again)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<div><button>Input</button></div>");
+    }
+
+    /**
+     * Tests navigation by document.
+     */
+    @LargeTest
+    public void testNavigationByDocument() throws Exception {
+        // a bit ugly but helps detect beginning and end of all tests so accessibility
+        // and the mock service are not toggled on every test (expensive)
+        sExecutedTestCount++;
+
+        String html =
+            "<!DOCTYPE html>" +
+            "<html>" +
+              "<head>" +
+              "</head>" +
+              "<body>" +
+                "<button>Click</button>" +
+              "</body>" +
+            "</html>";
+
+        WebView webView = createWebVewWithHtml(html);
+
+        // change navigation axis to document
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("6"); // expect the document navigation axis
+
+        // go to the bottom of the document
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("Click");
+
+        // go to the top of the document (reverse)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+        assertSelectionString("<body><button>Click</button></body>");
+
+        // go to the bottom of the document (reverse again)
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("Click");
+    }
+
+    /**
+     * Tests the sync between the text navigation and navigation by DOM elements.
+     */
+    @LargeTest
+    public void testSyncBetweenTextAndDomNodeNavigation() throws Exception {
+        // a bit ugly but helps detect beginning and end of all tests so accessibility
+        // and the mock service are not toggled on every test (expensive)
+        sExecutedTestCount++;
+
+        String html =
+            "<!DOCTYPE html>" +
+            "<html>" +
+              "<head>" +
+              "</head>" +
+              "<body>" +
+                "<p>" +
+                  "First" +
+                "</p>" +
+                "<button>Second</button>" +
+                "<p>" +
+                  "Third" +
+                "</p>" +
+              "</body>" +
+            "</html>";
+
+        WebView webView = createWebVewWithHtml(html);
+
+        // change navigation axis to word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("1"); // expect the word navigation axis
+
+        // go to the first word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("First");
+
+        // change navigation axis to heading
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("3"); // expect the heading navigation axis
+
+        // change navigation axis to sibling
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("4"); // expect the sibling navigation axis
+
+        // go to the next sibling
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("<button>Second</button>");
+
+        // change navigation axis to character
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("0"); // expect the character navigation axis
+
+        // change navigation axis to word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON);
+        assertSelectionString("1"); // expect the word navigation axis
+
+        // go to the next word
+        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+        assertSelectionString("Third");
+    }
+
+    /**
+     * Enable accessibility and the mock accessibility service.
+     */
+    private void enableAccessibilityAndMockAccessibilityService() {
+        // make sure the manager is instantiated so the system initializes it
+        AccessibilityManager.getInstance(getContext());
+
+        // enable accessibility and the mock accessibility service
+        Settings.Secure.putInt(getContext().getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED, 1);
+        String enabledServices = new ComponentName(getContext().getPackageName(),
+                MockAccessibilityService.class.getName()).flattenToShortString();
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices);
+
+        // poll within a timeout and let be interrupted in case of success
+        long incrementStep = TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE / 5;
+        long start = SystemClock.uptimeMillis();
+        while (SystemClock.uptimeMillis() - start < TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE &&
+                !sIsAccessibilityServiceReady) {
+            synchronized (sTestLock) {
+                try {
+                    sTestLock.wait(incrementStep);
+                } catch (InterruptedException ie) {
+                    /* ignore */
+                }
+            }
+        }
+
+        if (!sIsAccessibilityServiceReady) {
+            throw new IllegalStateException("MockAccessibilityService not ready. Did you add " +
+                    "tests and forgot to update AccessibilityInjectorTest#TEST_CASE_COUNT?");
+        }
+    }
+
+    @Override
+    protected void scrubClass(Class<?> testCaseClass) {
+        /* do nothing - avoid superclass behavior */
+    }
+
+    /**
+     * Disables accessibility and the mock accessibility service.
+     */
+    private void disableAccessibilityAndMockAccessibilityService() {
+        // disable accessibility and the mock accessibility service
+        Settings.Secure.putInt(getContext().getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED, 0);
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
+    }
+
+    /**
+     * Asserts the next <code>expectedSelectionString</code> to be received.
+     */
+    private void assertSelectionString(String expectedSelectionString) {
+        assertTrue("MockAccessibilityService not ready", sIsAccessibilityServiceReady);
+
+        long incrementStep = TIMEOUT_WAIT_FOR_SELECTION_STRING / 5;
+        long start = SystemClock.uptimeMillis();
+        while (SystemClock.uptimeMillis() - start < TIMEOUT_WAIT_FOR_SELECTION_STRING &&
+                sReceivedSelectionString == SELECTION_STRING_UNKNOWN) {
+            synchronized (sTestLock) {
+                try {
+                    sTestLock.wait(incrementStep);
+                } catch (InterruptedException ie) {
+                    /* ignore */
+                }
+            }
+        }
+        try {
+            if (sReceivedSelectionString == SELECTION_STRING_UNKNOWN) {
+                fail("No selection string received. Expected: " + expectedSelectionString);
+            }
+            assertEquals(expectedSelectionString, sReceivedSelectionString);
+        } finally {
+            sReceivedSelectionString = SELECTION_STRING_UNKNOWN;
+        }
+    }
+
+    /**
+     * Sends a {@link KeyEvent} (up and down) to the {@link WebView}.
+     *
+     * @param keyCode The event key code.
+     */
+    private void sendKeyEvent(WebView webView, int keyCode, int metaState) {
+        webView.onKeyDown(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 1, metaState));
+        webView.onKeyUp(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 1, metaState));
+    }
+
+    /**
+     * Creates a {@link WebView} with with a given HTML content.
+     *
+     * @param html The HTML content;
+     * @return The created view.
+     */
+    private WebView createWebVewWithHtml(final String html) {
+        mWorker.getHandler().post(new Runnable() {
+            public void run() {
+                mWebView = new WebView(getContext());
+                mWebView.loadData(html, "text/html", "utf-8");
+                mWebView.setWebViewClient(new WebViewClient() {
+                    @Override
+                    public void onPageFinished(WebView view, String url) {
+                        mWorker.getHandler().post(new Runnable() {
+                            public void run() {
+                                synchronized (sTestLock) {
+                                    sTestLock.notifyAll();
+                                }
+                            }
+                        });
+                    }
+                });
+            }
+        });
+        synchronized (sTestLock) {
+            try {
+                sTestLock.wait();
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+        }
+        return mWebView;
+    }
+
+    /**
+     * This is a worker thread responsible for creating the {@link WebView}.
+     */
+    private class Worker implements Runnable {
+        private final Object mWorkerLock = new Object();
+        private Handler mHandler;
+
+       public Worker() {
+            new Thread(this).start();
+            synchronized (mWorkerLock) {
+                while (mHandler == null) {
+                    try {
+                        mWorkerLock.wait();
+                    } catch (InterruptedException ex) {
+                        /* ignore */
+                    }
+                }
+            }
+        }
+
+        public void run() {
+            synchronized (mWorkerLock) {
+                Looper.prepare();
+                mHandler = new Handler();
+                mWorkerLock.notifyAll();
+            }
+            Looper.loop();
+        }
+
+        public Handler getHandler() {
+            return mHandler;
+        }
+
+        public void stop() {
+            mHandler.getLooper().quit();
+        }
+    }
+
+    /**
+     * Mock accessibility service to receive the accessibility events
+     * with the current {@link WebView} selection.
+     */
+    public static class MockAccessibilityService extends AccessibilityService {
+        private boolean mIsServiceInfoSet;
+
+        @Override
+        protected void onServiceConnected() {
+            if (mIsServiceInfoSet) {
+                return;
+            }
+            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+            info.eventTypes = AccessibilityEvent.TYPE_VIEW_SELECTED;
+            info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
+            setServiceInfo(info);
+            mIsServiceInfoSet = true;
+
+            sIsAccessibilityServiceReady = true;
+
+            if (sInstance == null) {
+                return;
+            }
+            synchronized (sTestLock) {
+                sTestLock.notifyAll();
+            }
+        }
+
+        @Override
+        public void onAccessibilityEvent(AccessibilityEvent event) {
+            if (sInstance == null) {
+                return;
+            }
+            if (!event.getText().isEmpty()) {
+                CharSequence text = event.getText().get(0);
+                sReceivedSelectionString = (text != null) ? text.toString() : null;
+            }
+            synchronized (sTestLock) {
+                sTestLock.notifyAll();
+            }
+        }
+
+        @Override
+        public void onInterrupt() {
+            /* do nothing */
+        }
+
+        @Override
+        public boolean onUnbind(Intent intent) {
+            sIsAccessibilityServiceReady = false;
+            return false;
+        }
+    }
+}
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 ebe13e1..c698e99 100644
--- a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
+++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
@@ -152,7 +152,11 @@
         }
 
         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 6f8afff..439fc90 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/data/etc/tablet_core_hardware.xml b/data/etc/tablet_core_hardware.xml
new file mode 100644
index 0000000..952d078
--- /dev/null
+++ b/data/etc/tablet_core_hardware.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.
+-->
+
+<!-- These are the hardware components that all handheld devices
+     must include. Devices with optional hardware must also include extra
+     hardware files, per the comments below.
+
+     Handheld devices include phones, mobile Internet devices (MIDs),
+     Personal Media Players (PMPs), small tablets (7" or less), and similar
+     devices.
+-->
+<permissions>
+    <feature name="android.hardware.location" />
+    <feature name="android.hardware.location.network" />
+    <feature name="android.hardware.sensor.compass" />
+    <feature name="android.hardware.sensor.accelerometer" />
+    <feature name="android.hardware.bluetooth" />
+    <feature name="android.hardware.touchscreen" />
+    <feature name="android.hardware.touchscreen.multitouch" />
+    <feature name="android.hardware.touchscreen.multitouch.distinct" />
+    <feature name="android.hardware.microphone" />
+    <!-- devices with GPS must include android.hardware.location.gps.xml -->
+    <!-- devices with a rear-facing camera must include one of these as appropriate:
+         android.hardware.camera.xml or 
+         android.hardware.camera.autofocus.xml or 
+         android.hardware.camera.autofocus-flash.xml -->
+    <!-- devices with a front facing camera must include
+         android.hardware.camera.front.xml -->
+    <!-- devices with WiFi must also include android.hardware.wifi.xml -->
+    <!-- devices with an ambient light sensor must also include
+         android.hardware.sensor.light.xml -->
+    <!-- devices with a proximity sensor must also include
+         android.hardware.sensor.proximity.xml -->
+    <!-- devices with a barometer must also include
+         android.hardware.sensor.barometer.xml -->
+    <!-- devices with a gyroscope must also include
+         android.hardware.sensor.gyroscope.xml -->
+    <!-- GSM phones must also include android.hardware.telephony.gsm.xml -->
+    <!-- CDMA phones must also include android.hardware.telephony.cdma.xml -->
+</permissions>
diff --git a/data/fonts/Ahem.ttf b/data/fonts/Ahem.ttf
deleted file mode 100644
index 17e6c60..0000000
--- a/data/fonts/Ahem.ttf
+++ /dev/null
Binary files differ
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
deleted file mode 100644
index 4c40646..0000000
--- a/data/fonts/Android.mk
+++ /dev/null
@@ -1,39 +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)
-
-copy_from :=                \
-    DroidSans.ttf           \
-    DroidSans-Bold.ttf      \
-    DroidSansArabic.ttf     \
-    DroidSansHebrew.ttf     \
-    DroidSansThai.ttf       \
-    DroidSerif-Regular.ttf  \
-    DroidSerif-Bold.ttf     \
-    DroidSerif-Italic.ttf   \
-    DroidSerif-BoldItalic.ttf   \
-    DroidSansMono.ttf        \
-    Clockopia.ttf
-
-ifneq ($(NO_FALLBACK_FONT),true)
-ifeq ($(filter %system/fonts/DroidSansFallback.ttf,$(PRODUCT_COPY_FILES)),)
-    # if the product makefile has set the the fallback font, don't override it.
-    copy_from += DroidSansFallback.ttf
-endif
-endif
-
-copy_file_pairs := $(foreach cf,$(copy_from),$(LOCAL_PATH)/$(cf):system/fonts/$(cf))
-PRODUCT_COPY_FILES += $(copy_file_pairs)
diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk
new file mode 100644
index 0000000..34d0331
--- /dev/null
+++ b/data/fonts/fonts.mk
@@ -0,0 +1,29 @@
+# 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.
+
+# Warning: this is actually a product definition, to be inherited from
+
+PRODUCT_COPY_FILES := \
+    frameworks/base/data/fonts/DroidSans.ttf:system/fonts/DroidSans.ttf \
+    frameworks/base/data/fonts/DroidSans-Bold.ttf:system/fonts/DroidSans-Bold.ttf \
+    frameworks/base/data/fonts/DroidSansArabic.ttf:system/fonts/DroidSansArabic.ttf \
+    frameworks/base/data/fonts/DroidSansHebrew.ttf:system/fonts/DroidSansHebrew.ttf \
+    frameworks/base/data/fonts/DroidSansThai.ttf:system/fonts/DroidSansThai.ttf \
+    frameworks/base/data/fonts/DroidSerif-Regular.ttf:system/fonts/DroidSerif-Regular.ttf \
+    frameworks/base/data/fonts/DroidSerif-Bold.ttf:system/fonts/DroidSerif-Bold.ttf \
+    frameworks/base/data/fonts/DroidSerif-Italic.ttf:system/fonts/DroidSerif-Italic.ttf \
+    frameworks/base/data/fonts/DroidSerif-BoldItalic.ttf:system/fonts/DroidSerif-BoldItalic.ttf \
+    frameworks/base/data/fonts/DroidSansMono.ttf:system/fonts/DroidSansMono.ttf \
+    frameworks/base/data/fonts/Clockopia.ttf:system/fonts/Clockopia.ttf \
+    frameworks/base/data/fonts/DroidSansFallback.ttf:system/fonts/DroidSansFallback.ttf
diff --git a/docs/html/guide/developing/testing/index.jd b/docs/html/guide/developing/testing/index.jd
index ea61cc3..2164705 100644
--- a/docs/html/guide/developing/testing/index.jd
+++ b/docs/html/guide/developing/testing/index.jd
@@ -1,6 +1,5 @@
 page.title=Testing Overview
 @jd:body
-
 <p>
     Android includes powerful tools for setting up and running test applications.
     Whether you are working in Eclipse with ADT or working from the command line, these tools
@@ -9,7 +8,7 @@
 </p>
 <p>
     If you aren't yet familiar with the Android testing framework, please read the topic
-    <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing and Instrumentation</a>
+    <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Fundamentals</a>
     before you get started.
     For a step-by-step introduction to Android testing, try the <a
     href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">Hello, Testing</a>
diff --git a/docs/html/guide/developing/testing/testing_eclipse.jd b/docs/html/guide/developing/testing/testing_eclipse.jd
index da1c0f0..ba7eaba 100644
--- a/docs/html/guide/developing/testing/testing_eclipse.jd
+++ b/docs/html/guide/developing/testing/testing_eclipse.jd
@@ -1,28 +1,23 @@
 page.title=Testing In Eclipse, with ADT
 @jd:body
-
 <div id="qv-wrapper">
-  <div id="qv">
-  <h2>In this document</h2>
-  <ol>
-    <li><a href="#CreateTestProjectEclipse">Creating a Test Project</a></li>
-    <li><a href="#CreateTestAppEclipse">Creating a Test Application</a></li>
-    <li><a href="#RunTestEclipse">Running Tests</a></li>
-  </ol>
-  </div>
+    <div id="qv">
+        <h2>In this document</h2>
+            <ol>
+                <li><a href="#CreateTestProjectEclipse">Creating a Test Project</a></li>
+                <li><a href="#CreateTestAppEclipse">Creating a Test Package</a></li>
+                <li><a href="#RunTestEclipse">Running Tests</a></li>
+            </ol>
+    </div>
 </div>
 <p>
-  This topic explains how create and run tests of Android applications in Eclipse with ADT.
-
-  with the basic processes for creating and running applications with ADT, as described in
-  <a href="{@docRoot}guide/developing/eclipse-adt.html">Developing In Eclipse, with ADT</a>.
-
-  Before you read this topic, you should read about how to create a Android application with the
-  basic processes for creating and running applications with ADT, as described in
-  <a href="{@docRoot}guide/developing/eclipse-adt.html">Developing In Eclipse, with ADT</a>.
-  You may also want to read
-  <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing and Instrumentation</a>,
-  which provides an overview of the Android testing framework.
+    This topic explains how create and run tests of Android applications in Eclipse with ADT.
+    Before you read this topic, you should read about how to create a Android application with the
+    basic processes for creating and running applications with ADT, as described in
+    <a href="{@docRoot}guide/developing/eclipse-adt.html">Developing In Eclipse, with ADT</a>.
+    You may also want to read
+    <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Fundamentals</a>,
+    which provides an overview of the Android testing framework.
 </p>
 <p>
     ADT provides several features that help you set up and manage your testing environment
@@ -32,20 +27,20 @@
         <li>
             It lets you quickly create a test project and link it to the application under test.
             When it creates the test project, it automatically inserts the necessary
-            <code>&lt;instrumentation&gt;</code> element in the test application's manifest file.
+            <code>&lt;instrumentation&gt;</code> element in the test package's manifest file.
         </li>
         <li>
             It lets you quickly import the classes of the application under test, so that your
             tests can inspect them.
         </li>
         <li>
-            It lets you create run configurations for your test application and include in
+            It lets you create run configurations for your test package and include in
             them flags that are passed to the Android testing framework.
         </li>
         <li>
-            It lets you run your test application without leaving Eclipse. ADT builds both the
-            application under test and the test application automatically, installs them if
-            necessary to your device or emulator, runs the test application, and displays the
+            It lets you run your test package without leaving Eclipse. ADT builds both the
+            application under test and the test package automatically, installs them if
+            necessary to your device or emulator, runs the test package, and displays the
             results in a separate window in Eclipse.
         </li>
     </ul>
@@ -55,305 +50,452 @@
     <a href="{@docRoot}guide/developing/testing/testing_otheride.html">Testing in Other IDEs</a>.
 </p>
 <h2 id="CreateTestProjectEclipse">Creating a Test Project</h2>
-  <p>
+<p>
     To set up a test environment for your Android application, you must first create a separate
-    application project that holds the test code. The new project follows the directory structure
+    project that holds the test code. The new project follows the directory structure
     used for any Android application. It includes the same types of content and files, such as
-    source code, resources, a manifest file, and so forth. The test application you
+    source code, resources, a manifest file, and so forth. The test package you
     create is connected to the application under test by an
     <a href="{@docRoot}guide/topics/manifest/instrumentation-element.html">
     <code>&lt;instrumentation&gt;</code></a> element in its manifest file.
-  </p>
-  <p>
-    The <strong>New Android Test Project</strong> dialog makes it easy for you to generate a
-    new test project that has the proper structure, including the
-    <code>&lt;instrumentation&gt;</code> element in the manifest file. You can use the New Android
-    Test Project dialog to generate the test project at any time. The dialog appears just after you
-    create a new Android main application project, but you can also run it to create a test project
-    for a project that you created previously.
-  </p>
+</p>
 <p>
-  To create a test project in Eclipse with ADT:
+    The <em>New Android Test Project</em> dialog makes it easy for you to generate a
+    new test project that has the proper structure, including the
+    <code>&lt;instrumentation&gt;</code> element in the manifest file. You can use the New
+    Android Test Project dialog to generate the test project at any time. The dialog appears
+    just after you create a new Android main application project, but you can also run it to
+    create a test project for a project that you created previously.
+</p>
+<p>
+    To create a test project in Eclipse with ADT:
 </p>
 <ol>
-  <li>
-    In Eclipse, select <strong>File &gt; New &gt; Other</strong>. This
-    opens the Select a Wizard dialog.
-  </li>
-  <li>
-    In the dialog, in the Wizards drop-down list,
-    find the entry for Android, then click the toggle to the left. Select
-    Android Test Project, then at the bottom
-    of the dialog click Next. The New Android Test Project wizard appears.
-  </li>
-  <li>
-    Enter a project name. You may use any name, but you may want to
-    associate the name with the project name for your Application. One
-    way to do this is to take the Application's project name, append the
-    string "Test" to it, and then use this as the test case project name.
-  </li>
-  <li>
-    In the Test Target panel, set
-    An Existing Android Project, click
-    Browse, then select your Android application from
-    the list. You now see that the wizard has completed the Test
-    Target Package, Application Name, and
-    Package Name fields for you (the latter two are in
-    the Properties panel).
-  </li>
-  <li>
-    In the Build Target panel, select the Android SDK
-    platform that you will use to test your application. Make this the same as the
-    build target of the application under test.
-  </li>
-  <li>
-    Click Finish to complete the wizard. If
-    Finish is disabled, look
-    for error messages at the top of the wizard dialog, and then fix
-    any problems.
-  </li>
+    <li>
+        In Eclipse, select <strong>File &gt; New &gt; Other</strong>. This opens the <em>Select a
+        Wizard</em> dialog.
+    </li>
+    <li>
+        In the dialog, in the <em>Wizards</em> drop-down list, find the entry for Android, then
+        click the toggle to the left. Select <strong>Android Test Project</strong>, then at the
+        bottom of the dialog click <strong>Next</strong>. The <em>New Android Test Project</em>
+        wizard appears.
+    </li>
+    <li>
+        Next to <em>Test Project Name</em>, enter a name for the project. You may use any name,
+        but you may want to associate the name with the project name for the application under test.
+        One way to do this is to take the application's project name, append the string "Test" to
+        it, and then use this as the test package project name.
+        <p>
+            The name becomes part of the suggested project path, but you can change this in the
+            next step.
+        </p>
+    </li>
+    <li>
+        In the <em>Content</em> panel, examine the suggested path to the project.
+        If <em>Use default location</em> is set, then the wizard will suggest a path that is
+        a concatenation of the workspace path and the project name you entered. For example,
+        if your workspace path is <code>/usr/local/workspace</code> and your project name is
+        <code>MyTestApp</code>, then the wizard will suggest
+        <code>/usr/local/workspace/MyTestApp</code>. To enter your own
+        choice for a path, unselect <em>Use default location</em>, then enter or browse to the
+        path where you want your project.
+        <p>
+            To learn more about choosing the location of test projects, please read
+            <a href="{@docRoot}guide/topics/testing/testing_android.html#TestProjectPaths">
+            Testing Fundamentals</a>.
+        </p>
+    </li>
+    <li>
+        In the Test Target panel, set An Existing Android Project, click Browse, then select your
+        Android application from the list. You now see that the wizard has completed the Test
+        Target Package, Application Name, and Package Name fields for you (the latter two are in
+        the Properties panel).
+    </li>
+    <li>
+        In the Build Target panel, select the Android SDK platform that the application under test
+        uses.
+    </li>
+    <li>
+        Click Finish to complete the wizard. If Finish is disabled, look for error messages at the
+        top of the wizard dialog, and then fix any problems.
+    </li>
+</ol>
+<h2 id="CreateTestAppEclipse">Creating a Test Package</h2>
+<p>
+    Once you have created a test project, you populate it with a test package. This package does not
+    require an Activity, although you can define one if you wish. Although your test package can
+    combine Activity classes, test case classes, or ordinary classes, your main test case
+    should extend one of the Android test case classes or JUnit classes, because these provide the
+    best testing features.
+</p>
+<p>
+    Test packages do not need to have an Android GUI. When you run the package in
+    Eclipse with ADT, its results appear in the JUnit view. Running tests and seeing the results is
+    described in more detail in the section <a href="#RunTestEclipse">Running Tests</a>.
+</p>
+
+<p>
+    To create a test package, start with one of Android's test case classes defined in
+    {@link android.test android.test}. These extend the JUnit
+    {@link junit.framework.TestCase TestCase} class. The Android test classes for Activity objects
+    also provide instrumentation for testing an Activity. To learn more about test case
+    classes, please read the topic <a href="{@docRoot}guide/topics/testing/testing_android.html">
+    Testing Fundamentals</a>.
+</p>
+<p>
+    Before you create your test package, you choose the Java package identifier you want to use
+    for your test case classes and the Android package name you want to use. To learn more
+    about this, please read
+    <a href="{@docRoot}guide/topics/testing/testing_android.html#PackageNames">
+    Testing Fundamentals</a>.
+</p>
+<p>
+    To add a test case class to your project:
+</p>
+<ol>
+    <li>
+        In the <em>Project Explorer</em> tab, open your test project, then open the <em>src</em>
+        folder.
+    </li>
+    <li>
+        Find the Java package identifier set by the projection creation wizard. If you haven't
+        added classes yet, this node won't have any children, and its icon will not be filled in.
+        If you want to change the identifier value, right-click the identifier and select
+        <strong>Refactor</strong> &gt; <strong>Rename</strong>, then enter the new name.
+    </li>
+    <li>
+        When you are ready, right-click the Java package identifier again and select
+        <strong>New</strong> &gt; <strong>Class</strong>. This displays the <em>New Java Class</em>
+        dialog, with the <em>Source folder</em> and <em>Package</em> values already set.
+    </li>
+    <li>
+        In the <em>Name</em> field, enter a name for the test case class. One way to choose a
+        class name is to append the string "Test" to the class of the component you are testing.
+        For example, if you are testing the class MyAppActivity, your test case class
+        name would be MyAppActivityTest. Leave the modifiers set to <em>public</em>.
+    </li>
+    <li>
+        In the <em>Superclass</em> field, enter the name of the Android test case class you
+        are extending. You can also browse the available classes.
+    </li>
+    <li>
+        In <em>Which method stubs would you like to create?</em>, unset all the options, then
+        click <strong>Finish</strong>. You will set up the constructor manually.
+    </li>
+    <li>
+        Your new class appears in a new Java editor pane.
+    </li>
 </ol>
 <p>
-
-</p>
-<h2 id="CreateTestAppEclipse">Creating a Test Application</h2>
-<p>
-  Once you have created a test project, you populate it with a test
-  Android application. This application does not require an {@link android.app.Activity Activity},
-  although you can define one if you wish. Although your test application can
-  combine Activities, Android test class extensions, JUnit extensions, or
-  ordinary classes, you should extend one of the Android test classes or JUnit classes,
-  because these provide the best testing features.
+    You now have to ensure that the constructor is set up correctly. Create a constructor for your
+    class that has no arguments; this is required by JUnit. As the first statement in this
+    constructor, add a call to the base class' constructor. Each base test case class has its
+    own constructor signature. Refer to the class documentation in the documentation for
+    {@link android.test} for more information.
 </p>
 <p>
-  Test applications do not have an Android GUI. Instead, when you run the application in
-  Eclipse with ADT, its results appear in the JUnit view. If you run
-  your tests with {@link android.test.InstrumentationTestRunner InstrumentationTestRunner} (or a related test runner),
-  then it will run all the methods in each class. You can modify this behavior
-  by using the {@link junit.framework.TestSuite TestSuite} class.
-</p>
-
-<p>
-  To create a test application, start with one of Android's test classes in the Java package {@link android.test android.test}.
-  These extend the JUnit {@link junit.framework.TestCase TestCase} class. With a few exceptions, the Android test classes
-  also provide instrumentation for testing.
-</p>
-<p>
-  For test classes that extend {@link junit.framework.TestCase TestCase}, you probably want to override
-  the <code>setUp()</code> and <code>tearDown()</code> methods:
+    To control your test environment, you will want to override the <code>setUp()</code> and
+    <code>tearDown()</code> methods:
 </p>
 <ul>
-  <li>
-    <code>setUp()</code>: This method is invoked before any of the test methods in the class.
-      Use it to set up the environment for the test. You can use <code>setUp()</code>
-      to instantiate a new <code>Intent</code> object with the action <code>ACTION_MAIN</code>. You can
-      then use this intent to start the Activity under test.
-      <p class="note"><strong>Note:</strong> If you override this method, call
-        <code>super.setUp()</code> as the first statement in your code.
-      </p>
-  </li>
-  <li>
-    <code>tearDown()</code>: This method is invoked after all the test methods in the class. Use
-    it to do garbage collection and re-setting before moving on to the next set of tests.
-    <p class="note"><strong>Note:</strong> If you override this method, you must call
-    <code>super.tearDown()</code> as the <em>last</em> statement in your code.</p>
-  </li>
+    <li>
+        <code>setUp()</code>: This method is invoked before any of the test methods in the class.
+        Use it to set up the environment for the test (the test fixture. You can use
+        <code>setUp()</code> to instantiate a new Intent with the action <code>ACTION_MAIN</code>.
+        You can then use this intent to start the Activity under test.
+    </li>
+    <li>
+        <code>tearDown()</code>: This method is invoked after all the test methods in the class. Use
+        it to do garbage collection and to reset the test fixture.
+    </li>
 </ul>
 <p>
-  Another useful convention is to add the method <code>testPreConditions()</code> to your test
-  class. Use this method to test that the application under test is initialized correctly. If this
-  test fails, you know that that the initial conditions were in error. When this happens, further test
-  results are suspect, regardless of whether or not the tests succeeded.
+    Another useful convention is to add the method <code>testPreconditions()</code> to your test
+    class. Use this method to test that the application under test is initialized correctly. If this
+    test fails, you know that that the initial conditions were in error. When this happens, further
+    test results are suspect, regardless of whether or not the tests succeeded.
 </p>
 <p>
-  The Resources tab contains an <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
-  tutorial with more information about creating test classes and methods.
+    The Resources tab contains an
+    <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
+    tutorial with more information about creating test classes and methods.
 </p>
 <h2 id="RunTestEclipse">Running Tests</h2>
+    <div class="sidebox-wrapper">
+        <div class="sidebox">
+            <h2>Running tests from the command line</h2>
+                <p>
+                    If you've created your tests in Eclipse, you can still run your tests and test
+                    suites by using command-line tools included with the Android SDK. You may want
+                    to do this, for example, if you have a large number of tests to run, if you
+                    have a large test case, or if you want a fine level of control over which
+                    tests are run at a particular time.
+                </p>
+                <p>
+                    To run tests created in Eclipse with ADT with command-line tools, you must first
+                    install additional files into the test project using the <code>android</code>
+                    tool's "create test-project" option. To see how to do this, read
+                   <a href="{@docRoot}guide/developing/testing/testing_otheride.html#CreateProject">
+                    Testing in Other IDEs</a>.
+                </p>
+        </div>
+    </div>
+<p>
+    When you run a test package in Eclipse with ADT, the output appears in the Eclipse JUnit view.
+    You can run the entire test package or one test case class. To do run tests, Eclipse runs the
+    <code>adb</code> command for running a test package, and displays the output, so there is no
+    difference between running tests inside Eclipse and running them from the command line.
+</p>
+<p>
+    As with any other package, to run a test package in Eclipse with ADT you must either attach a
+    device to your computer or use the Android emulator. If you use the emulator, you must have an
+    Android Virtual Device (AVD) that uses the same target as the test package.
+</p>
+<p>
+    To run a test in Eclipse, you have two choices:</p>
+<ul>
+    <li>
+        Run a test just as you run an application, by selecting
+        <strong>Run As... &gt; Android JUnit Test</strong> from the project's context menu or
+        from the main menu's <strong>Run</strong> item.
+    </li>
+    <li>
+        Create an Eclipse run configuration for your test project. This is useful if you want
+        multiple test suites, each consisting of selected tests from the project. To run
+        a test suite, you run the test configuration.
+        <p>
+            Creating and running test configurations is described in the next section.
+        </p>
+    </li>
+</ul>
+<p>
+    To create and run a test suite using a run configuration:
+</p>
+<ol>
+    <li>
+        In the Package Explorer, select the test project, then from the main menu, select
+        <strong>Run &gt; Run Configurations...</strong>. The Run Configurations dialog appears.
+    </li>
+    <li>
+        In the left-hand pane, find the Android JUnit Test entry. In the right-hand pane, click the
+        Test tab. The Name: text box shows the name of your project. The Test class: dropdown box
+        shows one of the test classes in your project.
+    </li>
+    <li>
+        To run one test class, click  Run a single test, then enter your project name in the
+        Project: text box and the class name in the Test class: text box.
+        <p>
+            To run all the test classes, click Run all tests in the selected project or package,
+            then enter the project or package name in the text box.
+        </p>
+    </li>
+    <li>
+        Now click the Target tab.
+        <ul>
+            <li>
+                Optional: If you are using the emulator, click Automatic, then in the Android
+                Virtual Device (AVD) selection table, select an existing AVD.
+            </li>
+            <li>
+                In the Emulator Launch Parameters pane, set the Android emulator flags you want to
+                use. These are documented in the topic
+                <a href="{@docRoot}guide/developing/tools/emulator.html#startup-options">
+                Android Emulator</a>.
+            </li>
+        </ul>
+    </li>
+    <li>
+        Click the Common tab. In the Save As pane, click Local to save this run configuration
+        locally, or click Shared to save it to another project.
+    </li>
+    <li>
+        Optional: Add the configuration to the Run toolbar and the <strong>Favorites</strong>
+        menu: in the Display in Favorites pane click the checkbox next to Run.
+    </li>
+    <li>
+        Optional: To add this configuration to the <strong>Debug</strong> menu and toolbar, click
+        the checkbox next to Debug.
+    </li>
+    <li>
+        To save your settings, click Close.<br/>
+        <p class="note"><strong>Note:</strong>
+            Although you can run the test immediately by clicking Run, you should save the test
+            first and then run it by selecting it from the Eclipse standard toolbar.
+        </p>
+    </li>
+    <li>
+        On the Eclipse standard toolbar, click the down arrow next to the green Run arrow. This
+        displays a menu of saved Run and Debug configurations.
+    </li>
+    <li>
+        Select the test run configuration you just created. The test starts.
+    </li>
+</ol>
+<p>
+    The progress of your test appears in the Console view as a series of messages. Each message is
+    preceded by a timestamp and the <code>.apk</code> filename to which it applies. For example,
+    this message appears when you run a test to the emulator, and the emulator is not yet started:
+</p>
 <div class="sidebox-wrapper">
     <div class="sidebox">
-        <h2>Running tests from the command line</h2>
-            <p>
-                If you've created your tests in Eclipse, you can still run your tests and test
-                suites by using command-line tools included with the Android SDK. You may want to
-                do this, for example, if you have a large number of tests to run, if you have a
-                large test case, or if you want a fine level of control over which tests are run at
-                a particular time.
-            </p>
-            <p>
-                To run tests created in Eclipse with ADT with command-line tools, you must first
-                install additional files into the test project using the <code>android</code> tool's
-                "create test-project" option. To see how to do this, read the section
-                <a href="{@docRoot}guide/developing/testing/testing_otheride.html#CreateProject">
-                Creating a test project</a> in the topic
-                <a href="{@docRoot}guide/developing/testing/testing_otheride.html">Testing in Other
-                IDEs</a>.
-            </p>
+        <h2>Message Examples</h2>
+        <p>
+            The examples shown in this section come from the
+            <a href="{@docRoot}resources/samples/SpinnerTest/index.html">SpinnerTest</a>
+            sample test package, which tests the
+            <a href="{@docRoot}resources/samples/Spinner/index.html">Spinner</a>
+            sample application. This test package is also featured in the
+            <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
+            tutorial.
+        </p>
     </div>
 </div>
+<pre>
+    [<em>yyyy-mm-dd hh:mm:ss</em> - <em>testfile</em>] Waiting for HOME ('android.process.acore') to be launched...
+</pre>
 <p>
-  When you run a test application in Eclipse with ADT, the output appears in
-  an Eclipse view panel. You can run the entire test application, one class, or one
-  method of a class. To do this, Eclipse runs the <code>adb</code> command for running a test application, and
-  displays the output, so there is no difference between running tests inside Eclipse and running them from the command line.
+    In the following description of these messages, <code><em>devicename</em></code> is the name of
+    the device or emulator you are using to run the test, and <code><em>port</em></code> is the
+    port number for the device. The name and port number are in the format used by the
+    <code><a href="{@docRoot}guide/developing/tools/adb.html#devicestatus">adb devices</a></code>
+    command. Also, <code><em>testfile</em></code> is the <code>.apk</code> filename of the test
+    package you are running, and <em>appfile</em> is the filename of the application under test.
 </p>
-<p>
-    As with any other application, to run a test application in Eclipse with ADT you must either attach a device to your
-    computer or use the Android emulator. If you use the emulator, you must have an Android Virtual Device (AVD) that uses
-    the same target
-</p>
-<p>
-  To run a test in Eclipse, you have two choices:</p>
-<ol>
-  <li>
-  Run a test just as you run an application, by selecting
-  <strong>Run As... &gt; Android JUnit Test</strong> from the project's context menu or
-  from the main menu's <strong>Run</strong> item.
-  </li>
-  <li>
-  Create an Eclipse run configuration for your test project. This is useful if you want multiple test suites, each consisting of selected tests from the project. To run
-  a test suite, you run the test configuration.
-  <p>
-    Creating and running test configurations is described in the next section.
-  </p>
-  </li>
-</ol>
-<p>To create and run a test suite using a run configuration:</p>
-<ol>
-  <li>
-    In the Package Explorer, select the test
-    project, then from the main menu, select
-    <strong>Run &gt; Run Configurations...</strong>. The
-    Run Configurations dialog appears.
-  </li>
-  <li>
-    In the left-hand pane, find the
-    Android JUnit Test entry.
-    In the right-hand pane, click the Test tab.
-    The Name: text box
-    shows the name of your project. The
-    Test class: dropdown box shows one your project's classes
-    test classes in your project.
-  </li>
-  <li>
-    To run one test class, click  Run a single test, then enter your project
-    name in the Project: text box and the class name in the
-    Test class: text box.
-    <p>
-        To run all the test classes,
-        click Run all tests in the selected project or package,
-        then enter the project or package name in the text box.
-    </p>
- </li>
-  <li>
-    Now click the Target tab.
-    <ul>
-        <li>
-            Optional: If you are using the emulator, click
-            Automatic, then in the Android Virtual Device (AVD)
-            selection table, select an existing AVD.
-        </li>
-        <li>
-            In the Emulator Launch Parameters pane, set the
-            Android emulator flags you want to use. These are documented in the topic
-            <a href="{@docRoot}guide/developing/tools/emulator.html#startup-options">Emulator Startup Options</a>.
-        </li>
-    </ul>
-  <li>
-    Click the Common tab. In the
-    Save As pane, click Local to save
-    this run configuration locally, or click Shared to
-    save it to another project.
-  </li>
-  <li>
-    Optional: Add the configuration to the Run toolbar and the <strong>Favorites</strong>
-    menu: in the Display in Favorites pane
-    click the checkbox next to Run.
-  </li>
-  <li>
-    Optional: To add this configuration to the <strong>Debug</strong> menu and toolbar, click
-    the checkbox next to Debug.
-  </li>
-  <li>
-    To save your settings, click Close.<br/>
-    <p class="note"><strong>Note:</strong> Although you can run the test immediately by
-    clicking Run, you should save the test first and then
-    run it by selecting it from the Eclipse standard toolbar.</p>
-  </li>
-  <li>
-    On the Eclipse standard toolbar, click the down arrow next to the
-    green Run arrow. This displays a menu of saved Run and Debug
-    configurations.
-  </li>
-  <li>
-    Select the test run configuration you just created.
-  </li>
-  <li>
-    The progress of your test appears in the Console view.
-    You should see the following messages, among others:
-    <ul>
-      <li>
-        <code>Performing Android.test.InstrumentationTestRunner JUnit launch</code><br>
-        The class name that proceeds "JUnit" depends on the Android instrumentation
-        class you have chosen.
-      </li>
-      <li>
-        If you are using an emulator and you have not yet started it, then you will see
+<ul>
+    <li>
+        If you are using an emulator and you have not yet started it, then Eclipse
+        first starts the emulator. When this is complete, you see
         the message:
         <p>
-          <code>Automatic Target Mode: launching new emulator with compatible
-          AVD <em>avdname</em></code><br>(where <em>avdname</em> is the name of
-          the AVD you are using.)
+            <code>HOME is up on device '<em>devicename</em>-<em>port</em>'</code>
         </p>
-      </li>
-      <li>
-        If you have not already installed your test application, then you will see
+    </li>
+    <li>
+        If you have not already installed your test package, then you see
         the message:
         <p>
-          <code>Uploading <em>testclass</em>.apk onto device '<em>device-id</em>'</code><br>
-          where <em>testclass</em> is the name of your unit test class and <em>device-id</em>
-          is the name and port for your test device or emulator, followed by the message <code>Installing <em>testclass</em>.apk</code>
+            <code>Uploading <em>testfile</em> onto device '<em>devicename</em>-<em>port</em>'
+            </code>
         </p>
-      </li>
-      <li>
-       <code>Launching instrumentation Android.test.InstrumentationTestRunner on device <em>device-id</em></code>.<br>
-       This indicates that Android's Instrumentation system is now testing your code. Again, the
-       instrumentation class name depends on the Android instrumentation class you have chosen.
-      </li>
-      <li>
-       <code>Test run complete</code>.<br> When you see this, your unit tests have finished.
-      </li>
-    </ul>
-</ol>
+        <p>
+            then the message <code>Installing <em>testfile</em></code>.
+        </p>
+        <p>
+            and finally the message <code>Success!</code>
+        </p>
+    </li>
+</ul>
 <p>
-        The test results appear in the JUnit view. This is divided into an upper summary pane,
-        and a lower stack trace pane.
+    The following lines are an example of this message sequence:
+</p>
+<code>
+[2010-07-01 12:44:40 - MyTest] HOME is up on device 'emulator-5554'<br>
+[2010-07-01 12:44:40 - MyTest] Uploading MyTest.apk onto device 'emulator-5554'<br>
+[2010-07-01 12:44:40 - MyTest] Installing MyTest.apk...<br>
+[2010-07-01 12:44:49 - MyTest] Success!<br>
+</code>
+<br>
+<ul>
+    <li>
+        Next, if you have not yet installed the application under test to the device or
+        emulator, you see the message
+        <p>
+        <code>Project dependency found, installing: <em>appfile</em></code>
+        </p>
+        <p>
+            then the message <code>Uploading <em>appfile</em></code> onto device
+            '<em>devicename</em>-<em>port</em>'
+        </p>
+        <p>
+            then the message <code>Installing <em>appfile</em></code>
+        </p>
+        <p>
+            and finally the message <code>Success!</code>
+        </p>
+    </li>
+</ul>
+<p>
+    The following lines are an example of this message sequence:
+</p>
+<code>
+[2010-07-01 12:44:49 - MyTest] Project dependency found, installing: MyApp<br>
+[2010-07-01 12:44:49 - MyApp] Uploading MyApp.apk onto device 'emulator-5554'<br>
+[2010-07-01 12:44:49 - MyApp] Installing MyApp.apk...<br>
+[2010-07-01 12:44:54 - MyApp] Success!<br>
+</code>
+<br>
+<ul>
+    <li>
+        Next, you see the message
+        <code>Launching instrumentation <em>instrumentation_class</em> on device
+        <em>devicename</em>-<em>port</em></code>
+        <p>
+            <code>instrumentation_class</code> is the fully-qualified class name of the
+            instrumentation test runner you have specified (usually
+            {@link android.test.InstrumentationTestRunner}.
+        </p>
+    </li>
+    <li>
+        Next, as {@link android.test.InstrumentationTestRunner} builds a list of tests to run,
+        you see the message
+        <p>
+            <code>Collecting test information</code>
+        </p>
+        <p>
+            followed by
+        </p>
+        <p>
+            <code>Sending test information to Eclipse</code>
+        </p>
+    </li>
+    <li>
+        Finally, you see the message <code>Running tests</code>, which indicates that your tests
+        are running. At this point, you should start seeing the test results in the JUnit view.
+        When the tests are finished, you see the console message <code>Test run complete</code>.
+        This indicates that your tests are finished.
+    </li>
+</ul>
+<p>
+    The following lines are an example of this message sequence:
+</p>
+<code>
+[2010-01-01 12:45:02 - MyTest] Launching instrumentation android.test.InstrumentationTestRunner on device emulator-5554<br>
+[2010-01-01 12:45:02 - MyTest] Collecting test information<br>
+[2010-01-01 12:45:02 - MyTest] Sending test information to Eclipse<br>
+[2010-01-01 12:45:02 - MyTest] Running tests...<br>
+[2010-01-01 12:45:22 - MyTest] Test run complete<br>
+</code>
+<br>
+<p>
+    The test results appear in the JUnit view. This is divided into an upper summary pane,
+    and a lower stack trace pane.
 </p>
 <p>
-        The upper pane contains test information. In the pane's header, you see the following
-        information:
+    The upper pane contains test information. In the pane's header, you see the following
+    information:
 </p>
-    <ul>
-        <li>
-           Total time elapsed for the test application (labeled Finished after <em>x</em> seconds).
-        </li>
-        <li>
-           Number of runs (Runs:) - the number of tests in the entire test class.
-        </li>
-        <li>
-           Number of errors (Errors:) - the number of program errors and exceptions encountered
-           during the test run.
-        </li>
-        <li>
-           Number of failures (Failures:) - the number of test failures encountered during the test
-           run. This is the number of assertion failures. A test can fail even if the program does
-           not encounter an error.
-        </li>
-        <li>
-           A progress bar. The progress bar extends from left to right as the tests run. If all the
-           tests succeed, the bar remains green. If a test fails, the bar turns from green to red.
-        </li>
-    </ul>
+<ul>
+    <li>
+        Total time elapsed for the test package (labeled Finished after <em>x</em> seconds).
+    </li>
+    <li>
+        Number of runs (Runs:) - the number of tests in the entire test class.
+    </li>
+    <li>
+        Number of errors (Errors:) - the number of program errors and exceptions encountered
+        during the test run.
+    </li>
+    <li>
+        Number of failures (Failures:) - the number of test failures encountered during the test
+        run. This is the number of assertion failures. A test can fail even if the program does
+        not encounter an error.
+    </li>
+    <li>
+        A progress bar. The progress bar extends from left to right as the tests run. If all the
+        tests succeed, the bar remains green. If a test fails, the bar turns from green to red.
+    </li>
+</ul>
 <p>
     The body of the upper pane contains the details of the test run. For each test case class
     that was run, you see a line with the class name. To look at the results for the individual
@@ -363,8 +505,30 @@
     pane and moves the focus to the first line of the test method.
 </p>
 <p>
+    The results of a successful test are shown in
+    <a href="#TestResults">Figure 1. Messages for a successful test</a>:
+</p>
+<a href="{@docRoot}images/testing/eclipse_test_results.png">
+    <img src="{@docRoot}images/testing/eclipse_test_results.png"
+         alt="Messages for a successful test" height="327px" id="TestResults"/>
+</a>
+<p class="img-caption">
+    <strong>Figure 1.</strong> Messages for a successful test
+</p>
+<p>
     The lower pane is for stack traces. If you highlight a failed test in the upper pane, the
     lower pane contains a stack trace for the test. If a line corresponds to a point in your
     test code, you can double-click it to display the code in an editor view pane, with the
     line highlighted. For a successful test, the lower pane is empty.
 </p>
+<p>
+    The results of a failed test are shown in
+    <a href="#FailedTestResults">Figure 2. Messages for a test failure</a>
+</p>
+<a href="{@docRoot}images/testing/eclipse_test_run_failure.png">
+    <img src="{@docRoot}images/testing/eclipse_test_run_failure.png"
+         alt="Messages for a test failure" height="372px" id="TestRun"/>
+</a>
+<p class="img-caption">
+    <strong>Figure 2.</strong> Messages for a test failure
+</p>
diff --git a/docs/html/guide/developing/testing/testing_otheride.jd b/docs/html/guide/developing/testing/testing_otheride.jd
index 2bdf4d0..523a8e5 100644
--- a/docs/html/guide/developing/testing/testing_otheride.jd
+++ b/docs/html/guide/developing/testing/testing_otheride.jd
@@ -2,122 +2,128 @@
 @jd:body
 
 <div id="qv-wrapper">
-  <div id="qv">
-  <h2>In this document</h2>
-  <ol>
-    <li>
-        <a href="#CreateTestProjectCommand">Working with Test Projects</a>
-        <ol>
-            <li>
-                <a href="#CreateTestProject">Creating a test project</a>
-            </li>
-            <li>
-                <a href="#UpdateTestProject">Updating a test project</a>
-            </li>
-        </ol>
-    </li>
-    <li>
-        <a href="#CreateTestApp">Creating a Test Application</a>
-    </li>
-    <li>
-        <a href="#RunTestsCommand">Running Tests</a>
-        <ol>
-            <li>
-                <a href="#RunTestsAnt">Quick build and run with Ant</a>
-            </li>
-            <li>
-                <a href="#RunTestsDevice">Running tests on a device or emulator</a>
-            </li>
-        </ol>
-    </li>
-    <li>
-        <a href="#AMSyntax">Using the Instrument Command</a>
-        <ol>
-            <li>
-                <a href="#AMOptionsSyntax">Instrument options</a>
-            </li>
-            <li>
-                <a href="#RunTestExamples">Instrument examples</a>
-            </li>
-        </ol>
-    </li>
-
-  </ol>
-  <h2>See Also</h2>
-  <ol>
-    <li>
-        <a
-        href="{@docRoot}guide/topics/testing/testing_android.html">Testing and Instrumentation</a>
-    </li>
-    <li>
-        <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
-    </li>
-    <li>
-        <a href="{@docRoot}guide/developing/tools/adb.html">Android Debug Bridge</a>
-    </li>
-  </ol>
-  </div>
+    <div id="qv">
+        <h2>In this document</h2>
+            <ol>
+                <li>
+                    <a href="#CreateTestProjectCommand">Working with Test Projects</a>
+                    <ol>
+                        <li>
+                            <a href="#CreateTestProject">Creating a test project</a>
+                        </li>
+                        <li>
+                            <a href="#UpdateTestProject">Updating a test project</a>
+                        </li>
+                    </ol>
+                </li>
+                <li>
+                    <a href="#CreateTestApp">Creating a Test Package</a>
+                </li>
+                <li>
+                    <a href="#RunTestsCommand">Running Tests</a>
+                    <ol>
+                        <li>
+                            <a href="#RunTestsAnt">Quick build and run with Ant</a>
+                        </li>
+                        <li>
+                            <a href="#RunTestsDevice">Running tests on a device or emulator</a>
+                        </li>
+                    </ol>
+                </li>
+                <li>
+                    <a href="#AMSyntax">Using the Instrument Command</a>
+                    <ol>
+                        <li>
+                            <a href="#AMOptionsSyntax">Instrument options</a>
+                        </li>
+                        <li>
+                            <a href="#RunTestExamples">Instrument examples</a>
+                        </li>
+                    </ol>
+                </li>
+            </ol>
+        <h2>See Also</h2>
+            <ol>
+                <li>
+                    <a href="{@docRoot}guide/topics/testing/testing_android.html">
+                        Testing Fundamentals</a>
+                </li>
+                <li>
+                    <a href="{@docRoot}guide/developing/tools/adb.html">Android Debug Bridge</a>
+                </li>
+            </ol>
+    </div>
 </div>
 <p>
-  This document describes how to create and run tests directly from the command line.
-  You can use the techniques described here if you are developing in an IDE other than Eclipse
-  or if you prefer to work from the command line. This document assumes that you already know how
-  to create a Android application in your programming environment. Before you start this
-  document, you should read the document <a
-  href="{@docRoot}guide/topics/testing/testing_android.html">Testing and Instrumentation</a>,
-  which provides an overview of Android testing.
+    This document describes how to create and run tests directly from the command line.
+    You can use the techniques described here if you are developing in an IDE other than Eclipse
+    or if you prefer to work from the command line. This document assumes that you already know how
+    to create a Android application in your programming environment. Before you start this
+    document, you should read the topic
+    <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Fundamentals</a>,
+    which provides an overview of Android testing.
 </p>
 <p>
-  If you are developing in Eclipse with ADT, you can set up and run your tests
-directly in Eclipse. For more information, please read <a
-  href="{@docRoot}guide/developing/testing/testing_eclipse.html">Testing&nbsp;in&nbsp;Eclipse,&nbsp;with&nbsp;ADT</a>.
+    If you are developing in Eclipse with ADT, you can set up and run your tests
+    directly in Eclipse. For more information, please read
+    <a href="{@docRoot}guide/developing/testing/testing_eclipse.html">
+    Testing in Eclipse, with ADT</a>.
 </p>
 <h2 id="CreateTestProjectCommand">Working with Test Projects</h2>
 <p>
-  You use the <code>android</code> tool to create test projects.
-  You also use <code>android</code> to convert existing test code into an Android test project,
-  or to add the <code>run-tests</code> Ant target to an existing Android test project.
-  These operations are described in more detail in the section <a
-  href="#UpdateTestProject">Updating a test project</a>.
-  The <code>run-tests</code> target is described in <a
-  href="#RunTestsAnt">Quick build and run with Ant</a>.
+    You use the <code>android</code> tool to create test projects.
+    You also use <code>android</code> to convert existing test code into an Android test project,
+    or to add the <code>run-tests</code> Ant target to an existing Android test project.
+    These operations are described in more detail in the section <a href="#UpdateTestProject">
+    Updating a test project</a>. The <code>run-tests</code> target is described in
+    <a href="#RunTestsAnt">Quick build and run with Ant</a>.
 </p>
 <h3 id="CreateTestProject">Creating a test project</h3>
 <p>
-  To create a test project with the <code>android</code> tool, enter:
-<pre>android create test-project -m &lt;main_path&gt; -n &lt;project_name&gt; -p &lt;test_path&gt;</pre>
+    To create a test project with the <code>android</code> tool, enter:
+</p>
+<pre>
+android create test-project -m &lt;main_path&gt; -n &lt;project_name&gt; -p &lt;test_path&gt;
+</pre>
 <p>
-  You must supply all the flags. The following table explains them in detail:
+    You must supply all the flags. The following table explains them in detail:
 </p>
 <table>
-  <tr>
-    <th>Flag</th>
-    <th>Value</th>
-    <th>Description</th>
-  <tr>
-    <td><code>-m, --main</code></td>
-    <td>
-        Path to the project of the application under test, relative to the test application
-        directory.
-    </td>
-    <td>
-        For example, if the application under test is in <code>source/HelloAndroid</code>, and you
-        want to create the test project in <code>source/HelloAndroidTest</code>, then the value of
-        <code>--main</code> should be <code>../HelloAndroid</code>.
+    <tr>
+        <th>Flag</th>
+        <th>Value</th>
+        <th>Description</th>
+    </tr>
+    <tr>
+        <td><code>-m, --main</code></td>
+        <td>
+            Path to the project of the application under test, relative to the test package
+            directory.
         </td>
-  <tr>
-    <td><code>-n, --name</code></td>
-    <td>Name that you want to give the test project.</td>
-    <td>&nbsp;</td>
-  </tr>
-  <tr>
-    <td><code>-p, --path</code></td>
-    <td>Directory in which you want to create the new test project.</td>
-    <td>
-      The <code>android</code> tool creates the test project files and directory structure in this
-      directory. If the directory does not exist, <code>android</code> creates it.
-    </td>
-  </tr>
+        <td>
+            For example, if the application under test is in <code>source/HelloAndroid</code>, and
+            you want to create the test project in <code>source/HelloAndroidTest</code>, then the
+            value of <code>--main</code> should be <code>../HelloAndroid</code>.
+        <p>
+            To learn more about choosing the location of test projects, please read
+            <a href="{@docRoot}guide/topics/testing/testing_android.html#TestProjects">
+            Testing Fundamentals</a>.
+        </p>
+        </td>
+    </tr>
+    <tr>
+        <td><code>-n, --name</code></td>
+        <td>Name that you want to give the test project.</td>
+        <td>&nbsp;</td>
+    </tr>
+    <tr>
+        <td><code>-p, --path</code></td>
+        <td>Directory in which you want to create the new test project.</td>
+        <td>
+            The <code>android</code> tool creates the test project files and directory structure
+            in this directory. If the directory does not exist, <code>android</code> creates it.
+        </td>
+    </tr>
 </table>
 <p>
     If the operation is successful, <code>android</code> lists to STDOUT the names of the files
@@ -135,11 +141,10 @@
     are testing and control it with instrumentation.
 </p>
 <p>
-    For example, suppose you create the <a
-    href="{@docRoot}resources/tutorials/hello-world.html">Hello, World</a> tutorial application
-    in the directory <code>~/source/HelloAndroid</code>. In the tutorial, this application uses the
-    package name <code>com.example.helloandroid</code> and the activity name
-    <code>HelloAndroid</code>. You can to create the test for this in
+    For example, suppose you create the <a href="{@docRoot}resources/tutorials/hello-world.html">
+    Hello, World</a> tutorial application in the directory <code>~/source/HelloAndroid</code>.
+    In the tutorial, this application uses the package name <code>com.example.helloandroid</code>
+    and the activity name <code>HelloAndroid</code>. You can to create the test for this in
     <code>~/source/HelloAndroidTest</code>. To do so, you enter:
 </p>
 <pre>
@@ -196,7 +201,7 @@
 <p class="note">
     <strong>Note:</strong> If you change the Android package name of the application under test,
     you must <em>manually</em> change the value of the <code>&lt;android:targetPackage&gt;</code>
-    attribute within the <code>AndroidManifest.xml</code> file of the test application.
+    attribute within the <code>AndroidManifest.xml</code> file of the test package.
     Running <code>android update test-project</code> does not do this.
 </p>
 <p>
@@ -205,38 +210,38 @@
 <pre>android update-test-project -m &lt;main_path&gt; -p &lt;test_path&gt;</pre>
 
 <table>
-<tr>
-  <th>Flag</th>
-  <th>Value</th>
-  <th>Description</th>
-</tr>
-<tr>
-  <td><code>-m, --main</code></td>
-  <td>The path to the project of the application under test, relative to the test project</td>
-  <td>
-    For example, if the application under test is in <code>source/HelloAndroid</code>, and
-    the test project is in <code>source/HelloAndroidTest</code>, then the value for
-    <code>--main</code> is <code>../HelloAndroid</code>.
-  </td>
-</tr>
-<tr>
-  <td><code>-p, --path</code></td>
-  <td>The of the test project.</td>
-  <td>
-    For example, if the test project is in <code>source/HelloAndroidTest</code>, then the
-    value for <code>--path</code> is <code>HelloAndroidTest</code>.
-  </td>
-</tr>
+    <tr>
+        <th>Flag</th>
+        <th>Value</th>
+        <th>Description</th>
+    </tr>
+    <tr>
+        <td><code>-m, --main</code></td>
+        <td>The path to the project of the application under test, relative to the test project</td>
+        <td>
+            For example, if the application under test is in <code>source/HelloAndroid</code>, and
+            the test project is in <code>source/HelloAndroidTest</code>, then the value for
+            <code>--main</code> is <code>../HelloAndroid</code>.
+        </td>
+    </tr>
+    <tr>
+        <td><code>-p, --path</code></td>
+        <td>The of the test project.</td>
+        <td>
+            For example, if the test project is in <code>source/HelloAndroidTest</code>, then the
+            value for <code>--path</code> is <code>HelloAndroidTest</code>.
+        </td>
+    </tr>
 </table>
 <p>
     If the operation is successful, <code>android</code> lists to STDOUT the names of the files
     and directories it has created.
 </p>
-<h2 id="CreateTestApp">Creating a Test Application</h2>
+<h2 id="CreateTestApp">Creating a Test Package</h2>
 <p>
-    Once you have created a test project, you populate it with a test application.
+    Once you have created a test project, you populate it with a test package.
     The application does not require an {@link android.app.Activity Activity},
-    although you can define one if you wish. Although your test application can
+    although you can define one if you wish. Although your test package can
     combine Activities, Android test class extensions, JUnit extensions, or
     ordinary classes, you should extend one of the Android test classes or JUnit classes,
     because these provide the best testing features.
@@ -248,7 +253,7 @@
 </p>
 
 <p>
-    To create a test application, start with one of Android's test classes in the Java package
+    To create a test package, start with one of Android's test classes in the Java package
     {@link android.test android.test}. These extend the JUnit
     {@link junit.framework.TestCase TestCase} class. With a few exceptions, the Android test
     classes also provide instrumentation for testing.
@@ -282,24 +287,17 @@
     test results are suspect, regardless of whether or not the tests succeeded.
 </p>
 <p>
-    To learn more about creating test applications, see the topic <a
-    href="{@docRoot}guide/topics/testing/testing_android.html">Testing and Instrumentation</a>,
+    To learn more about creating test packages, see the topic <a
+    href="{@docRoot}guide/topics/testing/testing_android.html">Testing Fundamentals</a>,
     which provides an overview of Android testing. If you prefer to follow a tutorial,
     try the <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
     tutorial, which leads you through the creation of tests for an actual Android application.
 </p>
 <h2 id="RunTestsCommand">Running Tests</h2>
 <p>
-    If you are not developing in Eclipse with ADT, you need to run tests from the command line.
-    You can do this either with Ant or with the {@link android.app.ActivityManager ActivityManager}
-    command line interface.
-</p>
-<p>
-    You can also run tests from the command line even if you are using Eclipse with ADT to develop
-    them. To do this, you need to create the proper files and directory structure in the test
-    project, using the <code>android</code> tool with the option <code>create test-project</code>.
-    This is described in the section <a
-    href="#CreateTestProjectCommand">Working with Test Projects</a>.
+    You run tests from the command line, either with Ant or with an
+    <a href="{@docRoot}http://developer.android.com/guide/developing/tools/adb.html">
+    Android Debug Bridge (adb)</a> shell.
 </p>
 <h3 id="RunTestsAnt">Quick build and run with Ant</h3>
 <p>
@@ -316,57 +314,63 @@
     You can update an existing test project to use this feature. To do this, use the
     <code>android</code> tool with the <code>update test-project</code> option. This is described
     in the section <a href="#UpdateTestProject">Updating a test project</a>.
+</p>
 <h3 id="RunTestsDevice">Running tests on a device or emulator</h3>
 <p>
-    When you run tests from the command line with the ActivityManager (<code>am</code>)
-    command-line tool, you get more options for choosing the tests to run than with any other
-    method. You can select individual test methods, filter tests according to their annotation, or
-    specify testing options. Since the test run is controlled entirely from a command line, you can
-    customize your testing with shell scripts in various ways.
+    When you run tests from the command line with
+    <a href="{@docRoot}http://developer.android.com/guide/developing/tools/adb.html">
+    Android Debug Bridge (adb)</a>, you get more options for choosing the tests
+    to run than with any other method. You can select individual test methods, filter tests
+    according to their annotation, or specify testing options. Since the test run is controlled
+    entirely from a command line, you can customize your testing with shell scripts in various ways.
 </p>
 <p>
-    You run the <code>am</code> tool on an Android device or emulator using the
-    <a href="{@docRoot}guide/developing/tools/adb.html">Android Debug Bridge</a>
-    (<code>adb</code>) shell. When you do this, you use the ActivityManager
-    <code>instrument</code> option to run your test application using an Android test runner
-    (usually {@link android.test.InstrumentationTestRunner}). You set <code>am</code>
-    options with command-line flags.
+    To run a test from the command line, you run <code>adb shell</code> to start a command-line
+    shell on your device or emulator, and then in the shell run the <code>am instrument</code>
+    command. You control <code>am</code> and your tests with command-line flags.
 </p>
 <p>
-    To run a test with <code>am</code>:
+    As a shortcut, you can start an <code>adb</code> shell, call <code>am instrument</code>, and
+    specify command-line flags all on one input line. The shell opens on the device or emulator,
+    runs your tests, produces output, and then returns to the command line on your computer.
+</p>
+<p>
+    To run a test with <code>am instrument</code>:
 </p>
 <ol>
-  <li>
-    If necessary, re-build your main application and test application.
-  </li>
-  <li>
-    Install your test application and main application Android package files
-    (<code>.apk</code> files) to your current Android device or emulator</li>
-  <li>
-    At the command line, enter:
+    <li>
+        If necessary, rebuild your main application and test package.
+    </li>
+    <li>
+        Install your test package and main application Android package files
+        (<code>.apk</code> files) to your current Android device or emulator</li>
+    <li>
+        At the command line, enter:
 <pre>
 $ adb shell am instrument -w &lt;test_package_name&gt;/&lt;runner_class&gt;
 </pre>
-<p>
-    where <code>&lt;test_package_name&gt;</code> is the Android package name of your test
-    application, and <code>&lt;runner_class&gt;</code> is the name of the Android test runner
-    class you are using. The Android package name is the value of the <code>package</code>
-    attribute of the <code>manifest</code> element in the manifest file
-    (<code>AndroidManifest.xml</code>) of your test application. The Android test runner
-    class is usually <code>InstrumentationTestRunner</code>.
-</p>
-<p>Your test results appear in <code>STDOUT</code>.</p>
-  </li>
+        <p>
+            where <code>&lt;test_package_name&gt;</code> is the Android package name of your test
+            application, and <code>&lt;runner_class&gt;</code> is the name of the Android test
+            runner class you are using. The Android package name is the value of the
+            <code>package</code> attribute of the <code>manifest</code> element in the manifest file
+            (<code>AndroidManifest.xml</code>) of your test package. The Android test runner
+            class is usually {@link android.test.InstrumentationTestRunner}.
+        </p>
+        <p>
+            Your test results appear in <code>STDOUT</code>.
+        </p>
+    </li>
 </ol>
 <p>
-    This operation starts an <code>adb</code> shell, then runs <code>am instrument</code> in it
+    This operation starts an <code>adb</code> shell, then runs <code>am instrument</code>
     with the specified parameters. This particular form of the command will run all of the tests
-    in your test application. You can control this behavior with flags that you pass to
+    in your test package. You can control this behavior with flags that you pass to
     <code>am instrument</code>. These flags are described in the next section.
 </p>
-<h2 id="AMSyntax">Using the Instrument Command</h2>
+<h2 id="AMSyntax">Using the am instrument Command</h2>
 <p>
-  The general syntax of the <code>am instrument</code> command is:
+    The general syntax of the <code>am instrument</code> command is:
 </p>
 <pre>
     am instrument [flags] &lt;test_package&gt;/&lt;runner_class&gt;
@@ -391,11 +395,11 @@
             <code>&lt;test_package&gt;</code>
         </td>
         <td>
-            The Android package name of the test application.
+            The Android package name of the test package.
         </td>
         <td>
             The value of the <code>package</code> attribute of the <code>manifest</code>
-            element in the test application's manifest file.
+            element in the test package's manifest file.
         </td>
     </tr>
     <tr>
@@ -411,7 +415,7 @@
     </tr>
 </table>
 <p>
-The flags for <code>am instrument</code> are described in the following table:
+    The flags for <code>am instrument</code> are described in the following table:
 </p>
 <table>
     <tr>
@@ -461,20 +465,21 @@
              &lt;test_options&gt;
         </td>
         <td>
-            Provides testing options , in the form of key-value pairs. The
+            Provides testing options as key-value pairs. The
             <code>am instrument</code> tool passes these to the specified instrumentation class
             via its <code>onCreate()</code> method. You can specify multiple occurrences of
-            <code>-e &lt;test_options</code>. The keys and values are described in the next table.
+            <code>-e &lt;test_options&gt;</code>. The keys and values are described in the
+            section <a href="#AMOptionsSyntax">am instrument options</a>.
             <p>
-                The only instrumentation class that understands these key-value pairs is
-                <code>InstrumentationTestRunner</code> (or a subclass). Using them with
+                The only instrumentation class that uses these key-value pairs is
+                {@link android.test.InstrumentationTestRunner} (or a subclass). Using them with
                 any other class has no effect.
             </p>
         </td>
     </tr>
 </table>
 
-<h3 id="AMOptionsSyntax">Instrument options</h3>
+<h3 id="AMOptionsSyntax">am instrument options</h3>
 <p>
     The <code>am instrument</code> tool passes testing options to
     <code>InstrumentationTestRunner</code> or a subclass in the form of key-value pairs,
@@ -484,123 +489,127 @@
     -e &lt;key&gt; &lt;value&gt;
 </pre>
 <p>
-    Where applicable, a &lt;key&gt; may have multiple values separated by a comma (,).
+    Some keys accept multiple values. You specify multiple values in a comma-separated list.
     For example, this invocation of <code>InstrumentationTestRunner</code> provides multiple
     values for the <code>package</code> key:
+</p>
 <pre>
-$ adb shell am instrument -w -e package com.android.test.package1,com.android.test.package2 com.android.test/android.test.InstrumentationTestRunner
+$ adb shell am instrument -w -e package com.android.test.package1,com.android.test.package2 \
+&gt; com.android.test/android.test.InstrumentationTestRunner
 </pre>
 <p>
     The following table describes the key-value pairs and their result. Please review the
     <strong>Usage Notes</strong> following the table.
 </p>
 <table>
-<tr>
-    <th>Key</th>
-    <th>Value</th>
-    <th>Description</th>
-</tr>
-<tr>
-    <td>
-        <code>package</code>
-    </td>
-    <td>
-        &lt;Java_package_name&gt;
-    </td>
-    <td>
-        The fully-qualified <em>Java</em> package name for one of the packages in the test
-        application. Any test case class that uses this package name is executed. Notice that this
-        is not an <em>Android</em> package name; a test application has a single Android package
-        name but may have several Java packages within it.
-    </td>
-</tr>
-<tr>
-    <td rowspan="2"><code>class</code></td>
-    <td>&lt;class_name&gt;</td>
-    <td>
-        The fully-qualified Java class name for one of the test case classes. Only this test case
-        class is executed.
-    </td>
-</tr>
-<tr>
-    <td>&lt;class_name&gt;<strong>#</strong>method name</td>
-    <td>
-        A fully-qualified test case class name, and one of its methods. Only this method is
-        executed. Note the hash mark (#) between the class name and the method name.
-    </td>
-</tr>
-<tr>
-    <td><code>func</code></td>
-    <td><code>true</code></td>
-    <td>
-        Runs all test classes that extend {@link android.test.InstrumentationTestCase}.
-    </td>
-</tr>
-<tr>
-    <td><code>unit</code></td>
-    <td><code>true</code></td>
-    <td>
-        Runs all test classes that do <em>not</em> extend either
-        {@link android.test.InstrumentationTestCase} or {@link android.test.PerformanceTestCase}.
-    </td>
-</tr>
-<tr>
-    <td><code>size</code></td>
-    <td>[<code>small</code> | <code>medium</code> | <code>large</code>]
-    </td>
-    <td>
-        Runs a test method annotated by size. The  annotations are <code>@SmallTest</code>,
-        <code>@MediumTest</code>, and <code>@LargeTest</code>.
-    </td>
-</tr>
-<tr>
-    <td><code>perf</code></td>
-    <td><code>true</code></td>
-    <td>
-        Runs all test classes that implement {@link android.test.PerformanceTestCase}.
-        When you use this option, also specify the <code>-r</code> flag for
-        <code>am instrument</code>, so that the output is kept in raw format and not
-        re-formatted as test results.
-    </td>
-</tr>
-<tr>
-    <td><code>debug</code></td>
-    <td><code>true</code></td>
-    <td>
-        Runs tests in debug mode.
-    </td>
-</tr>
-<tr>
-    <td><code>log</code></td>
-    <td><code>true</code></td>
-    <td>
-        Loads and logs all specified tests, but does not run them. The test
-        information appears in <code>STDOUT</code>. Use this to verify combinations of other filters
-        and test specifications.
-    </td>
-</tr>
-<tr>
-    <td><code>emma</code></td>
-    <td><code>true</code></td>
-    <td>
-        Runs an EMMA code coverage analysis and writes the output to <code>/data//coverage.ec</code>
-        on the device. To override the file location, use the <code>coverageFile</code> key that
-        is described in the following entry.
-        <p class="note">
-            <strong>Note:</strong> This option requires an EMMA-instrumented build of the test
-            application, which you can generate with the <code>coverage</code> target.
-        </p>
-    </td>
-</tr>
-<tr>
-    <td><code>coverageFile</code></td>
-    <td><code>&lt;filename&gt;</code></td>
-    <td>
-        Overrides the default location of the EMMA coverage file on the device. Specify this
-        value as a path and filename in UNIX format. The default filename is described in the
-        entry for the <code>emma</code> key.
-    </td>
-</tr>
+    <tr>
+        <th>Key</th>
+        <th>Value</th>
+        <th>Description</th>
+    </tr>
+    <tr>
+        <td>
+            <code>package</code>
+        </td>
+        <td>
+            &lt;Java_package_name&gt;
+        </td>
+        <td>
+            The fully-qualified <em>Java</em> package name for one of the packages in the test
+            application. Any test case class that uses this package name is executed. Notice that
+            this is not an <em>Android</em> package name; a test package has a single
+            Android package name but may have several Java packages within it.
+        </td>
+    </tr>
+    <tr>
+        <td rowspan="2"><code>class</code></td>
+        <td>&lt;class_name&gt;</td>
+        <td>
+            The fully-qualified Java class name for one of the test case classes. Only this test
+            case class is executed.
+        </td>
+    </tr>
+    <tr>
+        <td>&lt;class_name&gt;<strong>#</strong>method name</td>
+        <td>
+            A fully-qualified test case class name, and one of its methods. Only this method is
+            executed. Note the hash mark (#) between the class name and the method name.
+        </td>
+    </tr>
+    <tr>
+        <td><code>func</code></td>
+        <td><code>true</code></td>
+        <td>
+            Runs all test classes that extend {@link android.test.InstrumentationTestCase}.
+        </td>
+    </tr>
+    <tr>
+        <td><code>unit</code></td>
+        <td><code>true</code></td>
+        <td>
+            Runs all test classes that do <em>not</em> extend either
+            {@link android.test.InstrumentationTestCase} or
+            {@link android.test.PerformanceTestCase}.
+        </td>
+    </tr>
+    <tr>
+        <td><code>size</code></td>
+        <td>
+            [<code>small</code> | <code>medium</code> | <code>large</code>]
+        </td>
+        <td>
+            Runs a test method annotated by size. The  annotations are <code>@SmallTest</code>,
+            <code>@MediumTest</code>, and <code>@LargeTest</code>.
+        </td>
+    </tr>
+    <tr>
+        <td><code>perf</code></td>
+        <td><code>true</code></td>
+        <td>
+            Runs all test classes that implement {@link android.test.PerformanceTestCase}.
+            When you use this option, also specify the <code>-r</code> flag for
+            <code>am instrument</code>, so that the output is kept in raw format and not
+            re-formatted as test results.
+        </td>
+    </tr>
+    <tr>
+        <td><code>debug</code></td>
+        <td><code>true</code></td>
+        <td>
+            Runs tests in debug mode.
+        </td>
+    </tr>
+    <tr>
+        <td><code>log</code></td>
+        <td><code>true</code></td>
+        <td>
+            Loads and logs all specified tests, but does not run them. The test
+            information appears in <code>STDOUT</code>. Use this to verify combinations of other
+            filters and test specifications.
+        </td>
+    </tr>
+    <tr>
+        <td><code>emma</code></td>
+        <td><code>true</code></td>
+        <td>
+            Runs an EMMA code coverage analysis and writes the output to
+            <code>/data//coverage.ec</code> on the device. To override the file location, use the
+            <code>coverageFile</code> key that is described in the following entry.
+            <p class="note">
+                <strong>Note:</strong> This option requires an EMMA-instrumented build of the test
+                application, which you can generate with the <code>coverage</code> target.
+            </p>
+        </td>
+    </tr>
+    <tr>
+        <td><code>coverageFile</code></td>
+        <td><code>&lt;filename&gt;</code></td>
+        <td>
+            Overrides the default location of the EMMA coverage file on the device. Specify this
+            value as a path and filename in UNIX format. The default filename is described in the
+            entry for the <code>emma</code> key.
+        </td>
+    </tr>
 </table>
 <strong><code>-e</code> Flag Usage Notes</strong>
 <ul>
@@ -618,13 +627,13 @@
         The <code>func</code> key and <code>unit</code> key are mutually exclusive.
     </li>
 </ul>
-<h3 id="RunTestExamples">Instrument examples</h3>
+<h3 id="RunTestExamples">Usage examples</h3>
 <p>
-Here are some examples of using <code>am instrument</code> to run tests. They are based on
-the following structure:</p>
+The following sections provide examples of using <code>am instrument</code> to run tests.
+They are based on the following structure:</p>
 <ul>
     <li>
-        The test application has the Android package name <code>com.android.demo.app.tests</code>
+        The test package has the Android package name <code>com.android.demo.app.tests</code>
     </li>
     <li>
         There are three test classes:
@@ -647,35 +656,35 @@
         The test runner is {@link android.test.InstrumentationTestRunner}.
     </li>
 </ul>
-<h4>Running the Entire Test Application</h4>
+<h4>Running the entire test package</h4>
 <p>
-    To run all of the test classes in the test application, enter:
+    To run all of the test classes in the test package, enter:
 </p>
 <pre>
 $ adb shell am instrument -w com.android.demo.app.tests/android.test.InstrumentationTestRunner
 </pre>
-<h4>Running All Tests in a Test Case Class</h4>
+<h4>Running all tests in a test case class</h4>
 <p>
     To run all of the tests in the class <code>UnitTests</code>, enter:
 </p>
 <pre>
 $ adb shell am instrument -w  \
--e class com.android.demo.app.tests.UnitTests \
-com.android.demo.app.tests/android.test.InstrumentationTestRunner
+&gt; -e class com.android.demo.app.tests.UnitTests \
+&gt; com.android.demo.app.tests/android.test.InstrumentationTestRunner
 </pre>
 <p>
   <code>am instrument</code> gets the value of the <code>-e</code> flag, detects the
   <code>class</code> keyword, and runs all the methods in the <code>UnitTests</code> class.
 </p>
-<h4>Selecting a Subset of Tests</h4>
+<h4>Selecting a subset of tests</h4>
 <p>
-  To run all of the tests in <code>UnitTests</code>, and the <code>testCamera</code> method in
-  <code>FunctionTests</code>, enter:
+    To run all of the tests in <code>UnitTests</code>, and the <code>testCamera</code> method in
+    <code>FunctionTests</code>, enter:
 </p>
 <pre>
 $ adb shell am instrument -w \
--e class com.android.demo.app.tests.UnitTests,com.android.demo.app.tests.FunctionTests#testCamera \
-com.android.demo.app.tests/android.test.InstrumentationTestRunner
+&gt; -e class com.android.demo.app.tests.UnitTests,com.android.demo.app.tests.FunctionTests#testCamera \
+&gt; com.android.demo.app.tests/android.test.InstrumentationTestRunner
 </pre>
 <p>
     You can find more examples of the command in the documentation for
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index 35ce17e..9793748 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -254,9 +254,30 @@
             <li><a href="<?cs var:toroot?>guide/topics/search/searchable-config.html">Searchable Configuration</a></li>
           </ul>
       </li>
-      <li><a href="<?cs var:toroot?>guide/topics/testing/testing_android.html">
-            <span class="en">Testing and Instrumentation</span></a>
-            <span class="new">new!</span></li>
+      <li class="toggle-list">
+           <div>
+                <a href="<?cs var:toroot ?>guide/topics/testing/index.html">
+                   <span class="en">Testing</span>
+               </a> <span class="new">new!</span>
+           </div>
+           <ul>
+              <li><a href="<?cs var:toroot?>guide/topics/testing/testing_android.html">
+                <span class="en">Testing Fundamentals</span></a>
+              </li>
+              <li><a href="<?cs var:toroot?>guide/topics/testing/activity_testing.html">
+                <span class="en">Activity Testing</span></a>
+              </li>
+              <li><a href="<?cs var:toroot ?>guide/topics/testing/contentprovider_testing.html">
+                <span class="en">Content Provider Testing</span></a>
+              </li>
+              <li><a href="<?cs var:toroot ?>guide/topics/testing/service_testing.html">
+                <span class="en">Service Testing</span></a>
+              </li>
+              <li><a href="<?cs var:toroot ?>guide/topics/testing/what_to_test.html">
+                <span class="en">What To Test</span></a>
+              </li>
+           </ul>
+      </li>
     </ul>
   </li>
 
diff --git a/docs/html/guide/topics/fundamentals.jd b/docs/html/guide/topics/fundamentals.jd
index a095087..84c2ed2 100644
--- a/docs/html/guide/topics/fundamentals.jd
+++ b/docs/html/guide/topics/fundamentals.jd
@@ -294,8 +294,8 @@
 <li><p>A service is started (or new instructions are given to an ongoing 
 service) by passing an Intent object to <code>{@link 
 android.content.Context#startService Context.startService()}</code>.  
-Android calls the service's <code>{@link android.app.Service#onStart 
-onStart()}</code> method and passes it the Intent object.</p>
+Android calls the service's <code>{@link android.app.Service#onStartCommand
+onStartCommand()}</code> method and passes it the Intent object.</p>
 
 <p>
 Similarly, an intent can be passed to <code>{@link 
@@ -1510,9 +1510,9 @@
 in {@code onCreate()}, and then stop the thread in {@code onDestroy()}.</li>
 
 <li><p>The <b>active lifetime</b> of a service begins with a call to 
-<code>{@link android.app.Service#onStart onStart()}</code>.  This method 
+<code>{@link android.app.Service#onStartCommand onStartCommand()}</code>.  This method
 is handed the Intent object that was passed to {@code startService()}.
-The music service would open the Intent to discover which music to 
+The music service would open the Intent to discover which music to
 play, and begin the playback.</p>
 
 <p>
@@ -1527,7 +1527,7 @@
 <code>{@link android.content.Context#startService Context.startService()}</code>
 or 
 <code>{@link android.content.Context#bindService Context.bindService()}</code>.
-However, {@code onStart()} is called only for services started by {@code
+However, {@code onStartCommand()} is called only for services started by {@code
 startService()}.
 </p>
 
@@ -1631,7 +1631,7 @@
 
 <li><p>It has a {@link android.app.Service} object that's executing
 one of its lifecycle callbacks (<code>{@link android.app.Service#onCreate 
-onCreate()}</code>, <code>{@link android.app.Service#onStart onStart()}</code>, 
+onCreate()}</code>, <code>{@link android.app.Service#onStartCommand onStartCommand()}</code>,
 or <code>{@link android.app.Service#onDestroy onDestroy()}</code>).</p></li>
 
 <li><p>It has a {@link android.content.BroadcastReceiver} object that's 
diff --git a/docs/html/guide/topics/fundamentals/activities.jd b/docs/html/guide/topics/fundamentals/activities.jd
new file mode 100644
index 0000000..b616ab8
--- /dev/null
+++ b/docs/html/guide/topics/fundamentals/activities.jd
@@ -0,0 +1,762 @@
+page.title=Activities
+parent.title=Application Fundamentals
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+  <li>An activity provides a user interface for a single screen in your application</li>
+  <li>Activities can move into the background and then be resumed with their state restored</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+  <li><a href="#Creating">Creating an Activity</a>
+    <ol>
+      <li><a href="#UI">Implementing a user interface</a></li>
+      <li><a href="#Declaring">Declaring the activity in the manifest</a></li>
+    </ol>
+  </li>
+  <li><a href="#StartingAnActivity">Starting an Activity</a>
+    <ol>
+      <li><a href="#StartingAnActivityForResult">Starting an Activity for a Result</a></li>
+    </ol>
+  </li>
+  <li><a href="#Lifecycle">Managing the Activity Lifecycle</a>
+    <ol>
+      <li><a href="#ImplementingLifecycleCallbacks">Implementing the lifecycle callbacks</a></li>
+      <li><a href="#SavingActivityState">Saving activity state</a></li>
+      <li><a href="#ConfigurationChanges">Handling configuration changes</a></li>
+      <li><a href="#CoordinatingActivities">Coordinating activities</a></li>
+    </ol>
+  </li>
+</ol>
+
+<h2>Key classes</h2>
+<ol>
+  <li>{@link android.app.Activity}</li>
+</ol>
+
+<h2>See also</h2>
+<ol>
+  <li><a href="{@docRoot}resources/tutorials/hello-world.html">Hello World Tutorial</a></li>
+  <li><a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack">Tasks and Back
+Stack</a></li>
+</ol>
+
+</div>
+</div>
+
+
+
+<p>An {@link android.app.Activity} is an application component that provides a screen with which
+users can interact in order to do something, such as dial the phone, take a photo, send an email, or
+view a map. Each activity is given a window in which to draw its user interface. The window
+typically fills the screen, but may be smaller than the screen and float on top of other
+windows.</p>
+
+<p> An application usually consists of multiple activities that are loosely bound
+to each other. Typically, one activity in an application is specified as the "main" activity, which
+is presented to the user when launching the application for the first time. Each
+activity can then start another activity in order to perform different actions. Each time a new
+activity starts, the previous activity is stopped, but the system preserves the activity
+in a stack (the "back stack"). When a new activity starts, it is pushed onto the back stack and
+takes user focus. The back stack abides to the basic "last in, first out" queue mechanism,
+so, when the user is done with the current activity and presses the BACK key, it
+is popped from the stack (and destroyed) and the previous activity resumes. (The back stack is
+discussed more in the <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks
+and Back Stack</a> document.)</p>
+
+<p>When an activity is stopped because a new activity starts, it is notified of this change in state
+through the activity's lifecycle callback methods.
+There are several callback methods that an activity might receive, due to a change in its
+state&mdash;whether the system is creating it, stopping it, resuming it, or destroying it&mdash;and
+each callback provides you the opportunity to perform specific work that's
+appropriate to that state change. For instance, when stopped, your activity should release any
+large objects, such as network or database connections. When the activity resumes, you can
+reacquire the necessary resources and resume actions that were interrupted. These state transitions
+are all part of the activity lifecycle.</p>
+
+<p>The rest of this document discusses the basics of how to build and use an activity,
+including a complete discussion of how the activity lifecycle works, so you can properly manage
+the transition between various activity states.</p>
+
+
+
+<h2 id="Creating">Creating an Activity</h2>
+
+<p>To create an activity, you must create a subclass of {@link android.app.Activity} (or
+an existing subclass of it). In your subclass, you need to implement callback methods that the
+system calls when the activity transitions between various states of its lifecycle, such as when
+the activity is being created, stopped, resumed, or destroyed. The two most important callback
+methods are:</p>
+
+<dl>
+  <dt>{@link android.app.Activity#onCreate onCreate()}</dt>
+  <dd>You must implement this method. The system calls this when creating your
+    activity. Within your implementation, you should initialize the essential components of your
+activity.
+    Most importantly, this is where you must call {@link android.app.Activity#setContentView
+    setContentView()} to define the layout for the activity's user interface.</dd>
+  <dt>{@link android.app.Activity#onPause onPause()}</dt>
+  <dd>The system calls this method as the first indication that the user is leaving your
+activity (though it does not always mean the activity is being destroyed). This is usually where you
+should commit any changes that should be persisted beyond the current user session (because
+the user might not come back).</dd>
+</dl>
+
+<p>There are several other lifecycle callback methods that you should use in order to provide a
+fluid user experience between activities and handle unexpected interuptions that cause your activity
+to be stopped and even destroyed. All of the lifecycle callback methods are discussed later, in
+the section about <a href="#Lifecycle">Managing the Activity Lifecycle</a>.</p>
+
+
+
+<h3 id="UI">Implementing a user interface</h3>
+
+<p> The user interface for an activity is provided by a hierarchy of views&mdash;objects derived
+from the {@link android.view.View} class.  Each view controls a particular rectangular space
+within the activity's window and can respond to user interaction. For example, a view might be a
+button that initiates an action when the user touches it.</p>
+
+<p>Android provides a number of ready-made views that you can use to design and organize your
+layout. "Widgets" are views that provide a visual (and interactive) elements for the screen, such
+as a button, text field, checkbox, or just an image. "Layouts" are views derived from {@link
+android.view.ViewGroup} that provide a unique layout model for its child views, such as a linear
+layout, a grid layout, or relative layout. You can also subclass the {@link android.view.View} and
+{@link android.view.ViewGroup} classes (or existing subclasses) to create your own widgets and
+layouts and apply them to your activity layout.</p>
+
+<p>The most common way to define a layout using views is with an XML layout file saved in your
+application resources. This way, you can maintain the design of your user interface separately from
+the source code that defines the activity's behavior. You can set the layout as the UI for your
+activity with {@link android.app.Activity#setContentView(int) setContentView()}, passing the
+resource ID for the layout. However, you can also create new {@link android.view.View}s in your
+activity code and build a view hierarchy by inserting new {@link
+android.view.View}s into a {@link android.view.ViewGroup}, then use that layout by passing the root
+{@link android.view.ViewGroup} to {@link android.app.Activity#setContentView(View)
+setContentView()}.</p>
+
+<p>For information about creating a user interface, see the <a
+href="{@docRoot}guide/topics/ui/index.html">User Interface</a> documentation.</p>
+
+
+
+<h3 id="Declaring">Declaring the activity in the manifest</h3>
+
+<p>You must declare your activity in the manifest file in order for it to
+be accessible to the system. To decalare your activity, open your manifest file and add an <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a> element
+as a child of the <a
+href="{@docRoot}guide/topics/manifest/application-element.html">{@code &lt;application&gt;}</a>
+element. For example:</p>
+
+<pre>
+&lt;manifest ... &gt;
+  &lt;application ... &gt;
+      &lt;activity android:name=".ExampleActivity" /&gt;
+      ...
+  &lt;/application ... &gt;
+  ...
+&lt;/manifest &gt;
+</pre>
+
+<p>There are several other attributes that you can include in this element, to define properties
+such as the label for the activity, an icon for the activity, or a theme to style the activity's
+UI. See the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a> element
+reference for more information about available attributes.</p>
+
+
+<h4>Using intent filters</h4>
+
+<p>An <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code
+&lt;activity&gt;}</a> element can also specify various intent filters&mdash;using the <a
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code
+&lt;intent-filter&gt;}</a> element&mdash;in order to declare how other application components may
+activate it.</p>
+
+<p>When you create a new application using the Android SDK tools, the stub activity
+that's created for you automatically includes an intent filter that declares the activity
+responds to the "main" action and should be placed in the "launcher" category. The intent filter
+looks like this:</p>
+
+<pre>
+&lt;activity android:name=".ExampleActivity" android:icon="@drawable/app_icon"&gt;
+    &lt;intent-filter&gt;
+        &lt;action android:name="android.intent.action.MAIN" /&gt;
+        &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
+    &lt;/intent-filter&gt;
+&lt;/activity&gt;
+</pre>
+
+<p>The <a href="{@docRoot}guide/topics/manifest/action-element.html">{@code
+&lt;action&gt;}</a> element specifies that this is the "main" entry point to the application. The <a
+href="{@docRoot}guide/topics/manifest/category-element.html">{@code
+&lt;category&gt;}</a> element specifies that this activity should be listed in the
+system's application launcher (to allow users to launch this activity).</p>
+
+<p>If you intend for your application to be self-contained and not allow other applications to
+activate its activities, then you don't need any other intent filters. Only one activity should
+have the "main" action and "launcher" category, as in the previous example. Activities that
+you don't want to make available to other applications should have no intent filters and you can
+start them yourself using explicit intents (as discussed in the following section).</p>
+
+<p>However, if you want your activity to respond to implicit intents that are delivered from
+other applications (and your own), then you must define additional intent filters for your
+activity. For each type of intent to which you want to respond, you must include an <a
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code
+&lt;intent-filter&gt;}</a> that includes an
+<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code
+&lt;action&gt;}</a> element and, optionally, a <a
+href="{@docRoot}guide/topics/manifest/category-element.html">{@code
+&lt;category&gt;}</a> element and/or a <a
+href="{@docRoot}guide/topics/manifest/data-element.html">{@code
+&lt;data&gt;}</a> element. These elements specify the type of intent to which your activity can
+respond.</p>
+
+<p>For more information about how your activities can respond to intents, see the <a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>
+document.</p>
+
+
+
+<h2 id="StartingAnActivity">Starting an Activity</h2>
+
+<p>You can start another activity by calling {@link android.app.Activity#startActivity
+  startActivity()}, passing it an {@link android.content.Intent} that describes the activity you
+  want to start. The intent specifies either the exact activity you want to start or describes the
+  type of action you want to perform (and the system selects the appropriate activity for you,
+which
+  can even be from a different application). An intent can also carry small amounts of data to be
+  used by the activity that is started.</p>
+
+<p>When working within your own application, you'll often need to simply launch a known activity.
+ You can do so by creating an intent that explicitly defines the activity you want to start,
+using the class name. For example, here's how one activity starts another activity named {@code
+SignInActivity}:</p>
+
+<pre>
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+</pre>
+
+<p>However, your application might also want to perform some action, such as send an email, text
+  message, or status update, using data from your activity. In this case, your application might
+ not have its own activities to perform such actions, so you can instead leverage the activities
+  provided by other applications on the device, which can perform the actions for you. This is where
+intents are really valuable&mdash;you can create an intent that describes an action you want to
+perform and the system
+  launches the appropriate activity from another application. If there are
+  multiple activities that can handle the intent, then the user can select which one to use. For
+  example, if you want to allow the user to send an email message, you can create the
+  following intent:</p>
+
+<pre>
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+</pre>
+
+<p>The {@link android.content.Intent#EXTRA_EMAIL} extra added to the intent is a string array of
+  email addresses to which the email should be sent. When an email application responds to this
+  intent, it reads the string array provided in the extra and places them in the "to" field of the
+  email composition form. In this situation, the email application's activity starts and when the
+  user is done, your activity resumes.</p>
+
+
+
+
+<h3 id="StartingAnActivityForResult">Starting an activity for a result</h3>
+
+<p>Sometimes, you might want to receive a result from the activity that you start. In that case,
+  start the activity by calling {@link android.app.Activity#startActivityForResult
+  startActivityForResult()} (instead of {@link android.app.Activity#startActivity
+  startActivity()}). To then receive the result from the subsequent
+activity, implement the {@link android.app.Activity#onActivityResult onActivityResult()} callback
+  method. When the subsequent activity is done, it returns a result in an {@link
+android.content.Intent} to your {@link android.app.Activity#onActivityResult onActivityResult()}
+method.</p>
+
+<p>For example, perhaps you want the user to pick one of their contacts, so your activity can
+do something with the information in that contact. Here's how you can create such an intent and
+handle the result:</p>
+
+<pre>
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+&#64;Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK &amp;&amp; requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+</pre>
+
+<p>This example shows the basic logic you should use in your {@link
+android.app.Activity#onActivityResult onActivityResult()} method in order to handle an
+activity result. The first condition checks whether the request was successful&mdash;if it was, then
+the {@code resultCode} will be {@link android.app.Activity#RESULT_OK}&mdash;and whether the request
+to which this result is responding is known&mdash;in this case, the {@code requestCode} matches the
+second parameter sent with {@link android.app.Activity#startActivityForResult
+startActivityForResult()}. From there, the code handles the activity result by querying the
+data returned in an {@link android.content.Intent} (the {@code data} parameter).</p>
+
+<p>What happens is, a {@link
+android.content.ContentResolver} performs a query against a content provider, which returns a
+{@link android.database.Cursor} that allows the queried data to be read. For more information, see
+the <a
+href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> document.</p>
+
+<p>For more information about using intents, see the <a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent
+Filters</a> document.</p>
+
+
+<h2 id="ShuttingDown">Shutting Down an Activity</h2>
+
+<p>You can shut down an activity by calling its {@link android.app.Activity#finish
+finish()} method. You can also shut down a separate activity that you previously started by calling
+{@link android.app.Activity#finishActivity finishActivity()}.</p>
+
+<p class="note"><strong>Note:</strong> In most cases, you should not explicitly finish an activity
+using these methods. As discussed in the following section about the activity lifecycle, the
+Android system manages the life of an activity for you, so you do not need to finish your own
+activities. Calling these methods could adversely affect the expected user
+experience and should only be used when you absolutely do not want the user to return to this
+instance of the activity.</p>
+
+
+<h2 id="Lifecycle">Managing the Activity Lifecycle</h2>
+
+<p>Managing the lifecycle of your activities by implementing callback methods is
+crucial to developing a strong
+and flexible application. The lifecycle of an activity is directly affected by its association with
+other activities, its task and back stack.</p>
+
+<p>An activity can exist in essentially three states:</p>
+
+<dl>
+  <dt><i>Resumed</i></dt>
+    <dd>The activity is in the foreground of the screen and has user focus. (This state is
+also sometimes referred to as "running".)</dd>
+
+  <dt><i>Paused</i></dt>
+    <dd>Another activity is in the foreground and has focus, but this one is still visible. That is,
+another activity is visible on top of this one and that activity is partially transparent or doesn't
+cover the entire screen. A paused activity is completely alive (the {@link android.app.Activity}
+object is retained in memory, it maintains all state and member information, and remains attached to
+the window manager), but can be killed by the system in extremely low memory situations.</dd>
+
+  <dt><i>Stopped</i></dt>
+    <dd>The activity is completely obscured by another activity (the activity is now in the
+"background"). A stopped activity is also still alive (the {@link android.app.Activity}
+object is retained in memory, it maintains all state and member information, but is <em>not</em>
+attached to the window manager). However, it is no longer visible to the user and it
+can be killed by the system when memory is needed elsewhere.</dd>
+</dl>
+
+<p>If an activity is paused or stopped, the system can drop it from memory either by asking it to
+finish (calling its {@link android.app.Activity#finish finish()} method), or simply killing its
+process.  When the activity is opened again (after being finished or killed), it must be created all
+over.</p>
+
+
+
+<h3 id="ImplementingLifecycleCallbacks">Implementing the lifecycle callbacks</h3>
+
+<p>When an activity transitions into and out of the different states described above, it is notified
+through various callback methods. All of the callback methods are hooks that you
+can override to do appropriate work when the state of your activity changes. The following skeleton
+activity includes each of the fundamental lifecycle methods:</p>
+
+
+<pre>
+public class ExampleActivity extends Activity {
+    &#64;Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+</pre>
+
+<p class="note"><strong>Note:</strong> Your implementation of these lifecycle methods must
+always call the superclass implementation before doing any work, as shown in the examples above.</p>
+
+<p>Taken together, these methods define the entire lifecycle of an activity. By implementing these
+methods, you can monitor three nested loops in the activity lifecycle: </p>
+
+<ul>
+<li>The <b>entire lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onCreate onCreate()} and the call to {@link
+android.app.Activity#onDestroy}. Your activity should perform setup of
+"global" state (such as defining layout) in {@link android.app.Activity#onCreate onCreate()}, and
+release all remaining resources in {@link android.app.Activity#onDestroy}. For example, if your
+activity has a thread running in the background to download data from the network, it might create
+that thread in {@link android.app.Activity#onCreate onCreate()} and then stop the thread in {@link
+android.app.Activity#onDestroy}.</li>
+
+<li><p>The <b>visible lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onStart onStart()} and the call to {@link
+android.app.Activity#onStop onStop()}. During this time, the user can see the activity
+on-screen and interact with it. For example, {@link android.app.Activity#onStop onStop()} is called
+when a new activity starts and this one is no longer visible. Between these two methods, you can
+maintain resources that are needed to show the activity to the user. For example, you can register a
+{@link android.content.BroadcastReceiver} in {@link
+android.app.Activity#onStart onStart()} to monitor changes that impact your UI, and unregister
+it in {@link android.app.Activity#onStop onStop()} when the user can no longer see what you are
+displaying. The system might call {@link android.app.Activity#onStart onStart()} and {@link
+android.app.Activity#onStop onStop()} multiple times during the entire lifetime of the activity, as
+the activity alternates between being visible and hidden to the user.</p></li>
+
+<li><p>The <b>foreground lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onResume onResume()} and the call to {@link android.app.Activity#onPause
+onPause()}. During this time, the activity is in front of all other activities on screen and has
+user input focus.  An activity can frequently transition in and out of the foreground&mdash;for
+example, {@link android.app.Activity#onPause onPause()} is called when the device goes to sleep or
+when a dialog appears. Because this state can transition often, the code in these two methods should
+be fairly lightweight in order to avoid slow transitions that make the user wait.</p></li>
+</ul>
+
+<p>Figure 1 illustrates these loops and the paths an activity might take between states.
+The rectangles represent the callback methods you can implement to perform operations when
+the activity transitions between states. <p>
+
+<img src="{@docRoot}images/activity_lifecycle.png" alt="" />
+<p class="img-caption"><strong>Figure 1.</strong> The activity lifecycle.</p>
+
+<p>The same lifecycle callback methods are listed in table 1, which describes each of the callback
+methods in more detail and locates each one within the
+activity's overall lifecycle, including whether the system can kill the activity after the
+callback method completes.</p>
+
+<p class="table-caption"><strong>Table 1.</strong> A summary of the activity lifecycle's
+callback methods.</p>
+
+<table border="2" width="85%" frame="hsides" rules="rows">
+<colgroup align="left" span="3"></colgroup>
+<colgroup align="left"></colgroup>
+<colgroup align="center"></colgroup>
+<colgroup align="center"></colgroup>
+
+<thead>
+<tr><th colspan="3">Method</th> <th>Description</th> <th>Killable after?</th> <th>Next</th></tr>
+</thead>
+
+<tbody>
+<tr>
+  <td colspan="3" align="left"><code>{@link android.app.Activity#onCreate onCreate()}</code></td>
+  <td>Called when the activity is first created.
+      This is where you should do all of your normal static set up &mdash;
+      create views, bind data to lists, and so on.  This method is passed
+      a Bundle object containing the activity's previous state, if that
+      state was captured (see <a href="#actstate">Saving Activity State</a>,
+      later).
+      <p>Always followed by {@code onStart()}.</p></td>
+  <td align="center">No</td>
+      <td align="center">{@code onStart()}</td>
+</tr>
+
+<tr>
+   <td rowspan="5" style="border-left: none; border-right: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+   <td colspan="2" align="left"><code>{@link android.app.Activity#onRestart
+onRestart()}</code></td>
+   <td>Called after the activity has been stopped, just prior to it being
+       started again.
+       <p>Always followed by {@code onStart()}</p></td>
+   <td align="center">No</td>
+   <td align="center">{@code onStart()}</td>
+</tr>
+
+<tr>
+   <td colspan="2" align="left"><code>{@link android.app.Activity#onStart onStart()}</code></td>
+   <td>Called just before the activity becomes visible to the user.
+       <p>Followed by {@code onResume()} if the activity comes
+       to the foreground, or {@code onStop()} if it becomes hidden.</p></td>
+    <td align="center">No</td>
+    <td align="center">{@code onResume()} <br/>or<br/> {@code onStop()}</td>
+</tr>
+
+<tr>
+   <td rowspan="2" style="border-left: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+   <td align="left"><code>{@link android.app.Activity#onResume onResume()}</code></td>
+   <td>Called just before the activity starts
+       interacting with the user.  At this point the activity is at
+       the top of the activity stack, with user input going to it.
+       <p>Always followed by {@code onPause()}.</p></td>
+   <td align="center">No</td>
+   <td align="center">{@code onPause()}</td>
+</tr>
+
+<tr>
+   <td align="left"><code>{@link android.app.Activity#onPause onPause()}</code></td>
+   <td>Called when the system is about to start resuming another
+       activity.  This method is typically used to commit unsaved changes to
+       persistent data, stop animations and other things that may be consuming
+       CPU, and so on.  It should do whatever it does very quickly, because
+       the next activity will not be resumed until it returns.
+       <p>Followed either by {@code onResume()} if the activity
+       returns back to the front, or by {@code onStop()} if it becomes
+       invisible to the user.</td>
+   <td align="center"><strong style="color:#800000">Yes</strong></td>
+   <td align="center">{@code onResume()} <br/>or<br/> {@code onStop()}</td>
+</tr>
+
+<tr>
+   <td colspan="2" align="left"><code>{@link android.app.Activity#onStop onStop()}</code></td>
+   <td>Called when the activity is no longer visible to the user.  This
+       may happen because it is being destroyed, or because another activity
+       (either an existing one or a new one) has been resumed and is covering it.
+       <p>Followed either by {@code onRestart()} if
+       the activity is coming back to interact with the user, or by
+       {@code onDestroy()} if this activity is going away.</p></td>
+   <td align="center"><strong style="color:#800000">Yes</strong></td>
+   <td align="center">{@code onRestart()} <br/>or<br/> {@code onDestroy()}</td>
+</tr>
+
+<tr>
+   <td colspan="3" align="left"><code>{@link android.app.Activity#onDestroy
+onDestroy()}</code></td>
+   <td>Called before the activity is destroyed.  This is the final call
+       that the activity will receive.  It could be called either because the
+       activity is finishing (someone called <code>{@link android.app.Activity#finish
+       finish()}</code> on it), or because the system is temporarily destroying this
+       instance of the activity to save space.  You can distinguish
+       between these two scenarios with the <code>{@link
+       android.app.Activity#isFinishing isFinishing()}</code> method.</td>
+   <td align="center"><strong style="color:#800000">Yes</strong></td>
+   <td align="center"><em>nothing</em></td>
+</tr>
+</tbody>
+</table>
+
+<p>The column labeled "Killable after?" indicates whether or not the system can
+kill the process hosting the activity at any time <em>after the method returns</em>, without
+executing another line of the activity's code.  Three methods are marked "yes": ({@link
+android.app.Activity#onPause
+onPause()}, {@link android.app.Activity#onStop onStop()}, and {@link android.app.Activity#onDestroy
+onDestroy()}). Because {@link android.app.Activity#onPause onPause()} is the first
+of the three, once the activity is created, {@link android.app.Activity#onPause onPause()} is the
+last method that's guaranteed to be called before the process <em>can</em> be killed&mdash;if
+the system must recover memory in an emergency, then {@link
+android.app.Activity#onStop onStop()} and {@link android.app.Activity#onDestroy onDestroy()} might
+not be called. Therefore, you should use {@link android.app.Activity#onPause onPause()} to write
+crucial persistent data (such as user edits) to storage. However, you should be selective about
+what information must be retained during {@link android.app.Activity#onPause onPause()}, because any
+blocking procedures in this method block the transition to the next activity and slow the user
+experience.</p>
+
+<p> Methods that are marked "No" in the <b>Killable</b> column protect the process hosting the
+activity from being killed from the moment they are called.  Thus, an activity is killable
+from the time {@link android.app.Activity#onPause onPause()} returns to the time
+{@link android.app.Activity#onResume onResume()} is called. It will not again be killable until
+{@link android.app.Activity#onPause onPause()} is again called and returns. </p>
+
+<p class="note"><strong>Note:</strong> An activity that's not technically "killable" by this
+definition in table 1 might still be killed by the system&mdash;but that would happen only in
+extreme circumstances when there is no other recourse. When an activity might be killed is
+discussed more in the <a
+href="{@docRoot}guide/topics/fundamentals/processes-and-threading.html">Processes and
+Threading</a> document.</p>
+
+
+<h3 id="SavingActivityState">Saving activity state</h3>
+
+<p>The introduction to <a href="Lifecycle">Managing the Activity Lifecycle</a> briefly mentions that
+when an activity is paused or stopped, the state of the activity is retained. This is true because
+the {@link android.app.Activity} object is still held in memory when it is paused or
+stopped&mdash;all information about its members and current state is still alive. Thus, any changes
+the user made within the activity are retained in memory, so that when the activity returns to the
+foreground (when it "resumes"), those changes are still there.</p>
+
+<div class="figure" style="width:615px">
+<img src="{@docRoot}images/fundamentals/restore_instance.png" alt="" />
+<p class="img-caption"><strong>Figure 2.</strong> The two ways in which an activity returns to user
+focus with its state intact: either the activity is stopped, then resumed and the activity state
+remains intact (left), or the activity is destroyed, then recreated and the activity must restore
+the previous activity state (right).</p>
+</div>
+
+<p>However, when the system destroys an activity in order to recover memory, the {@link
+android.app.Activity} object is destroyed, so the system cannot simply resume it with its state
+intact. Instead, the system must recreate the {@link android.app.Activity} object if the user
+navigates back to it. Yet, the user is unaware
+that the system destroyed the activity and recreated it and, thus, probably
+expects the activity to be exactly as it was. In this situation, you can ensure that
+important information about the activity state is preserved by implementing an additional
+callback method that allows you to save information about the state of your activity and then
+restore it when the the system recreates the activity.</p>
+
+<p>The callback method in which you can save information about the current state of your activity is
+{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}. The system calls this method
+before making the activity vulnerable to being destroyed and passes it
+a {@link android.os.Bundle} object. The {@link android.os.Bundle} is where you can store 
+state information about the activity as name-value pairs, using methods such as {@link
+android.os.Bundle#putString putString()}. Then, if the system kills your activity's
+process and the user navigates back to your activity, the system passes the {@link
+android.os.Bundle} to {@link android.app.Activity#onCreate onCreate()} so you can restore the
+activity state you saved during {@link android.app.Activity#onSaveInstanceState
+onSaveInstanceState()}. If there is no state information to restore, then the {@link
+android.os.Bundle} passed to {@link android.app.Activity#onCreate onCreate()} is null.</p>
+
+<p class="note"><strong>Note:</strong> There's no guarantee that {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} will be called before your
+activity is destroyed, because there are cases in which it won't be necessary to save the state
+(such as when the user leaves your activity using the BACK key, because the user is explicitly
+closing the activity). If the method is called, it is always called before {@link
+android.app.Activity#onStop onStop()} and possibly before {@link android.app.Activity#onPause
+onPause()}.</p>
+
+<p>However, even if you do nothing and do not implement {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()}, some of the activity state is
+restored by the {@link android.app.Activity} class's default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Specifically, the default
+implementation calls {@link
+android.view.View#onSaveInstanceState onSaveInstanceState()} for every {@link android.view.View}
+in the layout, which allows each view to provide information about itself
+that should be saved. Almost every widget in the Android framework implements this method as
+appropriate, such that any visible changes to the UI are automatically saved and restored when your
+activity is recreated. For example, the {@link android.widget.EditText} widget saves any text
+entered by the user and the {@link android.widget.CheckBox} widget saves whether it's checked or
+not. The only work required by you is to provide a unique ID (with the <a
+href="{@docRoot}guide/topics/resources/layout-resource.html#idvalue">{@code android:id}</a>
+attribute) for each widget you want to save its state. If a widget does not have an ID, then it
+cannot save its state.</p>
+
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<p>You can also explicitly stop a view in your layout from saving its state by setting the
+{@link android.R.attr#saveEnabled android:saveEnabled} attribute to {@code "false"} or by calling
+the {@link android.view.View#setSaveEnabled setSaveEnabled()} method. Usually, you should not
+disable this, but you might if you want to restore the state of the activity UI differently.</p>
+</div>
+</div>
+
+<p>Although the default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} saves useful information about
+your activity's UI, you still might need to override it to save additional information.
+For example, you might need to save member values that changed during the activity's life (which
+might correlate to values restored in the UI, but the members that hold those UI values are not
+restored, by default).</p>
+
+<p>Because the default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} helps save the state of the UI, if
+you override the method in order to save additional state information, you should always call the
+superclass implementation of {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}
+before doing any work.</p>
+
+<p class="note"><strong>Note:</strong> Because {@link android.app.Activity#onSaveInstanceState
+onSaveInstanceState()} is not guaranteed
+to be called, you should use it only to record the transient state of the activity (the state of
+the UI)&mdash;you should never use it to store persistent data.  Instead, you should use  {@link
+android.app.Activity#onPause onPause()} to store persistent data (such as data that should be saved
+to a database) when the user leaves the activity.</p>
+
+<p>A good way to test your application's ability to restore its state is to simply rotate the
+device so that the screen orientation changes. When the screen orientation changes, the system
+destroys and recreates the activity in order to apply alternative resources that might be available
+for the new orientation. For this reason alone, it's very important that your activity
+completely restores its state when it is recreated, because users regularly rotate the screen while
+using applications.</p>
+
+
+<h3 id="ConfigurationChanges">Handling configuration changes</h3>
+
+<p>Some device configurations can change during runtime (such as screen orientation, keyboard
+availability, and language). When such a change occurs, Android restarts the running Activity
+({@link android.app.Activity#onDestroy} is called, followed immediately by {@link
+android.app.Activity#onCreate onCreate()}). The restart behavior is
+designed to help your application adapt to new configurations by automatically reloading your
+application with alternative resources that you've provided. If you design your activity to
+properly handle this event, it will be more resilient to unexpected events in the activity
+lifecycle.</p>
+
+<p>The best way to handle a configuration change, such as a change in the screen orientation, is
+  to simply preserve the state of your application using {@link
+  android.app.Activity#onSaveInstanceState onSaveInstanceState()} and {@link
+android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (or {@link
+android.app.Activity#onCreate onCreate()}), as discussed in the previous section.</p>
+
+<p>For a detailed discussion about configuration changes that happen at runtime and how you should
+handle them, read <a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling
+Runtime Changes</a>.</p>
+
+
+
+<h3 id="CoordinatingActivities">Coordinating activities</h3>
+
+ <p>When one activity starts another, they both experience lifecycle transitions. The first activity
+pauses and stops (though, it won't stop if it's still visible in the background), while the other
+activity is created. In case these activities share data saved to disc or elsewhere, it's important
+to understand that the first activity is not completely stopped before the second one is created.
+Rather, the process of starting the second one overlaps with the process of stopping the first
+one.</p>
+
+<p>The order of lifecycle callbacks is well defined, particularly when the two activities are in the
+same process and one is starting the other. Here's the order of operations that occur when Activity
+A starts Acivity B: </p>
+
+<ol>
+<li>Activity A's {@link android.app.Activity#onPause onPause()} method executes.</li>
+
+<li>Activity B's {@link android.app.Activity#onCreate onCreate()}, {@link
+android.app.Activity#onStart onStart()}, and {@link android.app.Activity#onResume onResume()}
+methods execute in sequence. (Activity B now has user focus.)</li>
+
+<li>Then, if Activity A is no longer visible on screen, its {@link
+android.app.Activity#onStop onStop()} method executes.</li>
+</ol>
+
+ <p>This predictable sequence of lifecycle callbacks allows you to manage the transition of
+information from one activity to another. For example, if you must write to a database when the
+first activity stops so that the following activity can read it, then you should write to the
+database during {@link android.app.Activity#onPause onPause()} instead of during {@link
+android.app.Activity#onStop onStop()}.</p>
+
+
+<h2>Beginner's Path</h2>
+
+<p>For more information about how Android maintains a history of activities and
+enables user multitasking, continue with the <b><a
+href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
+Stack</a></b> document.</p>
diff --git a/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd
new file mode 100644
index 0000000..47dc5471
--- /dev/null
+++ b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd
@@ -0,0 +1,568 @@
+page.title=Tasks and Back Stack
+parent.title=Application Fundamentals
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+  <li>All activities belong to a task</li>
+  <li>A task contains a collection of activities in the order in which the user interacts with
+them</li>
+  <li>Tasks can move to the background and retain the state of each activity in order for the user
+to perform other tasks without loosing their work</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+<li><a href="#ActivityState">Saving Activity State</a></li></li>
+<li><a href="#ManagingTasks">Managing Tasks</a>
+  <ol>
+    <li><a href="#TaskLaunchModes">Defining launch modes</a></li>
+    <li><a href="#Affinities">Handling affinities</a></li>
+    <li><a href="#Clearing">Clearing the back stack</a></li>
+    <li><a href="#Starting">Starting a task</a></li>
+  </ol>
+</li>
+</ol>
+
+<h2>See also</h2>
+<ol>
+  <li><a><a href="{@docRoot}videos/index.html#v=fL6gSd4ugSI">Application Lifecycle video</a></li>
+  <li><a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;} manifest
+element</a></li>
+</ol>
+</div>
+</div>
+
+
+<p>An application usually contains multiple <a
+href="{@docRoot}guide/topics/fundamentals/activities.html">activities</a>. Each activity
+should be designed around a specific kind of action the user can perform and can start other
+activities. For example, an email application might have one activity to show a list of new email.
+When the user selects an email, a new activity opens to view that email.</p>
+
+<p>An activity can even start activities that exist in other applications on the device. For
+example, if your application wants to send an email, you can define an intent to perform a "send"
+action and include some data, such as an email address and a message. An activity from another
+application that declares itself to handle this kind of intent then opens. In this case, the intent
+is to send an email, so an email application's "compose" activity starts (if multiple activities
+support the same intent, then the system lets the user select which one to use). When the email is
+sent, your activity resumes and it seems as if the email activity was part of your application. Even
+though the activities may be from different applications, Android maintains this seamless user
+experience by keeping both activities in the same <em>task</em>.</p>
+
+<p>A task is a collection of activities that users interact with
+when performing a certain job. The activities are arranged in a stack (the "back stack"), in the
+order in which each activity is opened.</p>
+
+<!-- SAVE FOR WHEN THE FRAGMENT DOC IS ADDED
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<h3>Adding fragments to a task's back stack</h3>
+
+<p>Your activity can also include {@link android.app.Fragment}s to the back stack. For example,
+suppose you have a two-pane layout using fragments, one of which is a list view (fragment A) and the
+other being a layout to display an item from the list (fragment B). When the user selects an item
+from the list, fragment B is replaced by a new fragment (fragment C). In this case, it might be
+desireable for the user to navigate back to reveal fragment B, using the BACK key.</p>
+<p>In order to add fragment B to the back stack so that this is possible, you must call {@link
+android.app.FragmentTransaction#addToBackStack addToBackStack()} before you {@link
+android.app.FragmentTransaction#commit()} the transaction that replaces fragment B with fragment
+C.</p>
+<p>For more information about using fragments and adding them to the back stack, see the {@link
+android.app.Fragment} class documentation.</p>
+
+</div>
+</div>
+-->
+
+<p>The device Home screen is the starting place for most tasks. When the user touches an icon in the
+application
+launcher (or a shortcut on the Home screen), that application's task comes to the foreground. If no
+task exists for the application (the application has not been used recently), then a new task
+is created and the "main" activity for that application opens as the root activity in the stack.</p>
+
+<p>When the current activity starts another, the new activity is pushed on the top of the stack and
+takes focus. The previous activity remains in the stack, but is stopped. When an activity
+stops, the system retains the current state of its user interface. When the user presses the BACK
+key, the current activity is popped from the top of the stack (the activity is destroyed) and the
+previous activity resumes (the previous state of its UI is restored). Activities in the stack are
+never rearranged, only pushed and popped from the stack&mdash;pushed onto the stack when started by
+the current activity and popped off when the user leaves it using the BACK key. As such, the back
+stack operates as a "last in, first out" object structure. Figure 1 visualizes
+this behavior with a timeline showing the progress between activities along with the current back
+stack at each point in time.</p>
+
+<img src="{@docRoot}images/fundamentals/diagram_backstack.png" alt="" />
+<p class="img-caption"><strong>Figure 1.</strong> A representation of how each new activity in a
+task adds an item to the back stack. When the user presses the BACK key, the current activity is
+destroyed and the previous activity resumes.</p>
+
+
+<p>If the user continues to press BACK, then each activity in the stack is popped off to reveal the
+previous one, until the user returns to the Home screen (or to whichever activity was running when
+the task began). When all activities are removed from the stack, the task no longer exists.</p>
+
+<div class="figure" style="width:369px">
+<img src="{@docRoot}images/fundamentals/diagram_multitasking.png" alt="" /> <p
+class="img-caption"><strong>Figure 2.</strong> Two tasks: Task A is in the background, waiting
+to be resumed, while Task B receives user interaction in the foreground.</p>
+</div>
+<div class="figure" style="width:178px">
+  <img src="{@docRoot}images/fundamentals/diagram_multiple_instances.png" alt="" /> <p
+class="img-caption"><strong>Figure 3.</strong> A single activity is instantiated multiple times.</p>
+</div>
+
+<p>A task is a cohesive unit that can move to the "background" when users begin a new task or go
+to the Home screen, via the HOME key. While in the background, all the activities in the task are
+stopped, but the back stack for the task remains intact&mdash;the task has simply lost focus while
+another task takes place, as shown in figure 2. A task can then return to the "foreground" so users
+can pick up where they left off. Suppose, for example, that the current task (Task A) has three
+activities in its stack&mdash;two under the current activity. The user presses the HOME key, then
+starts a new application from the application launcher. When the Home screen appears, Task A goes
+into the background. When the new application starts, the system starts a task for that application
+(Task B) with its own stack of activities. After interacting with
+that application, the user returns Home again and selects the application that originally
+started Task A. Now, Task A comes to the
+foreground&mdash;all three activities in its stack are intact and the activity at the top of the
+stack resumes. At
+this point, the user can also switch back to Task B by going Home and selecting the application icon
+that started that task (or by touching and holding the HOME key to reveal recent tasks and selecting
+one). This is an example of multitasking on Android.</p>
+
+<p class="note"><strong>Note:</strong> Multiple tasks can be held in the background at once.
+However, if the user is running many background tasks at the same time, the system might begin
+destroying background activities in order to recover memory, causing the activity states to be lost.
+See the following section about <a href="#ActivityState">Activity state</a>.</p>
+
+<p>Because the activities in the back stack are never rearranged, if your application allows
+users to start a particular activity from more than one activity, a new instance of
+that activity is created and popped onto the stack (rather than bringing any previous instance of
+the activity to the top). As such, one activity in your application might be instantiated multiple
+times (even from different tasks), as shown in figure 3. As such, if the user navigates backward
+using the BACK key, each instance of the activity is revealed in the order they were opened (each
+with their own UI state). However, you can modify this behavior if you do not want an activity to be
+instantiated more than once. How to do so is discussed in the later section about <a
+href="#ManagingTasks">Managing Tasks</a>.</p>
+
+
+<p>To summarize the default behavior for activities and tasks:</p>
+
+<ul>
+  <li>When Activity A starts Activity B, Activity A is stopped, but the system retains its state
+(such as scroll position and text entered into forms).
+If the user presses the BACK key while in Activity B, Activity A resumes with its state
+restored.</li>
+  <li>When the user leaves a task by pressing the HOME key, the current activity is stopped and
+its task goes into the background. The system retains the state of every activity in the task. If
+the user later resumes the task by selecting the launcher icon that began the task, the task comes
+to the foreground and resumes the activity at the top of the stack.</li>
+  <li>If the user presses the BACK key, the current activity is popped from the stack and
+destroyed. The previous activity in the stack is resumed. When an activity is destroyed, the system
+<em>does not</em> retain the activity's state.</li>
+  <li>Activities can be instantiated multiple times, even from other tasks.</li>
+</ul>
+
+
+<h2 id="ActivityState">Saving Activity State</h2>
+
+<p>As discussed above, the system's default behavior preserves the state of an activity when it is
+stopped. This way, when users navigate back to a previous activity, its user interface appears
+the way they left it. However, you can&mdash;and <strong>should</strong>&mdash;proactively retain
+the state of your activities using callback methods, in case the activity is destroyed and must
+be recreated.</p>
+
+<p>When the system stops one of your activities (such as when a new activity starts or the task
+moves to the background), the system might destroy that activity completely if it needs to recover
+system memory. When this happens, information about the activity state is lost. If this happens, the
+system still
+knows that the activity has a place in the back stack, but when the activity is brought to the
+top of the stack the system must recreate it (rather than resume it). In order to
+avoid loosing the user's work, you should proactively retain it by implementing the {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} callback
+methods in your activity.</p>
+
+<p>For more information about how to save your activity state, see the <a
+href="{@docRoot}guide/topics/fundamentals/activities.html#SavingActivityState">Activities</a>
+document.</p>
+
+
+
+<h2 id="ManagingTasks">Managing Tasks</h2>
+
+<p>The way Android manages tasks and the back stack, as described above&mdash;by placing all
+activities started in succession in the same task and in a "last in, first out" stack&mdash;works
+great for most applications and you shouldn't have to worry about how your activities are associated
+with tasks or how they exist in the back stack. However, you might decide that you want to interrupt
+the normal behavior. Perhaps you want an activity in your application to begin a new task when it is
+started (instead of being placed within the current task); or, when you start an activity, you want
+to bring forward an existing instance of it (instead of creating a new
+instance on top of the back stack); or, you want your back stack to be cleared of all
+activitiesstart an activity except for the root activity when the user leaves the task.</p>
+
+<p>You can do these things and more, with attributes in the
+<a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code
+&lt;activity&gt;}</a> manifest element and with flags in the intent that you pass to {@link
+android.app.Activity#startActivity startActivity()}.</p>
+
+<p>In this regard, the the principal <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a>
+attributes you can use are:</p>
+
+<ul class="nolist">
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code
+taskAffinity}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#reparent">{@code
+allowTaskReparenting}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code
+clearTaskOnLaunch}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#always">{@code
+alwaysRetainTaskState}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code
+finishOnTaskLaunch}</a></li>
+</ul>
+
+<p>And the principal intent flags you can use are:</p>
+
+<ul class="nolist">
+  <li>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</li>
+  <li>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</li>
+  <li>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</li>
+</ul>
+
+<p>In the following sections, you'll see how you can use these manifest attributes and intent
+flags to define how activities are associated with tasks and how the behave in the back stack.</p>
+
+
+<p class="caution"><strong>Caution:</strong> Most applications should not interrupt the default
+behavior for activities and tasks. If you determine that it's necessary for your activity to modify
+the default behaviors, use caution and be sure to test the usability of the activity during
+launch and when navigating back to it from other activities and tasks with the BACK key. Be sure 
+to test for navigation behaviors that might conflict with the user's expected behavior.</p>
+
+
+<h3 id="TaskLaunchModes">Defining launch modes</h3>
+
+<p>Launch modes allow you to define how a new instance of an activity is associated with the
+current task. You can define different launch modes in two ways:</p>
+<ul class="nolist">
+  <li><a href="#ManifestForTasks">Using the manifest file</a>
+    <p>When you declare an activity in your manifest file, you can specify how the activity
+should associate with tasks when it starts.</li>
+  <li><a href="#IntentFlagsForTasks">Using Intent flags</a>
+    <p>When you call {@link android.app.Activity#startActivity startActivity()},
+you can include a flag in the {@link android.content.Intent} that declares how (or
+whether) the new activity should associate with the current task.</p></li>
+</ul>
+
+<p>As such, if Activity A starts Activity B, Activity B can define in its manifest how it
+should associate with the current task (if at all) and Activity A can also request how Activity
+B should associate with current task. If both activities define how Activity B
+should associate with a task, then Activity A's request (as defined in the intent) is honored
+over Activity B's request (as defined in its manifest).</p>
+
+<p class="note"><strong>Note:</strong> Some the launch modes available in the manifest
+are not available as flags for an intent and, likewise, some launch modes available as flags
+for an intent cannot be defined in the manifest.</p>
+
+
+<h4 id="ManifestForTasks">Using the manifest file</h4>
+
+<p>When declaring an activity in your manifest file, you can specify how the activity should
+associate with a task using the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a>
+element's <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a> attribute.</p>
+
+<p>The <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a> attribute specifies an instruction on how the activity should be launched into a
+task. There are four different launch modes you can assign to the
+<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">launchMode</a></code>
+attribute:</p>
+
+<dl>
+<dt>{@code "standard"} (the default mode)</dt>
+  <dd>Default. The system creates a new instance of the activity in the task from
+which it was started and routes the intent to it. The activity can be instantiated multiple times,
+each instance can belong to different tasks, and one task can have multiple instances.</dd>
+<dt>{@code "singleTop"}</dt>
+  <dd>If an instance of the activity already exists at the top of the current task, the system
+routes the intent to that instance through a call to its {@link
+android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance of the
+activity. The activity can be instantiated multiple times, each instance can
+belong to different tasks, and one task can have multiple instances (but only if the the
+activity at the top of the back stack is <em>not</em> an existing instance of the activity).
+  <p>For example, suppose a task's back stack consists of root activity A with activities B, C,
+and D on top (the stack is A-B-C-D; D is on top). An intent arrives for an activity of type D.
+If D has the default {@code "standard"} launch mode, a new instance of the class is launched and the
+stack becomes A-B-C-D-D. However, if D's launch mode is {@code "singleTop"}, the existing instance
+of D is deliverd the intent through {@link
+android.app.Activity#onNewIntent onNewIntent()}, because it's at the top of the stack&mdash;the
+stack remains A-B-C-D. However, if an intent arrives for an activity of type B, then a new
+instance of B is added to the stack, even if its launch mode is {@code "singleTop"}.</p>
+  <p class="note"><strong>Note:</strong> When a new instance of an activity is created,
+the user can press the BACK key to return to the previous activity. But when an existing instance of
+an activity handles a new intent, the user cannot press the BACK key to return to the state of
+the activity before the new intent arrived in {@link android.app.Activity#onNewIntent
+onNewIntent()}.</p>
+</dd>
+
+<dt>{@code "singleTask"}</dt>
+  <dd>The system creates a new task and instantiates the activity at the root of the new task.
+However, if an instance of the activity already exists in a separate task, the system routes the
+intent to the existing instance through a call to its {@link
+android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance. Only
+one instance of the activity can exist at a time.
+  <p class="note"><strong>Note:</strong> Although the activity starts in a new task, the
+BACK key still returns the user to the previous activity.</p></dd>
+<dt>{@code "singleInstance"}.</dt>
+  <dd>Same as {@code "singleTask"}, except that the system doesn't launch any other activities into
+the task holding the instance. The activity is always the single and only member of its task;
+any activities started by this one open in a separate task.</dd>
+</dl>
+
+
+<p>As another example, the Android Browser application declares that the web browser activity should
+always open in its own task&mdash;by specifying the {@code singleTask} launch mode in the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a> element.
+This means that if your application issues an
+intent to open the Android Browser, its activity is <em>not</em> placed in the same
+task as your application. Instead, either a new task starts for the Browser or, if the Browser
+already has a task running in the background, that task is brought forward to handle the new
+intent.</p>
+
+<p>Regardless of whether an activity starts in a new task or in the same task as the activity that
+started it, the BACK key always takes the user to the previous activity. However, if you
+start an activity from your task (Task A) that specifies the {@code singleTask} launch mode, then
+that activity might have an instance in the background that belongs to a task with its own back
+stack (Task B). In this
+case, when Task B is brought forward to handle a new intent, the BACK key first navigates
+backward through the activities in Task B before returning to
+the top-most activity in Task A. Figure 4 visualizes this type of scenario.</p>
+
+<img src="{@docRoot}images/fundamentals/diagram_backstack_singletask_multiactivity.png" alt="" />
+<p class="img-caption"><strong>Figure 4.</strong> A representation of how an activity with
+launch mode "singleTask" is added to the back stack. If the activity is already a part of a
+background task with its own back stack (Task B), then the entire back stack also comes
+forward, on top of the current task (Task A).</p>
+
+<p>For more information about using launch modes in the manifest file, see the
+<code><a href="{@docRoot}guide/topics/manifest/activity-element.html">&lt;activity&gt;</a></code>
+element documentation, where the {@code launchMode} attribute and the accepted values are
+discussed more.</p>
+
+<p class="note"><strong>Note:</strong> The behaviors that you specify for your activity with the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> attribute
+can be overriden by flags included with the intent that start your activity, as discussed in the
+next section.</p>
+
+
+
+<h4 id="#IntentFlagsForTasks">Using Intent flags</h4>
+
+<p>When starting an activity, you can modify the default association of an activity to its task
+by including flags in the intent that you deliver to {@link
+android.app.Activity#startActivity startActivity()}. The flags you can use to modify the
+default behavior are:</p>
+
+<p>
+  <dt>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</dt>
+    <dd>Start the activity in a new task. If a task is already running for the activity you are now
+starting, that task is brought to the foreground with its last state restored and the activity
+receives the new intent in {@link android.app.Activity#onNewIntent onNewIntent()}. 
+    <p>This produces the same behavior as the {@code "singleTask"} <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> value,
+discussed in the previous section.</p></dd>
+  <dt>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</dt>
+    <dd>If the activity being started is the current activity (at the top of the back stack), then
+the existing instance receives a call to {@link android.app.Activity#onNewIntent onNewIntent()},
+instead of creating a new instance of the activity.
+    <p>This produces the same behavior as the {@code "singleTop"} <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> value,
+discussed in the previous section.</p></dd>
+  <dt>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</dt>
+    <dd>If the activity being started is already running in the current task, then instead
+of launching a new instance of that activity, all of the other activities on top of it are
+destroyed and this intent is delivered to the resumed instance of the activity (now on top),
+through {@link android.app.Activity#onNewIntent onNewIntent()}).
+    <p>There is no value for the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a>
+attribute that produces this behavior.</p>
+    <p>{@code FLAG_ACTIVITY_CLEAR_TOP} is most often used in conjunction with {@code
+FLAG_ACTIVITY_NEW_TASK}.  When used together, these flags are a way of locating an existing activity
+in another task and putting it in a position where it can respond to the intent. </p>
+    <p class="note"><strong>Note:</strong> If the launch mode of the designated activity is {@code
+"standard"}, it too is removed from the stack and a new instance is launched in its place to handle
+the incoming intent.  That's because a new instance is always created for a new intent when the
+launch mode is {@code "standard"}. </p>
+</dd>
+</dl>
+
+
+
+
+
+<h3 id="Affinities">Handling affinities</h3>
+
+<p>The <em>affinity</em> indicates which task an activity prefers to belong to. By default, all the
+activities from the same application have an affinity for each other. So, by default, all
+activities in the same application prefer to be in the same task. However, you can modify
+the default affinity for an activity. Activities defined in
+different applications can share an affinity, or activities defined in the same application can be
+assigned different task affinities.</p>
+
+<p>You can modify the affinity for any given activity with the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> attribute
+of the <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a>
+element.</p>
+
+<p>The <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a>
+attribute takes a string value, which must be unique from the default package name
+declared in the <a href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code
+&lt;manifest&gt;}</a> element, because the system uses that name to identify the default task
+affinity for the application.</p>
+
+<p>The affinity comes into play in two circumstances:</p>
+<ul>
+  <li>When the intent that launches an activity contains the {@link
+android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag.
+
+<p>A new activity is, by default, launched into the task of the activity
+that called {@link android.app.Activity#startActivity startActivity()}. It's pushed onto the same
+back stack as the caller.  However, if the intent passed to {@link
+android.app.Activity#startActivity startActivity()} contains the {@link
+android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+flag, the system looks for a different task to house the new activity. Often, it's a new task. 
+However, it doesn't have to be.  If there's already an existing task with the same affinity as the
+new activity, the activity is launched into that task.  If not, it begins a new task.</p>
+
+<p>If this flag causes an activity to begin a new task and the user presses the HOME key to leave
+it, there must be some way for the user to navigate back to the task. Some entities (such as the
+notification manager) always start activities in an external task, never as part of their own, so
+they always put {@code FLAG_ACTIVITY_NEW_TASK} in the intents they pass to {@link
+android.app.Activity#startActivity startActivity()}.  If you have an activity that can be invoked by
+an external entity that might use this flag, take care that the user has a independent way to get
+back to the task that's started, such as with a launcher icon (the root activity of the task
+has a {@link android.content.Intent#CATEGORY_LAUNCHER} intent filter; see the <a
+href="#Starting">Starting a task</a> section below).</p>
+</li>
+
+  <li>When an activity has its <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#reparent">{@code
+allowTaskReparenting}</a> attribute set to {@code "true"}.
+  <p>In this case, the activity can move from the task it starts to the task it has an affinity
+for, when that task comes to the foreground.</p>
+  <p>For example, suppose that an activity that reports weather conditions in selected cities is
+defined as part of a travel application.  It has the same affinity as other activities in the same
+application (the default application affinity) and it allows re-parenting with this attribute.
+When one of your activities starts the weather reporter activity, it initially belongs to the same
+task as your activity. However, when the travel application's task comes to the foreground, the
+weather reporter activity is reassigned to that task and displayed within it.</p>
+</li>
+</ul>
+
+<p class="note"><strong>Tip:</strong> If an {@code .apk} file contains more than one "application"
+from the user's point of view, you probably want to use the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a>
+attribute to assign different affinities to the activities associated with each "application".</p>
+
+
+
+<h3 id="Clearing">Clearing the back stack</h3>
+
+<p>If the user leaves a task for a long time, the system clears the task of all activities except
+the root activity.  When the user returns to the task again, only the root activity is restored.
+The system behaves this way, because, after an extended amount of time, users likely have abandoned
+what they were doing before and are returning to the task to begin something new. </p>
+
+<p>There are some activity attributes that you can use to modify this behavior: </p>
+
+<dl>
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#always">alwaysRetainTaskState</a></code>
+</dt>
+<dd>If this attribute is set to {@code "true"} in the root activity of a task,
+the default behavior just described does not happen.
+The task retains all activities in its stack even after a long period.</dd>
+
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#clear">clearTaskOnLaunch</a></code></dt>
+<dd>If this attribute is set to {@code "true"} in the root activity of a task,
+the stack is cleared down to the root activity whenever the user leaves the task
+and returns to it.  In other words, it's the opposite of <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#always">{@code
+alwaysRetainTaskState}</a>.  The user always returns to the task in its
+initial state, even after a leaving the task for only a moment.</dd>
+
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#finish">finishOnTaskLaunch</a></code>
+</dt>
+<dd>This attribute is like <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code clearTaskOnLaunch}</a>,
+but it operates on a
+single activity, not an entire task.  It can also cause any activity to go
+away, including the root activity.  When it's set to {@code "true"}, the
+activity remains part of the task only for the current session.  If the user
+leaves and then returns to the task, it is no longer present.</dd>
+</dl>
+
+
+
+
+<h3 id="Starting">Starting a task</h3>
+
+<p>You can set up an activity as the entry point for a task by giving it an intent filter with
+{@code "android.intent.action.MAIN"} as the specified action and {@code
+"android.intent.category.LAUNCHER"} as the specified category. For example:</p>
+
+<pre>
+&lt;activity ... &gt;
+    &lt;intent-filter ... &gt;
+        &lt;action android:name="android.intent.action.MAIN" /&gt;
+        &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
+    &lt;/intent-filter&gt;
+    ...
+&lt;/activity&gt;
+</pre>
+
+<p>An intent filter of this kind causes an icon and label for the
+activity to be displayed in the application launcher, giving users a way to launch the activity and
+to return to the task that it creates any time after it has been launched.
+</p>
+
+<p>This second ability is important: Users must be able to leave a task and then come back to it
+later using this activity launcher.  For this reason, the two <a href="#LaunchModes">launch
+modes</a> that mark activities as always initiating a task, {@code "singleTask"} and "{@code
+"singleInstance"}, should be used only when the activity has an {@link
+android.content.Intent#ACTION_MAIN}
+and a {@link android.content.Intent#CATEGORY_LAUNCHER}
+filter. Imagine, for example, what could happen if the filter is missing: An intent launches a
+{@code "singleTask"} activity, initiating a new task, and the user spends some time working in
+that task.  The user then presses the HOME key. The task is now sent to the background and not
+visible. Because it is not represented in the application launcher, the user has no way to return to
+the task.
+</p>
+
+<p>For those cases where you don't want the user to be able to return to an activity, set the
+  <code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html">&lt;activity&gt;</a></code> element's
+<a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code
+finishOnTaskLaunch}</a> to {@code "true"} (see <a
+href="#Clearing">Clearing the stack</a>).</p>
+
+
+
+
+<h2>Beginner's Path</h2>
+
+<p>For more information about how to use intents to
+activate other application components and publish the intents to which your components
+respond, continue with the <b><a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent
+Filters</a></b> document.</p>
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/activity_testing.jd b/docs/html/guide/topics/testing/activity_testing.jd
new file mode 100644
index 0000000..6392ad7
--- /dev/null
+++ b/docs/html/guide/topics/testing/activity_testing.jd
@@ -0,0 +1,392 @@
+page.title=Activity Testing
+@jd:body
+
+<div id="qv-wrapper">
+  <div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li>
+      <a href="#ActivityTestAPI">The Activity Testing API</a>
+      <ol>
+        <li>
+            <a href="#ActivityInstrumentationTestCase2">ActivityInstrumentationTestCase2</a>
+        </li>
+        <li>
+            <a href="#ActivityUnitTestCase">ActivityUnitTestCase</a>
+        </li>
+        <li>
+            <a href="#SingleLaunchActivityTestCase">SingleLaunchActivityTestCase</a>
+        </li>
+        <li>
+            <a href="#MockObjectNotes">Mock objects and activity testing</a>
+        </li>
+        <li>
+            <a href="#AssertionNotes">Assertions for activity testing</a>
+        </li>
+      </ol>
+    </li>
+    <li>
+        <a href="#WhatToTest">What to Test</a>
+    </li>
+    <li>
+        <a href="#NextSteps">Next Steps</a>
+    </li>
+    <li>
+      <a href="#UITesting">Appendix: UI Testing Notes</a>
+      <ol>
+        <li>
+          <a href="#RunOnUIThread">Testing on the UI thread</a>
+        </li>
+        <li>
+          <a href="#NotouchMode">Turning off touch mode</a>
+        </li>
+        <li>
+          <a href="#UnlockDevice">Unlocking the Emulator or Device</a>
+        </li>
+        <li>
+          <a href="#UITestTroubleshooting">Troubleshooting UI tests</a>
+        </li>
+      </ol>
+    </li>
+    </ol>
+<h2>Key Classes</h2>
+    <ol>
+      <li>{@link android.test.InstrumentationTestRunner}</li>
+      <li>{@link android.test.ActivityInstrumentationTestCase2}</li>
+      <li>{@link android.test.ActivityUnitTestCase}</li>
+    </ol>
+<h2>Related Tutorials</h2>
+    <ol>
+      <li>
+        <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>
+      </li>
+    </ol>
+<h2>See Also</h2>
+      <ol>
+        <li>
+          <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>
+        </li>
+      </ol>
+  </div>
+</div>
+<p>
+    Activity testing is particularly dependent on the the Android instrumentation framework.
+    Unlike other components, activities have a complex lifecycle based on callback methods; these
+    can't be invoked directly except by instrumentation. Also, the only way to send events to the
+    user interface from a program is through instrumentation.
+</p>
+<p>
+    This document describes how to test activities using instrumentation and other test
+    facilities. The document assumes you have already read
+    <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Fundamentals</a>,
+    the introduction to the Android testing and instrumentation framework.
+</p>
+<h2 id="ActivityTestAPI">The Activity Testing API</h2>
+<p>
+    The activity testing API base class is {@link android.test.InstrumentationTestCase},
+    which provides instrumentation to the test case subclasses you use for Activities.
+</p>
+<p>
+    For activity testing, this base class provides these functions:
+</p>
+<ul>
+    <li>
+        Lifecycle control: With instrumentation, you can start the activity under test, pause it,
+        and destroy it, using methods provided by the test case classes.
+    </li>
+    <li>
+        Dependency injection: Instrumentation allows you to create mock system objects such as
+        Contexts or Applications and use them to run the activity under test. This
+        helps you control the test environment and isolate it from production systems. You can
+        also set up customized Intents and start an activity with them.
+    </li>
+    <li>
+        User interface interaction: You use instrumentation to send keystrokes or touch events
+        directly to the UI of the activity under test.
+    </li>
+</ul>
+<p>
+    The activity testing classes also provide the JUnit framework by extending
+    {@link junit.framework.TestCase} and {@link junit.framework.Assert}.
+</p>
+<p>
+    The two main testing subclasses are {@link android.test.ActivityInstrumentationTestCase2} and
+    {@link android.test.ActivityUnitTestCase}. To test an Activity that is launched in a mode
+    other than <code>standard</code>, you use {@link android.test.SingleLaunchActivityTestCase}.
+</p>
+<h3 id="ActivityInstrumentationTestCase2">ActivityInstrumentationTestCase2</h3>
+<p>
+    The {@link android.test.ActivityInstrumentationTestCase2} test case class is designed to do
+    functional testing of one or more Activities in an application, using a normal system
+    infrastructure. It runs the Activities in a normal instance of the application under test,
+    using a standard system Context. It allows you to send mock Intents to the activity under
+    test, so you can use it to test an activity that responds to multiple types of intents, or
+    an activity that expects a certain type of data in the intent, or both. Notice, though, that it
+    does not allow mock Contexts or Applications, so you can not isolate the test from the rest of
+    a production system.
+</p>
+<h3 id="ActivityUnitTestCase">ActivityUnitTestCase</h3>
+<p>
+    The {@link android.test.ActivityUnitTestCase} test case class tests a single activity in
+    isolation. Before you start the activity, you can inject a mock Context or Application, or both.
+    You use it to run activity tests in isolation, and to do unit testing of methods
+    that do not interact with Android. You can not send mock Intents to the activity under test,
+    although you can call
+    {@link android.app.Activity#startActivity(Intent) Activity.startActivity(Intent)} and then
+    look at arguments that were received.
+</p>
+<h3 id="SingleLaunchActivityTestCase">SingleLaunchActivityTestCase</h3>
+<p>
+    The {@link android.test.SingleLaunchActivityTestCase} class is a convenience class for
+    testing a single activity in an environment that doesn't change from test to test.
+    It invokes {@link junit.framework.TestCase#setUp() setUp()} and
+    {@link junit.framework.TestCase#tearDown() tearDown()} only once, instead of once per
+    method call. It does not allow you to inject any mock objects.
+</p>
+<p>
+    This test case is useful for testing an activity that runs in a mode other than
+    <code>standard</code>. It ensures that the test fixture is not reset between tests. You
+    can then test that the activity handles multiple calls correctly.
+</p>
+<h3 id="MockObjectNotes">Mock objects and activity testing</h3>
+<p>
+    This section contains notes about the use of the mock objects defined in
+    {@link android.test.mock} with activity tests.
+</p>
+<p>
+    The mock object {@link android.test.mock.MockApplication} is only available for activity
+    testing if you use the {@link android.test.ActivityUnitTestCase} test case class.
+    By default, <code>ActivityUnitTestCase</code>, creates a hidden <code>MockApplication</code>
+    object that is used as the application under test. You can inject your own object using
+    {@link android.test.ActivityUnitTestCase#setApplication(Application) setApplication()}.
+</p>
+<h3 id="AssertionNotes">Assertions for activity testing</h3>
+<p>
+    {@link android.test.ViewAsserts} defines assertions for Views. You use it to verify the
+    alignment and position of View objects, and to look at the state of ViewGroup objects.
+</p>
+<h2 id="WhatToTest">What To Test</h2>
+<ul>
+    <li>
+        Input validation: Test that an activity responds correctly to input values in an
+        EditText View. Set up a keystroke sequence, send it to the activity, and then
+        use {@link android.view.View#findViewById(int)} to examine the state of the View. You can
+        verify that a valid keystroke sequence enables an OK button, while an invalid one leaves the
+        button disabled. You can also verify that the Activity responds to invalid input by
+        setting error messages in the View.
+    </li>
+    <li>
+        Lifecycle events: Test that each of your application's activities handles lifecycle events
+        correctly. In general, lifecycle events are actions, either from the system or from the
+        user, that trigger a callback method such as <code>onCreate()</code> or
+        <code>onClick()</code>. 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>
+        Intents: Test that each activity correctly handles the intents listed in the intent
+        filter specified in its manifest. You can use
+        {@link android.test.ActivityInstrumentationTestCase2} to send mock Intents to the
+        activity under test.
+    </li>
+    <li>
+        Runtime configuration changes: Test that each activity responds correctly to the
+        possible changes in the device's configuration while your application is running. These
+        include a change to the device's orientation, a change to the current language, and so
+        forth. Handling these changes is described in detail in the topic
+        <a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime
+        Changes</a>.
+    </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>.
+    </li>
+</ul>
+<h2 id="NextSteps">Next Steps</h2>
+<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 testing activities, 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 activity-oriented application.
+    </li>
+</ul>
+<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.
+</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.
+</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>.
+</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 application 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>
+<pre>
+  private MyActivity mActivity; // MyActivity is the class name of the app under test
+  private Spinner mSpinner;
+
+  ...
+
+  protected void setUp() throws Exception {
+      super.setUp();
+      mInstrumentation = getInstrumentation();
+
+      mActivity = getActivity(); // get a references to the app under test
+
+      /*
+       * Get a reference to the main widget of the app under test, a Spinner
+       */
+      mSpinner = (Spinner) mActivity.findViewById(com.android.demo.myactivity.R.id.Spinner01);
+
+  ...
+
+  public void aTest() {
+      /*
+       * request focus for the Spinner, so that the test can send key events to it
+       * This request must be run on the UI thread. To do this, use the runOnUiThread method
+       * and pass it a Runnable that contains a call to requestFocus on the Spinner.
+       */
+      mActivity.runOnUiThread(new Runnable() {
+          public void run() {
+              mSpinner.requestFocus();
+          }
+      });
+
+      mInstrumentation.waitForIdleSync();
+
+      this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+</pre>
+
+<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.
+</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>.
+</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.
+</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 with code in
+    the published application.
+</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:
+</p>
+<pre>
+  mKeyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
+  mLock = mKeyGuardManager.newKeyguardLock("<em>activity_classname</em>");
+  mLock.disableKeyguard();
+</pre>
+<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:
+</p>
+<dl>
+    <dt><code>WrongThreadException</code></dt>
+    <dd>
+      <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.
+      </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.
+      <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>
+        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:
+      <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>.
+      <p><strong>Suggested Resolution:</strong></p>
+        Remove the <code>@UiThreadTest</code> annotation, remove the <code>runOnUiThread()</code>
+        call, or re-factor your tests.
+    </dd>
+</dl>
diff --git a/docs/html/guide/topics/testing/contentprovider_testing.jd b/docs/html/guide/topics/testing/contentprovider_testing.jd
new file mode 100644
index 0000000..893b5c9
--- /dev/null
+++ b/docs/html/guide/topics/testing/contentprovider_testing.jd
@@ -0,0 +1,224 @@
+page.title=Content Provider Testing
+@jd:body
+
+<div id="qv-wrapper">
+  <div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li>
+        <a href="#DesignAndTest">Content Provider Design and Testing</a>
+    </li>
+    <li>
+      <a href="#ContentProviderTestAPI">The Content Provider Testing API</a>
+      <ol>
+        <li>
+          <a href="#ProviderTestCase2">ProviderTestCase2 </a>
+        </li>
+        <li>
+          <a href="#MockObjects">Mock object classes</a>
+        </li>
+      </ol>
+    </li>
+    <li>
+        <a href="#WhatToTest">What To Test</a>
+    </li>
+    <li>
+        <a href="#NextSteps">Next Steps</a>
+    </li>
+  </ol>
+  <h2>Key Classes</h2>
+    <ol>
+      <li>{@link android.test.InstrumentationTestRunner}</li>
+      <li>{@link android.test.ProviderTestCase2}</li>
+      <li>{@link android.test.IsolatedContext}</li>
+      <li>{@link android.test.mock.MockContentResolver}</li>
+    </ol>
+  <h2>See Also</h2>
+      <ol>
+        <li>
+          <a
+          href="{@docRoot}guide/topics/testing/topics/testing_android.html">
+          Testing Fundamentals</a>
+        </li>
+        <li>
+          <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>
+        </li>
+      </ol>
+  </div>
+</div>
+<p>
+    Content providers, which store and retrieve data and make it accessible across applications,
+    are a key part of the Android API. As an application developer you're allowed to provide your
+    own public providers for use by other applications. If you do, then you should test them
+    using the API you publish.
+</p>
+<p>
+    This document describes how to test public content providers, although the information is
+    also applicable to providers that you keep private to your own application. If you aren't
+    familiar with content  providers or the Android testing framework, please read
+    <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>,
+    the guide to developing content providers, and
+    <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Fundamentals</a>,
+    the introduction to the Android testing and instrumentation framework.
+</p>
+<h2 id="DesignAndTest">Content Provider Design and Testing</h2>
+<p>
+    In Android, content providers are viewed externally as data APIs that provide
+    tables of data, with their internals hidden from view. A content provider may have many
+    public constants, but it usually has few if any public methods and no public variables.
+    This suggests that you should write your tests based only on the provider's public members.
+    A content provider that is designed like this is offering a contract between itself and its
+    users.
+</p>
+<p>
+    The base test case class for content providers,
+    {@link android.test.ProviderTestCase2}, allows you to test your content provider in an
+    isolated environment. Android mock objects such as {@link android.test.IsolatedContext} and
+    {@link android.test.mock.MockContentResolver} also help provide an isolated test environment.
+</p>
+<p>
+    As with other Android tests, provider test packages are run under the control of the test
+    runner {@link android.test.InstrumentationTestRunner}. The section
+    <a href="{@docRoot}guide/topics/testing/testing_android.html#InstrumentationTestRunner">
+    Running Tests With InstrumentationTestRunner</a> describes the test runner in
+    more detail. The topic <a href="{@docRoot}guide/developing/testing/testing_eclipse.html">
+    Testing in Eclipse, with ADT</a> shows you how to run a test package in Eclipse, and the
+    topic <a href="{@docRoot}guide/developing/testing/testing_otheride.html">
+    Testing in Other IDEs</a>
+    shows you how to run a test package from the command line.
+</p>
+<h2 id="ContentProviderTestAPI">Content Provider Testing API</h2>
+<p>
+    The main focus of the provider testing API is to provide an isolated testing environment. This
+    ensures that tests always run against data dependencies set explicitly in the test case. It
+    also prevents tests from modifying actual user data. For example, you want to avoid writing
+    a test that fails because there was data left over from a previous test, and you want to
+    avoid adding or deleting contact information in a actual provider.
+</p>
+<p>
+    The test case class and mock object classes for provider testing set up this isolated testing
+    environment for you.
+</p>
+<h3 id="ProviderTestCase2">ProviderTestCase2</h3>
+<p>
+    You test a provider with a subclass of {@link android.test.ProviderTestCase2}. This base class
+    extends {@link android.test.AndroidTestCase}, so it provides the JUnit testing framework as well
+    as Android-specific methods for testing application permissions. The most important
+    feature of this class is its initialization, which creates the isolated test environment.
+</p>
+<p>
+    The initialization is done in the constructor for {@link android.test.ProviderTestCase2}, which
+    subclasses call in their own constructors. The {@link android.test.ProviderTestCase2}
+    constructor creates an {@link android.test.IsolatedContext} object that allows file and
+    database operations but stubs out other interactions with the Android system.
+    The file and database operations themselves take place in a directory that is local to the
+    device or emulator and has a special prefix.
+</p>
+<p>
+    The constructor then creates a {@link android.test.mock.MockContentResolver} to use as the
+    resolver for the test. The {@link android.test.mock.MockContentResolver} class is described in
+    detail in the section
+    <a href="{@docRoot}guide/topics/testing/test_android#MockObjectClasses">Mock object classes</a>.
+</p>
+<p>
+    Lastly, the constructor creates an instance of the provider under test. This is a normal
+    {@link android.content.ContentProvider} object, but it takes all of its environment information
+    from the {@link android.test.IsolatedContext}, so it is restricted to
+    working in the isolated test environment. All of the tests done in the test case class run
+    against this isolated object.
+</p>
+<h3 id="MockObjects">Mock object classes</h3>
+<p>
+    {@link android.test.ProviderTestCase2} uses {@link android.test.IsolatedContext} and
+    {@link android.test.mock.MockContentResolver}, which are standard mock object classes. To
+    learn more about them, please read
+    <a href="{@docRoot}guide/topics/testing/test_android#MockObjectClasses">
+    Testing Fundamentals</a>.
+</p>
+<h2 id="WhatToTest">What To Test</h2>
+<p>
+    The topic <a href="{@docRoot}guide/topics/testing/what_to_test.html">What To Test</a>
+    lists general considerations for testing Android components.
+    Here are some specific guidelines for testing content providers.
+</p>
+<ul>
+    <li>
+        Test with resolver methods: Even though you can instantiate a provider object in
+        {@link android.test.ProviderTestCase2}, you should always test with a resolver object
+        using the appropriate URI. This ensures that you are testing the provider using the same
+        interaction that a regular application would use.
+    </li>
+    <li>
+        Test a public provider as a contract: If you intent your provider to be public and
+        available to other applications, you should test it as a contract. This includes
+        the following ideas:
+        <ul>
+            <li>
+                Test with constants that your provider publicly exposes. For
+                example, look for constants that refer to column names in one of the provider's
+                data tables. These should always be constants publicly defined by the provider.
+            </li>
+            <li>
+                Test all the URIs offered by your provider. Your provider may offer several URIs,
+                each one referring to a different aspect of the data. The
+                <a href="{@docRoot}resources/samples/NotePad/index.html">Note Pad</a> sample,
+                for example, features a provider that offers one URI for retrieving a list of notes,
+                another for retrieving an individual note by it's database ID, and a third for
+                displaying notes in a live folder. The sample test package for Note Pad,
+                <a href="{@docRoot}resources/samples/NotePadTest/index.html"> Note Pad Test</a>, has
+                unit tests for two of these URIs.
+            </li>
+            <li>
+                Test invalid URIs: Your unit tests should deliberately call the provider with an
+                invalid URI, and look for errors. Good provider design is to throw an
+                IllegalArgumentException for invalid URIs.
+
+            </li>
+        </ul>
+    </li>
+    <li>
+        Test the standard provider interactions: Most providers offer six access methods:
+        query, insert, delete, update, getType, and onCreate(). Your tests should verify that all
+        of these methods work. These are described in more detail in the topic
+        <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>.
+    </li>
+    <li>
+        Test business logic: Don't forget to test the business logic that your provider should
+        enforce. Business logic includes handling of invalid values, financial or arithmetic
+        calculations, elimination or combining of duplicates, and so forth. A content provider
+        does not have to have business logic, because it may be implemented by activities that
+        modify the data. If the provider does implement business logic, you should test it.
+    </li>
+</ul>
+<h2 id="NextSteps">Next Steps</h2>
+<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 testing activities, 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 activity-oriented application.
+    </li>
+</ul>
diff --git a/docs/html/guide/topics/testing/index.jd b/docs/html/guide/topics/testing/index.jd
new file mode 100644
index 0000000..92ed5a7
--- /dev/null
+++ b/docs/html/guide/topics/testing/index.jd
@@ -0,0 +1,80 @@
+page.title=Testing
+@jd:body
+<p>
+    The Android development environment includes an integrated testing framework that helps you
+    test all aspects of your application.
+</p>
+<h4>Fundamentals</h4>
+<p>
+    To start learning how to use the framework to create tests for your applications, please
+    read the topic <a href="{@docRoot}guide/topics/testing/testing_android.html">
+    Testing Fundamentals</a>.
+</p>
+<h4>Concepts</h4>
+<ul>
+    <li>
+        Testing Tools describes the Eclipse with ADT and command-line tools you use to test
+        Android applications.
+    </li>
+    <li>
+        What to Test is an overview of the types of testing you should do. It focuses on testing
+        system-wide aspects of Android that can affect every component in your application.
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/testing/activity_testing.html">
+        Activity Testing</a> focuses on testing activities. It describes how instrumentation allows
+        you to control activities outside the normal application lifecycle. It also lists
+        activity-specific features you should test, and it provides tips for testing Android
+        user interfaces.
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/testing/contentprovider_testing.html">
+        Content Provider Testing</a> focuses on testing content providers. It describes the
+        mock system objects you can use, provides tips for designing providers so that they
+        can be tested, and lists provider-specific features you should test.
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/testing/service_testing.html">
+        Service Testing</a> focuses on testing services. It also lists service-specific features
+        you should test.
+    </li>
+</ul>
+<h4>Procedures</h4>
+<ul>
+    <li>
+        The topic <a href="{@docRoot}guide/developing/testing/testing_eclipse.html">
+        Testing in Eclipse, with ADT</a> describes how to create and run tests in Eclipse with ADT.
+    </li>
+    <li>
+        The topic <a href="{@docRoot}guide/developing/testing/testing_otheride.html">
+        Testing in other IDEs</a> describes how to create and run tests with command-line tools.
+    </li>
+</ul>
+<h4>Tutorials</h4>
+<ul>
+    <li>
+        The <a href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">
+        Hello, Testing</a> tutorial introduces basic testing concepts and procedures.
+    </li>
+    <li>
+        For a more advanced tutorial, try
+        <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>,
+        which guides you through a more complex testing scenario.
+    </li>
+</ul>
+<h4>Samples</h4>
+<ul>
+    <li>
+        <a href="{@docRoot}resources/samples/NotePadTest.html">Note Pad Provider
+        Test</a> is a test package for the
+        <a href="{@docRoot}resources/samples/NotePad.html">Note Pad</a> sample
+        application. It provides a simple example of unit testing
+        a {@link android.content.ContentProvider}.
+    </li>
+    <li>
+        The <a href="{@docRoot}resources/samples/AlarmServiceTest.html">Alarm Service Test</a>
+        is a test package for the <a href="{@docRoot}resources/samples/Alarm.html">Alarm</a>
+        sample application. It provides a simple example of unit
+        testing a {@link android.app.Service}.
+    </li>
+</ul>
diff --git a/docs/html/guide/topics/testing/service_testing.jd b/docs/html/guide/topics/testing/service_testing.jd
new file mode 100644
index 0000000..3979f3c
--- /dev/null
+++ b/docs/html/guide/topics/testing/service_testing.jd
@@ -0,0 +1,178 @@
+page.title=Service Testing
+@jd:body
+
+<div id="qv-wrapper">
+  <div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li>
+        <a href="#DesignAndTest">Service Design and Testing</a>
+    </li>
+    <li>
+        <a href="#ServiceTestCase">ServiceTestCase</a>
+    </li>
+    <li>
+        <a href="#MockObjects">Mock object classes</a>
+    </li>
+    <li>
+        <a href="#TestAreas">What to Test</a>
+    </li>
+  </ol>
+  <h2>Key Classes</h2>
+    <ol>
+      <li>{@link android.test.InstrumentationTestRunner}</li>
+      <li>{@link android.test.ServiceTestCase}</li>
+      <li>{@link android.test.mock.MockApplication}</li>
+      <li>{@link android.test.RenamingDelegatingContext}</li>
+    </ol>
+  <h2>Related Tutorials</h2>
+    <ol>
+        <li>
+            <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>
+        </li>
+    </ol>
+  <h2>See Also</h2>
+      <ol>
+        <li>
+          <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>
+        </li>
+      </ol>
+  </div>
+</div>
+<p>
+    Android provides a testing framework for Service objects that can run them in
+    isolation and provides mock objects. The test case class for Service objects is
+    {@link android.test.ServiceTestCase}. Since the Service class assumes that it is separate
+    from its clients, you can test a Service object without using instrumentation.
+</p>
+<p>
+    This document describes techniques for testing Service objects. If you aren't familiar with the
+    Service class, please read <a href="{@docRoot}guide/topics/fundamentals.html">
+    Application Fundamentals</a>. If you aren't familiar with Android testing, please read
+    <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Fundamentals</a>,
+    the introduction to the Android testing and instrumentation framework.
+</p>
+<h2 id="DesignAndTest">Service Design and Testing</h2>
+<p>
+    When you design a Service, you should consider how your tests can examine the various states
+    of the Service lifecycle. If the lifecycle methods that start up your Service, such as
+    {@link android.app.Service#onCreate() onCreate()} or
+    {@link android.app.Service#onStartCommand(Intent, int, int) onStartCommand()} do not normally
+    set a global variable to indicate that they were successful, you may want to provide such a
+    variable for testing purposes.
+</p>
+<p>
+    Most other testing is facilitated by the methods in the {@link android.test.ServiceTestCase}
+    test case class. For example, the {@link android.test.ServiceTestCase#getService()} method
+    returns a handle to the Service under test, which you can test to confirm that the Service is
+    running even at the end of your tests.
+</p>
+<h2 id="ServiceTestCase">ServiceTestCase</h2>
+<p>
+    {@link android.test.ServiceTestCase} extends the JUnit {@link junit.framework.TestCase} class
+    with with methods for testing application permissions and for controlling the application and
+    Service under test. It also provides mock application and Context objects that isolate your
+    test from the rest of the system.
+</p>
+<p>
+    {@link android.test.ServiceTestCase} defers initialization of the test environment until you
+    call {@link android.test.ServiceTestCase#startService(Intent) ServiceTestCase.startService()} or
+    {@link android.test.ServiceTestCase#bindService(Intent) ServiceTestCase.bindService()}. This
+    allows you to set up your test environment, particularly your mock objects, before the Service
+    is started.
+</p>
+<p>
+    Notice that the parameters to <code>ServiceTestCase.bindService()</code>are different from
+    those for <code>Service.bindService()</code>. For the <code>ServiceTestCase</code> version,
+    you only provide an Intent. Instead of returning a boolean,
+    <code>ServiceTestCase.bindService()</code> returns an object that subclasses
+    {@link android.os.IBinder}.
+</p>
+<p>
+    The {@link android.test.ServiceTestCase#setUp()} method for {@link android.test.ServiceTestCase}
+    is called before each test. It sets up the test fixture by making a copy of the current system
+    Context before any test methods touch it. You can retrieve this Context by calling
+    {@link android.test.ServiceTestCase#getSystemContext()}. If you override this method, you must
+    call <code>super.setUp()</code> as the first statement in the override.
+</p>
+<p>
+    The methods {@link android.test.ServiceTestCase#setApplication(Application) setApplication()}
+    and {@link android.test.AndroidTestCase#setContext(Context)} setContext()} allow you to set
+    a mock Context or mock Application (or both) for the Service, before you start it. These mock
+    objects are described in <a href="#MockObjects">Mock object classes</a>.
+</p>
+<p>
+    By default, {@link android.test.ServiceTestCase} runs the test method
+    {@link android.test.AndroidTestCase#testAndroidTestCaseSetupProperly()}, which asserts that
+    the base test case class successfully set up a Context before running.
+</p>
+<h2 id="MockObjects">Mock object classes</h2>
+<p>
+    <code>ServiceTestCase</code> assumes that you will use a mock Context or mock Application
+    (or both) for the test environment. These objects isolate the test environment from the
+    rest of the system. If you don't provide your own instances of these objects before you
+    start the Service, then {@link android.test.ServiceTestCase} will create its own internal
+    instances and inject them into the Service. You can override this behavior by creating and
+    injecting your own instances before starting the Service
+</p>
+<p>
+    To inject a mock Application object into the Service under test, first create a subclass of
+    {@link android.test.mock.MockApplication}. <code>MockApplication</code> is a subclass of
+    {@link android.app.Application} in which all the methods throw an Exception, so to use it
+    effectively you subclass it and override the methods you need. You then inject it into the
+    Service with the
+    {@link android.test.ServiceTestCase#setApplication(Application) setApplication()} method.
+    This mock object allows you to control the application values that the Service sees, and
+    isolates it from the real system. In addition, any hidden dependencies your Service has on
+    its application reveal themselves as exceptions when you run the test.
+</p>
+<p>
+    You inject a mock Context into the Service under test with the
+    {@link android.test.AndroidTestCase#setContext(Context) setContext()} method. The mock
+    Context classes you can use are described in more detail in
+    <a href="{@docRoot}guide/topics/testing/testing_android.html#MockObjectClasses">
+    Testing Fundamentals</a>.
+</p>
+<h2 id="TestAreas">What to Test</h2>
+<p>
+    The topic <a href="{@docRoot}guide/topics/testing/what_to_test.html">What To Test</a>
+    lists general considerations for testing Android components.
+    Here are some specific guidelines for testing a Service:
+</p>
+<ul>
+    <li>
+        Ensure that the {@link android.app.Service#onCreate()} is called in response to
+        {@link android.content.Context#startService(Intent) Context.startService()} or
+    {@link android.content.Context#bindService(Intent,ServiceConnection,int) Context.bindService()}.
+        Similarly, you should ensure that {@link android.app.Service#onDestroy()} is called in
+        response to {@link android.content.Context#stopService(Intent) Context.stopService()},
+        {@link android.content.Context#unbindService(ServiceConnection) Context.unbindService()},
+        {@link android.app.Service#stopSelf()}, or
+        {@link android.app.Service#stopSelfResult(int) stopSelfResult()}.
+    </li>
+    <li>
+        Test that your Service correctly handles multiple calls from
+        <code>Context.startService()</code>. Only the first call triggers
+        <code>Service.onCreate()</code>, but all calls trigger a call to
+        <code>Service.onStartCommand()</code>.
+        <p>
+            In addition, remember that <code>startService()</code> calls don't
+            nest, so a single call to <code>Context.stopService()</code> or
+            <code>Service.stopSelf()</code> (but not <code>stopSelf(int)</code>)
+            will stop the Service. You should test that your Service stops at the correct point.
+        </p>
+    </li>
+    <li>
+        Test any business logic that your Service implements. Business logic includes checking for
+        invalid values, financial and arithmetic calculations, and so forth.
+    </li>
+</ul>
diff --git a/docs/html/guide/topics/testing/testing_android.jd b/docs/html/guide/topics/testing/testing_android.jd
index 935aaf9..1d5f911 100755
--- a/docs/html/guide/topics/testing/testing_android.jd
+++ b/docs/html/guide/topics/testing/testing_android.jd
@@ -1,4 +1,4 @@
-page.title=Testing and Instrumentation
+page.title=Testing Fundamentals
 @jd:body
 
 <div id="qv-wrapper">
@@ -6,65 +6,62 @@
   <h2>In this document</h2>
   <ol>
     <li>
-        <a href="#Overview">Overview</a>
+        <a href="#TestStructure">Test Structure</a>
+    </li>
+    <li>
+        <a href="#TestProjects">Test Projects</a>
     </li>
     <li>
       <a href="#TestAPI">The Testing API</a>
       <ol>
         <li>
-          <a href="#Extensions">JUnit test case classes</a>
+          <a href="#JUnit">JUnit</a>
         </li>
         <li>
-          <a href="#Instrumentation">Instrumentation test case classes</a>
+          <a href="#Instrumentation">Instrumentation</a>
         </li>
         <li>
-          <a href="#Assert">Assert classes</a>
+            <a href="#TestCaseClasses">Test case classes</a>
         </li>
         <li>
-          <a href="#MockObjects">Mock object classes</a>
+          <a href="#AssertionClasses">Assertion classes</a>
         </li>
-            <li>
-                <a href="#InstrumentationTestRunner">Instrumentation Test Runner</a>
-            </li>
+        <li>
+          <a href="#MockObjectClasses">Mock object classes</a>
+        </li>
       </ol>
     </li>
     <li>
-     <a href="#TestEnviroment">Working in the Test Environment</a>
+        <a href="#InstrumentationTestRunner">Running Tests</a>
     </li>
     <li>
-        <a href="#TestAreas">What to Test</a>
+        <a href="#TestResults">Seeing Test Results</a>
     </li>
     <li>
-      <a href="#UITesting">Appendix: UI Testing Notes</a>
-      <ol>
-        <li>
-          <a href="#RunOnUIThread">Testing on the UI thread</a>
-        </li>
-        <li>
-          <a href="#NotouchMode">Turning off touch mode</a>
-        </li>
-        <li>
-          <a href="#UnlockDevice">Unlocking the Emulator or Device</a>
-        </li>
-        <li>
-          <a href="#UITestTroubleshooting">Troubleshooting UI tests</a>
-        </li>
-      </ol>
+        <a href="#Monkeys">Monkey and MonkeyRunner</a>
+    </li>
+    <li>
+       <a href="#PackageNames">Working With Package Names</a>
+    </li>
+    <li>
+        <a href="#WhatToTest">What To Test</a>
+    </li>
+    <li>
+        <a href="#NextSteps">Next Steps</a>
     </li>
   </ol>
   <h2>Key classes</h2>
     <ol>
       <li>{@link android.test.InstrumentationTestRunner}</li>
-      <li>{@link android.test.ActivityInstrumentationTestCase2}</li>
-      <li>{@link android.test.ActivityUnitTestCase}</li>
-      <li>{@link android.test.ApplicationTestCase}</li>
-      <li>{@link android.test.ProviderTestCase2}</li>
-      <li>{@link android.test.ServiceTestCase}</li>
+      <li>{@link android.test}</li>
+      <li>{@link android.test.mock}</li>
+      <li>{@link junit.framework}</li>
     </ol>
   <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,445 +70,590 @@
   <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>
 </div>
-
-<p>Android includes a powerful set of testing tools that extend the
-industry-standard JUnit test framework with features specific to the Android
-environment. Although you can test an Android application with JUnit, the
-Android tools allow you to write much more sophisticated tests for every aspect
-of your application, both at the unit and framework levels.</p>
-
-<p>Key features of the Android testing environment include:</p>
-
+<p>
+    The Android testing framework, an integral part of the development environment,
+    provides an architecture and powerful tools that help you test every aspect of your application
+    at every level from unit to framework.
+</p>
+<p>
+    The testing framework has these key features:
+</p>
 <ul>
-  <li>Android extensions to the JUnit framework that provide access to Android
-system objects.</li>
-  <li>An instrumentation framework that lets tests control and examine the
-application.</li>
-  <li>Mock versions of commonly-used Android system objects.</li>
-  <li>Tools for running single tests or test suites, with or without
-instrumentation.</li>
-  <li>Support for managing tests and test projects in the ADT Plugin for Eclipse
-and at the command line.</li>
+    <li>
+        Android test suites are based on JUnit. You can use plain JUnit to test a class that doesn't
+        call the Android API, or Android's JUnit extensions to test Android components. If you're
+        new to Android testing, you can start with general-purpose test case classes such as {@link
+        android.test.AndroidTestCase} and then go on to use more sophisticated classes.
+    </li>
+    <li>
+        The Android JUnit extensions provide component-specific test case classes. These classes
+        provide helper methods for creating mock objects and methods that help you control the
+        lifecycle of a component.
+    </li>
+    <li>
+        Test suites are contained in test packages that are similar to main application packages, so
+        you don't need to learn a new set of tools or techniques for designing and building tests.
+    </li>
+    <li>
+        The SDK tools for building and tests are available in Eclipse with ADT, and also in
+        command-line form for use with other IDES. These tools get information from the project of
+        the application under test and use this information to automatically create the build files,
+        manifest file, and directory structure for the test package.
+    </li>
+    <li>
+        The SDK also provides
+        <a href="{@docRoot}guide/topics/testing/monkeyrunner.html">MonkeyRunner</a>, an API for
+        testing devices with Jython scripts, and <a
+        href="{@docRoot}guide/developing/tools/monkey.html">Monkey</a>, a command-line tool for
+        stress-testing UIs by sending pseudo-random events to a device.
+    </li>
 </ul>
-
-<p>This document is an overview of the Android testing environment and the way
-you use it. The document assumes you have a basic knowledge of Android
-application programming and JUnit testing methodology.</p>
-
-<h2 id="Overview">Overview</h2>
-
-<p> At the heart of the Android testing environment is an instrumentation
-framework that your test application uses to precisely control the application
-under test. With instrumentation, you can set up mock system objects such as
-Contexts before the main application starts, control your application at various
-points of its lifecycle, send UI events to the application, and examine the
-application's state during its execution. The instrumentation framework
-accomplishes this by running both the main application and the test application
-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>
-
-<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>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>
-
-<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>
-</ul>
-
+<p>
+    This document describes the fundamentals of the Android testing framework, including the
+    structure of tests, the APIs that you use to develop tests, and the tools that you use to run
+    tests and view results. The document assumes you have a basic knowledge of Android application
+    programming and JUnit testing methodology.
+</p>
+<p>
+    The following diagram summarizes the testing framework:
+</p>
+<div style="width: 70%; margin-left:auto; margin-right:auto;">
+<a href="{@docRoot}images/testing/test_framework.png">
+    <img src="{@docRoot}images/testing/test_framework.png"
+        alt="The Android testing framework"/>
+</a>
+</div>
+<h2 id="TestStructure">Test Structure</h2>
+<p>
+    Android's build and test tools assume that test projects are organized into a standard
+    structure of tests, test case classes, test packages, and test projects.
+</p>
+<p>
+    Android testing is based on JUnit. In general, a JUnit test is a method whose
+    statements test a part of the application under test. You organize test methods into classes
+    called test cases (or test suites). Each test is an isolated test of an individual module in
+    the application under test. Each class is a container for related test methods, although it
+    often provides helper methods as well.
+</p>
+<p>
+    In JUnit, you build one or more test source files into a class file. Similarly, in Android you
+    use the SDK's build tools to build one or more test source files into class files in an
+    Android test package. In JUnit, you use a test runner to execute test classes. In Android, you
+    use test tools to load the test package and the application under test, and the tools then
+    execute an Android-specific test runner.
+</p>
+<h2 id="TestProjects">Test Projects</h2>
+<p>
+    Tests, like Android applications, are organized into projects.
+</p>
+<p>
+    A test project is a directory or Eclipse project in which you create the source code, manifest
+    file, and other files for a test package. The Android SDK contains tools for Eclipse with ADT
+    and for the command line that create and update test projects for you. The tools create the
+    directories you use for source code and resources and the manifest file for the test package.
+    The command-line tools also create the Ant build files you need.
+</p>
+<p>
+    You should always use Android tools to create a test project. Among other benefits,
+    the tools:
+</p>
+    <ul>
+        <li>
+            Automatically set up your test package to use
+            {@link android.test.InstrumentationTestRunner} as the test case runner. You must use
+            <code>InstrumentationTestRunner</code> (or a subclass) to run JUnit tests.
+        </li>
+        <li>
+            Create an appropriate name for the test package. If the application
+            under test has a package name of <code>com.mydomain.myapp</code>, then the
+            Android tools set the test package name to <code>com.mydomain.myapp.test</code>. This
+            helps you identify their relationship, while preventing conflicts within the system.
+        </li>
+        <li>
+            Automatically create the proper build files, manifest file, and directory
+            structure for the test project. This helps you to build the test package without
+            having to modify build files and sets up the linkage between your test package and
+            the application under test.
+            The
+        </li>
+    </ul>
+<p>
+    You can create a test project anywhere in your file system, but the best approach is to
+    add the test project so that its root directory <code>tests/</code> is at the same level
+    as the <code>src/</code> directory of the main application's project. This helps you find the
+    tests associated with an application. For example, if your application project's root directory
+    is <code>MyProject</code>, then you should use the following directory structure:
+</p>
+<pre class="classic no-pretty-print">
+  MyProject/
+      AndroidManifest.xml
+      res/
+          ... (resources for main application)
+      src/
+          ... (source code for main application) ...
+      tests/
+          AndroidManifest.xml
+          res/
+              ... (resources for tests)
+          src/
+              ... (source code for tests)
+</pre>
 <h2 id="TestAPI">The Testing API</h2>
 <p>
-    For writing tests and test applications in the Java programming language, Android provides a
-    testing API that is based in part on the JUnit test framework. Adding to that, Android includes
-    a powerful instrumentation framework that lets your tests access the state and runtime objects
-    of the application under tests.
+    The Android testing API is based on the JUnit API and extended with a instrumentation
+    framework and Android-specific testing classes.
 </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>
+<h3 id="JUnit">JUnit</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.
-<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.
-    </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
-      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}.
-    </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.
+    You can use the JUnit {@link junit.framework.TestCase TestCase} class to do unit testing on
+    a plain Java object. <code>TestCase</code> is also the base class for
+    {@link android.test.AndroidTestCase}, which you can use to test Android-dependent objects.
+    Besides providing the JUnit framework, AndroidTestCase offers Android-specific setup,
+    teardown, and helper methods.
 </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:
+    You use the JUnit {@link junit.framework.Assert} class to display test results.
+    The assert methods compare values you expect from a test to the actual results and
+    throw an exception if the comparison fails. Android also provides a class of assertions that
+    extend the possible types of comparisons, and another class of assertions for testing the UI.
+    These are described in more detail in the section <a href="#AssertionClasses">
+    Assertion classes</a>
 </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.
-    </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.
-    </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>
-    </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.
-    </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}:
+    To learn more about JUnit, you can read the documentation on the
+    <a href="http://www.junit.org">junit.org</a> home page.
+    Note that the Android testing API supports JUnit 3 code style, but not JUnit 4. Also, you must
+    use Android's instrumented test runner {@link android.test.InstrumentationTestRunner} to run
+    your test case classes. This test runner is described in the
+    section <a href="#InstrumentationTestRunner">Running Tests</a>.
+</p>
+<h3 id="Instrumentation">Instrumentation</h3>
+<p>
+    Android instrumentation is a set of control methods or "hooks" in the Android system. These hooks
+    control an Android component independently of its normal lifecycle. They also control how
+    Android loads applications.
+</p>
+<p>
+    Normally, an Android component runs in a lifecycle determined by the system. For example, an
+    Activity object's lifecycle starts when the Activity is activated by an Intent. The object's
+    <code>onCreate()</code> method is called, followed by <code>onResume()</code>. When the user
+    starts another application, the <code>onPause()</code> method is called. If the Activity
+    code calls the <code>finish()</code> method, the <code>onDestroy()</code> method is called.
+    The Android framework API does not provide a way for your code to invoke these callback
+    methods directly, but you can do so using instrumentation.
+</p>
+<p>
+    Also, the system runs all the components of an application into the same
+    process. You can allow some components, such as content providers, to run in a separate process,
+    but you can't force an application to run in the same process as another application that is
+    already running.
+</p>
+<p>
+    With Android instrumentation, though, you can invoke callback methods in your test code.
+    This allows you to run through the lifecycle of a component step by step, as if you were
+    debugging the component. The following test code snippet demonstrates how to use this to
+    test that an Activity saves and restores its state:
+</p>
+<a name="ActivitySnippet"></a>
+<pre>
+    // Start the main activity of the application under test
+    mActivity = getActivity();
+
+    // Get a handle to the Activity object's main UI widget, a Spinner
+    mSpinner = (Spinner)mActivity.findViewById(com.android.example.spinner.R.id.Spinner01);
+
+    // Set the Spinner to a known position
+    mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
+
+    // Stop the activity - The onDestroy() method should save the state of the Spinner
+    mActivity.finish();
+
+    // Re-start the Activity - the onResume() method should restore the state of the Spinner
+    mActivity = getActivity();
+
+    // Get the Spinner's current position
+    int currentPosition = mActivity.getSpinnerPosition();
+
+    // Assert that the current position is the same as the starting position
+    assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
+</pre>
+<p>
+    The key method used here is
+    {@link android.test.ActivityInstrumentationTestCase2#getActivity()}, which is a
+    part of the instrumentation API. The Activity under test is not started until you call this
+    method. You can set up the test fixture in advance, and then call this method to start the
+    Activity.
+</p>
+<p>
+    Also, instrumentation can load both a test package and the application under test into the
+    same process. Since the application components and their tests are in the same process, the
+    tests can invoke methods in the components, and modify and examine fields in the components.
+</p>
+<h3 id="TestCaseClasses">Test case classes</h3>
+<p>
+    Android provides several test case classes that extend {@link junit.framework.TestCase} and
+    {@link junit.framework.Assert} with Android-specific setup, teardown, and helper methods.
+</p>
+<h4 id="AndroidTestCase">AndroidTestCase</h4>
+<p>
+    A useful general test case class, especially if you are
+    just starting out with Android testing, is {@link android.test.AndroidTestCase}. It extends
+    both {@link junit.framework.TestCase} and {@link junit.framework.Assert}. It provides the
+    JUnit-standard <code>setUp()</code> and <code>tearDown()</code> methods, as well as well as
+    all of JUnit's Assert methods. In addition, it provides methods for testing permissions, and a
+    method that guards against memory leaks by clearing out certain class references.
+</p>
+<h4 id="ComponentTestCase">Component-specific test cases</h4>
+<p>
+    A key feature of the Android testing framework is its component-specific test case classes.
+    These address specific component testing needs with methods for fixture setup and
+    teardown and component lifecycle control. They also provide methods for setting up mock objects.
+    These classes are described in the component-specific testing topics:
 </p>
 <ul>
-  <li>
-    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.
-  </li>
+    <li>
+        <a href="{@docRoot}guide/topics/testing/activity_testing.html">Activity Testing</a>
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/testing/contentprovider_testing.html">
+        Content Provider Testing</a>
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/testing/service_testing.html">Service Testing</a>
+    </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:
-  </p>
+<p>
+    Android does not provide a separate test case class for BroadcastReceiver. Instead, test a
+    BroadcastReceiver by testing the component that sends it Intent objects, to verify that the
+    BroadcastReceiver responds correctly.
+</p>
+<h4 id="ApplicationTestCase">ApplicationTestCase</h4>
+<p>
+    You use the {@link android.test.ApplicationTestCase} test case class to test the setup and
+    teardown of {@link android.app.Application} objects. These objects maintain the global state of
+    information that applies to all the components in an application package. The test case can
+    be useful in verifying that the &lt;application&gt; element in the manifest file is correctly
+    set up. Note, however, that this test case does not allow you to control testing of the
+    components within your application package.
+</p>
+<h4 id="InstrumentationTestCase">InstrumentationTestCase</h4>
+<p>
+    If you want to use instrumentation methods in a test case class, you must use
+    {@link android.test.InstrumentationTestCase} or one of its subclasses. The
+    {@link android.app.Activity} test cases extend this base class with other functionality that
+    assists in Activity testing.
+</p>
+
+<h3 id="AssertionClasses">Assertion classes</h3>
+<p>
+    Because Android test case classes extend JUnit, you can use assertion methods to display the
+    results of tests. An assertion method compares an actual value returned by a test to an
+    expected value, and throws an AssertionException if the comparison test fails. Using assertions
+    is more convenient than doing logging, and provides better test performance.
+</p>
+<p>
+    Besides the JUnit {@link junit.framework.Assert} class methods, the testing API also provides
+    the {@link android.test.MoreAsserts} and {@link android.test.ViewAsserts} classes:
+</p>
+<ul>
+    <li>
+        {@link android.test.MoreAsserts} contains more powerful assertions such as
+        {@link android.test.MoreAsserts#assertContainsRegex}, which does regular expression
+        matching.
+    </li>
+    <li>
+        {@link android.test.ViewAsserts} contains useful assertions about Views. For example
+        it contains {@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="MockObjectClasses">Mock object classes</h3>
+<p>
+    To facilitate dependency injection in testing, Android provides classes that create mock system
+    objects such as {@link android.content.Context} objects,
+    {@link android.content.ContentProvider} objects, {@link android.content.ContentResolver}
+    objects, and {@link android.app.Service} objects. Some test cases also provide mock
+    {@link android.content.Intent} objects. You use these mocks both to isolate tests
+    from the rest of the system and to facilitate dependency injection for testing. These classes
+    are found in the Java packages {@link android.test} and {@link android.test.mock}.
+</p>
+<p>
+    Mock objects isolate tests from a running system by stubbing out or overriding
+    normal operations. For example, a {@link android.test.mock.MockContentResolver}
+    replaces the normal resolver framework with its own local framework, which is isolated
+    from the rest of the system. MockContentResolver also also stubs out the
+    {@link android.content.ContentResolver#notifyChange(Uri, ContentObserver, boolean)} method
+    so that observer objects outside the test environment are not accidentally triggered.
+</p>
+<p>
+    Mock object classes also facilitate dependency injection by providing a subclass of the
+    normal object that is non-functional except for overrides you define. For example, the
+    {@link android.test.mock.MockResources} object provides a subclass of
+    {@link android.content.res.Resources} in which all the methods throw Exceptions when called.
+    To use it, you override only those methods that must provide information.
+</p>
+<p>
+    These are the mock object classes available in Android:
+</p>
+<h4 id="SimpleMocks">Simple mock object classes</h4>
+<p>
+    {@link android.test.mock.MockApplication}, {@link android.test.mock.MockContext},
+    {@link android.test.mock.MockContentProvider}, {@link android.test.mock.MockCursor},
+    {@link android.test.mock.MockDialogInterface}, {@link android.test.mock.MockPackageManager}, and
+    {@link android.test.mock.MockResources} provide a simple and useful mock strategy. They are
+    stubbed-out versions of the corresponding system object class, and all of their methods throw an
+    {@link java.lang.UnsupportedOperationException} exception if called. To use them, you override
+    the methods you need in order to provide mock dependencies.
+</p>
+<p class="Note"><strong>Note:</strong>
+    {@link android.test.mock.MockContentProvider}
+    and {@link android.test.mock.MockCursor} are new as of API level 8.
+</p>
+<h4 id="ResolverMocks">Resolver mock objects</h4>
+<p>
+    {@link android.test.mock.MockContentResolver} provides isolated testing of content providers by
+    masking out the normal system resolver framework. Instead of looking in the system to find a
+    content provider given an authority string, MockContentResolver uses its own internal table. You
+    must explicitly add providers to this table using
+    {@link android.test.mock.MockContentResolver#addProvider(String,ContentProvider)}.
+</p>
+<p>
+    With this feature, you can associate a mock content provider with an authority. You can create
+    an instance of a real provider but use test data in it. You can even set the provider for an
+    authority to <code>null</code>. In effect, a MockContentResolver object isolates your test
+    from providers that contain real data. You can control the
+    function of the provider, and you can prevent your test from affecting real data.
+</p>
+<h3 id="ContextMocks">Contexts for testing</h3>
+<p>
+    Android provides two Context classes that are useful for testing:
+</p>
+<ul>
+    <li>
+        {@link android.test.IsolatedContext} provides an isolated {@link android.content.Context},
+        File, directory, and database operations that use this Context take place in a test area.
+        Though its functionality is limited, this Context has enough stub code to respond to
+        system calls.
+        <p>
+            This class allows you to test an application's data operations without affecting real
+            data that may be present on the device.
+        </p>
+    </li>
+    <li>
+        {@link android.test.RenamingDelegatingContext} provides a Context in which
+        most functions are handled by an existing {@link android.content.Context}, but
+        file and database operations are handled by a {@link android.test.IsolatedContext}.
+        The isolated part uses a test directory and creates special file and directory names.
+        You can control the naming yourself, or let the constructor determine it automatically.
+        <p>
+            This object provides a quick way to set up an isolated area for data operations,
+            while keeping normal functionality for all other Context operations.
+        </p>
+    </li>
+</ul>
+<h2 id="InstrumentationTestRunner">Running Tests</h2>
+<p>
+    Test cases are run by a test runner class that loads the test case class, set ups,
+    runs, and tears down each test. An Android test runner must also be instrumented, so that
+    the system utility for starting applications can control how the test package
+    loads test cases and the application under test. You tell the Android platform
+    which instrumented test runner to use by setting a value in the test package's manifest file.
+</p>
+<p>
+    {@link android.test.InstrumentationTestRunner} is the primary Android test runner class. It
+    extends the JUnit test runner framework and is also instrumented. It can run any of the test
+    case classes provided by Android and supports all possible types of testing.
+</p>
+<p>
+    You specify <code>InstrumentationTestRunner</code> or a subclass in your test package's
+    manifest file, in the <a href="{@docRoot}guide/topics/manifest/instrumentation-element.html">
+    instrumentation</a> element. Also, <code>InstrumentationTestRunner</code> code resides
+    in the shared library <code>android.test.runner</code>,  which is not normally linked to
+    Android code. To include it, you must specify it in a
+    <a href="{@docRoot}guide/topics/manifest/uses-library-element.html">uses-library</a> element.
+    You do not have to set up these elements yourself. Both Eclipse with ADT and the
+    <code>android</code> command-line tool construct them automatically and add them to your
+    test package's manifest file.
+</p>
+<p class="Note">
+    <strong>Note:</strong> If you use a test runner other than
+    <code>InstrumentationTestRunner</code>, you must change the &lt;instrumentation&gt;
+    element to point to the class you want to use.
+</p>
+<p>
+    To run {@link android.test.InstrumentationTestRunner}, you use internal system classes called by
+    Android tools. When you run a test in Eclipse with ADT, the classes are called automatically.
+    When you run a test from the command line, you run these classes with
+    <a href="{@docRoot}guide/developing/tools/adb.html">Android Debug Bridge (adb)</a>.
+</p>
+<p>
+    The system classes load and start the test package, kill any processes that
+    are running an instance of the application under test, and then load a new instance of the
+    application under test. They then pass control to
+    {@link android.test.InstrumentationTestRunner}, which runs
+    each test case class in the test package. You can also control which test cases and
+    methods are run using settings in Eclipse with ADT, or using flags with the command-line tools.
+</p>
+<p>
+    Neither the system classes nor {@link android.test.InstrumentationTestRunner} run
+    the application under test. Instead, the test case does this directly. It either calls methods
+    in the application under test, or it calls its own methods that trigger lifecycle events in
+    the application under test. The application is under the complete control of the test case,
+    which allows it to set up the test environment (the test fixture) before running a test. This
+    is demonstrated in the previous <a href="#ActivitySnippet">code snippet</a> that tests an
+    Activity that displays a Spinner widget.
+</p>
+<p>
+    To learn more about running tests, please read the topics
+    <a href="{@docRoot}guide/developing/testing/testing_eclipse.html"">
+    Testing in Eclipse, with ADT</a> or
+    <a href="{@docRoot}guide/developing/testing/testing_otheride.html">
+    Testing in Other IDes</a>.
+</p>
+<h2 id="TestResults">Seeing Test Results</h2>
+<p>
+    The Android testing framework returns test results back to the tool that started the test.
+    If you run a test in Eclipse with ADT, the results are displayed in a new JUnit view pane. If
+    you run a test from the command line, the results are displayed in <code>STDOUT</code>. In
+    both cases, you see a test summary that displays the name of each test case and method that
+    was run. You also see all the assertion failures that occurred. These include pointers to the
+    line in the test code where the failure occurred. Assertion failures also list the expected
+    value and actual value.
+</p>
+<p>
+    The test results have a format that is specific to the IDE that you are using. The test
+    results format for Eclipse with ADT is described in
+    <a href="{@docRoot}guide/developing/testing/testing_eclipse.html#RunTestEclipse">
+    Testing in Eclipse, with ADT</a>. The test results format for tests run from the
+    command line is described in
+    <a href="{@docRoot}guide/developing/testing/testing_otheride.html#RunTestsCommand">
+    Testing in Other IDEs</a>.
+</p>
+<h2 id="Monkeys">Monkey and MonkeyRunner</h2>
+<p>
+    The SDK provides two tools for functional-level application testing:
+</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.
-      </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.
-      </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.
-      </li>
+        <li>
+            <a href="{@docRoot}guide/developing/tools/monkey.html">Monkey</a> is a command-line
+            tool that sends pseudo-random streams of keystrokes, touches, and gestures to a
+            device. You run it with the <a href="{@docRoot}guide/developing/tools/adb.html">
+            Android Debug Bridge</a> (adb) tool. You use it to stress-test your application and
+            report back errors that are encountered. You can repeat a stream of events by
+            running the tool each time with the same random number seed.
+        </li>
+        <li>
+            <a href="{@docRoot}guide/topics/testing/monkeyrunner.html">MonkeyRunner</a> is a
+            Jython API that you use in test programs written in Python. The API includes functions
+            for connecting to a device, installing and uninstalling packages, taking screenshots,
+            comparing two images, and running a test package against an application. Using the API
+            with Python, you can write a wide range of large, powerful, and complex tests.
+        </li>
     </ul>
-<h3 id="InstrumentationTestRunner">Instrumentation Test Runner</h3>
+<h2 id="PackageNames">Working With Package names</h2>
 <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.
+    In the test environment, you work with both Android application package names and
+    Java package identifiers. Both use the same naming format, but they represent substantially
+    different entities. You need to know the difference to set up your tests correctly.
 </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.
+    An Android package name is a unique system name for a <code>.apk</code> file, set by the
+    &quot;android:package&quot; attribute of the &lt;manifest&gt; element in the package's
+    manifest. The Android package name of your test package must be different from the
+    Android package name of the application under test. By default, Android tools create the
+    test package name by appending ".test" to the package name of the application under test.
 </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.
+    The test package also uses an Android package name to target the application package it
+    tests. This is set in the &quot;android:targetPackage&quot; attribute of the
+    &lt;instrumentation&gt; element in the test package's manifest.
 </p>
 <p>
-  If you prefer working from the command line, you can use Ant and the <code>android</code>
-  tool to help you set up your test projects. To run tests with instrumentation, you can access the
-  Activity Manager through the <a href="{@docRoot}guide/developing/tools/adb.html">Android Debug
-  Bridge</a> (<code>adb</code>) tool and the output is directed to <code>STDOUT</code>.
-</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.
+    A Java package identifier applies to a source file. This package name reflects the directory
+    path of the source file. It also affects the visibility of classes and members to each other.
 </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.
+    Android tools that create test projects set up an Android test package name for you.
+    From your input, the tools set up the test package name and the target package name for the
+    application under test. For these tools to work, the application project must already exist.
 </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>.
+    By default, these tools set the Java package identifier for the test class to be the same
+    as the Android package identifier. You may want to change this if you want to expose
+    members in the application under test by giving them package visibility. If you do this,
+    change only the Java package identifier, not the Android package names, and change only the
+    test case source files. Do not change the Java package name of the generated
+    <code>R.java</code> class in your test package, because it will then conflict with the
+    <code>R.java</code> class in the application under test. Do not change the Android package name
+    of your test package to be the same as the application it tests, because then their names
+    will no longer be unique in the system.
 </p>
-<h3 id="TestProjects">Working with test projects</h3>
+<h2 id="WhatToTest">What to Test</h2>
 <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>.
-</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>.
+    The topic <a href="{@docRoot}guide/topics/testing/what_to_test.html">What To Test</a>
+    describes the key functionality you should test in an Android application, and the key
+    situations that might affect that functionality.
 </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}.
-<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
-    mocked Contexts. In general, you can't mock dependencies for the activity under test.
+    Most unit testing is specific to the Android component you are testing.
+    The topics <a href="{@docRoot}guide/topics/testing/activity_testing.html">Activity Testing</a>,
+    <a href="{@docRoot}guide/topics/testing/contentprovider_testing.html">
+    Content Provider Testing</a>, and <a href="{@docRoot}guide/topics/testing/service_testing.html">
+    Service Testing</a> each have a section entitled "What To Test" that lists possible testing
+    areas.
 </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.
+    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="NextSteps">Next Steps</h2>
+<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>
-    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>.
+    If you want a step-by-step introduction to Android testing, try one of the
+    testing tutorials or sample test packages:
 </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.
-</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.
-</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.
-</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.
-</p>
-<h2 id="TestAreas">What to Test</h2>
-<p>
-  In addition to the functional areas you would normally test, here are some areas
-  of Android application testing that you should consider:
-</p>
-  <ul>
+<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
-      application state.
+        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>
-      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}.
+        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>
-        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>.
+        The sample test package
+        <a href="{@docRoot}resources/samples/NotePadTest">Note Pad Test</a> is an example of
+        testing a {@link android.content.ContentProvider}. It contains a set of unit tests for the
+        Note Pad sample application's {@link android.content.ContentProvider}.
     </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.
-</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.
-</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.
-</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>.
-</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.
-</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>
-<pre>
-  private MyActivity mActivity; // MyActivity is the class name of the app under test
-  private Spinner mSpinner;
-
-  ...
-
-  protected void setUp() throws Exception {
-      super.setUp();
-      mInstrumentation = getInstrumentation();
-
-      mActivity = getActivity(); // get a references to the app under test
-
-      /*
-       * Get a reference to the main widget of the app under test, a Spinner
-       */
-      mSpinner = (Spinner) mActivity.findViewById(com.android.demo.myactivity.R.id.Spinner01);
-
-  ...
-
-  public void aTest() {
-      /*
-       * request focus for the Spinner, so that the test can send key events to it
-       * This request must be run on the UI thread. To do this, use the runOnUiThread method
-       * and pass it a Runnable that contains a call to requestFocus on the Spinner.
-       */
-      mActivity.runOnUiThread(new Runnable() {
-          public void run() {
-              mSpinner.requestFocus();
-          }
-      });
-
-      mInstrumentation.waitForIdleSync();
-
-      this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-</pre>
-
-<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.
-</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>.
-</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.
-</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.
-</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:
-</p>
-<pre>
-  mKeyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
-  mLock = mKeyGuardManager.newKeyguardLock("<em>activity_classname</em>");
-  mLock.disableKeyguard();
-</pre>
-<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:
-</p>
-<dl>
-    <dt><code>WrongThreadException</code></dt>
-    <dd>
-      <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.
-      </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.
-      <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>
-        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:
-      <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>.
-      <p><strong>Suggested Resolution:</strong></p>
-        Remove the <code>@UiThreadTest</code> annotation, remove the <code>runOnUiThread()</code> call, or re-factor your tests.
-    </dd>
-</dl>
+    <li>
+        The sample test package <a href="{@docRoot}resources/samples/AlarmServiceTest"}>
+        Alarm Service Test</a> is an example of testing a {@link android.app.Service}. It contains
+        a set of unit tests for the Alarm Service sample application's {@link android.app.Service}.
+    </li>
+</ul>
 
diff --git a/docs/html/guide/topics/testing/what_to_test.jd b/docs/html/guide/topics/testing/what_to_test.jd
new file mode 100644
index 0000000..e13538a
--- /dev/null
+++ b/docs/html/guide/topics/testing/what_to_test.jd
@@ -0,0 +1,84 @@
+page.title=What To Test
+@jd:body
+<p>
+    As you develop Android applications, knowing what to test is as important as knowing how to
+    test. This document lists some most common Android-related situations that you should consider
+    when you test, even at the unit test level. This is not an exhaustive list, and you consult the
+    documentation for the features that you use for more ideas. The
+    <a href="http://groups.google.com/group/android-developers">android-developers</a> Google Groups
+    site is another resource for information about testing.
+</p>
+<h2 id="Tests">Ideas for Testing</h2>
+<p>
+    The following sections are organized by behaviors or situations that you should test. Each
+    section contains a scenario that further illustrates the situation and the test or tests you
+    should do.
+</p>
+<h4>Change in orientation</h4>
+<p>
+    For devices that support multiple orientations, Android detects a change in orientation when
+    the user turns the device so that the display is "landscape" (long edge is horizontal) instead
+    of "portrait" (long edge is vertical).
+</p>
+<p>
+    When Android detects a change in orientation, its default behavior is to destroy and then
+    re-start the foreground Activity. You should consider testing the following:
+</p>
+<ul>
+    <li>
+        Is the screen re-drawn correctly? Any custom UI code you have should handle changes in the
+        orientation.
+    </li>
+    <li>
+        Does the application maintain its state? The Activity should not lose anything that the
+        user has already entered into the UI. The application should not "forget" its place in the
+        current transaction.
+    </li>
+</ul>
+<h4>Change in configuration</h4>
+<p>
+    A situation that is more general than a change in orientation is a change in the device's
+    configuration, such as a change in the availability of a keyboard or a change in system
+    language.
+</p>
+<p>
+    A change in configuration also triggers the default behavior of destroying and then restarting
+    the foreground Activity. Besides testing that the application maintains the UI and its
+    transaction state, you should also test that the application updates itself to respond
+    correctly to the new configuration.
+</p>
+<h4>Battery life</h4>
+<p>
+    Mobile devices primarily run on battery power. A device has finite "battery budget", and when it
+    is gone, the device is useless until it is recharged. You need to write your application to
+    minimize battery usage, you need to test its battery performance, and you need to test the
+    methods that manage battery usage.
+</p>
+<p>
+    Techniques for minimizing battery usage were presented at the 2010 Google I/O conference in the
+    presentation
+    <a href="http://code.google.com/events/io/2009/sessions/CodingLifeBatteryLife.html">
+    Coding for Life -- Battery Life, That Is</a>. This presentation describes the impact on battery
+    life of various operations, and the ways you can design your application to minimize these
+    impacts. When you code your application to reduce battery usage, you also write the
+    appropriate unit tests.
+</p>
+<h4>Dependence on external resources</h4>
+<p>
+    If your application depends on network access, SMS, Bluetooth, or GPS, then you should
+    test what happens when the resource or resources are not available.
+</p>
+<p>
+    For example, if your application uses the network,it can notify the user if access is
+    unavailable, or disable network-related features, or do both. For GPS, it can switch to
+    IP-based location awareness. It can also wait for WiFi access before doing large data transfers,
+    since WiFi transfers maximize battery usage compared to transfers over 3G or EDGE.
+</p>
+<p>
+    You can use the emulator to test network access and bandwidth. To learn more, please see
+    <a href="{@docRoot}guide/developing/tools/emulator.html#netspeed">Network Speed Emulation</a>.
+    To test GPS, you can use the emulator console and {@link android.location.LocationManager}. To
+    learn more about the emulator console, please see
+    <a href="{@docRoot}/guide/developing/tools/emulator.html#console">
+    Using the Emulator Console</a>.
+</p>
diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd
index 1a997f9..879eb8b 100644
--- a/docs/html/guide/topics/ui/dialogs.jd
+++ b/docs/html/guide/topics/ui/dialogs.jd
@@ -473,18 +473,25 @@
             progressDialog = new ProgressDialog(NotificationTest.this);
             progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
             progressDialog.setMessage("Loading...");
-            progressThread = new ProgressThread(handler);
-            progressThread.start();
             return progressDialog;
         default:
             return null;
         }
     }
 
+    &#64;Override
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        switch(id) {
+        case PROGRESS_DIALOG:
+            progressDialog.setProgress(0);
+            progressThread = new ProgressThread(handler);
+            progressThread.start();
+    }
+
     // Define the Handler that receives messages from the thread and update the progress
     final Handler handler = new Handler() {
         public void handleMessage(Message msg) {
-            int total = msg.getData().getInt("total");
+            int total = msg.arg1;
             progressDialog.setProgress(total);
             if (total >= 100){
                 dismissDialog(PROGRESS_DIALOG);
@@ -515,9 +522,7 @@
                     Log.e("ERROR", "Thread Interrupted");
                 }
                 Message msg = mHandler.obtainMessage();
-                Bundle b = new Bundle();
-                b.putInt("total", total);
-                msg.setData(b);
+                msg.arg1 = total;
                 mHandler.sendMessage(msg);
                 total++;
             }
diff --git a/docs/html/guide/tutorials/notepad/notepad-ex2.jd b/docs/html/guide/tutorials/notepad/notepad-ex2.jd
index a945a62..854731f 100644
--- a/docs/html/guide/tutorials/notepad/notepad-ex2.jd
+++ b/docs/html/guide/tutorials/notepad/notepad-ex2.jd
@@ -87,8 +87,8 @@
     menu callback used for the options menu. Here, we add just one line, which will add a menu item
     to delete a note. Call <code>menu.add()</code> like so:
       <pre>
-public boolean onCreateContextMenu(Menu menu, View v
-        ContextMenuInfo menuInfo) {
+public void onCreateContextMenu(Menu menu, View v,
+        ContextMenu.ContextMenuInfo menuInfo) {
     super.onCreateContextMenu(menu, v, menuInfo);
     menu.add(0, DELETE_ID, 0, R.string.menu_delete);
 }</pre>
diff --git a/docs/html/images/fundamentals/diagram_backstack.png b/docs/html/images/fundamentals/diagram_backstack.png
new file mode 100644
index 0000000..2c6e33f
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_backstack.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png b/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png
new file mode 100644
index 0000000..d6a21d7
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_multiple_instances.png b/docs/html/images/fundamentals/diagram_multiple_instances.png
new file mode 100644
index 0000000..380e7788
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_multiple_instances.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_multitasking.png b/docs/html/images/fundamentals/diagram_multitasking.png
new file mode 100644
index 0000000..b8c7b45
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_multitasking.png
Binary files differ
diff --git a/docs/html/images/fundamentals/restore_instance.png b/docs/html/images/fundamentals/restore_instance.png
new file mode 100644
index 0000000..fa428a7
--- /dev/null
+++ b/docs/html/images/fundamentals/restore_instance.png
Binary files differ
diff --git a/docs/html/images/testing/android_test_framework.png b/docs/html/images/testing/android_test_framework.png
index 6f80530..459975c 100755
--- a/docs/html/images/testing/android_test_framework.png
+++ b/docs/html/images/testing/android_test_framework.png
Binary files differ
diff --git a/docs/html/images/testing/eclipse_test_results.png b/docs/html/images/testing/eclipse_test_results.png
new file mode 100644
index 0000000..105e149
--- /dev/null
+++ b/docs/html/images/testing/eclipse_test_results.png
Binary files differ
diff --git a/docs/html/images/testing/eclipse_test_run_failure.png b/docs/html/images/testing/eclipse_test_run_failure.png
new file mode 100644
index 0000000..8111127
--- /dev/null
+++ b/docs/html/images/testing/eclipse_test_run_failure.png
Binary files differ
diff --git a/docs/html/images/testing/test_framework.png b/docs/html/images/testing/test_framework.png
new file mode 100644
index 0000000..fbc5fc2
--- /dev/null
+++ b/docs/html/images/testing/test_framework.png
Binary files differ
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/samples/images/sample_notepadtest_junit.png b/docs/html/resources/samples/images/sample_notepadtest_junit.png
new file mode 100644
index 0000000..4cda54e
--- /dev/null
+++ b/docs/html/resources/samples/images/sample_notepadtest_junit.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/docs/html/resources/tutorials/notepad/notepad-ex2.jd b/docs/html/resources/tutorials/notepad/notepad-ex2.jd
index 289b5fe..499b796 100644
--- a/docs/html/resources/tutorials/notepad/notepad-ex2.jd
+++ b/docs/html/resources/tutorials/notepad/notepad-ex2.jd
@@ -87,8 +87,8 @@
     menu callback used for the options menu. Here, we add just one line, which will add a menu item
     to delete a note. Call <code>menu.add()</code> like so:
       <pre>
-public boolean onCreateContextMenu(Menu menu, View v
-        ContextMenuInfo menuInfo) {
+public void onCreateContextMenu(Menu menu, View v,
+        ContextMenu.ContextMenuInfo menuInfo) {
     super.onCreateContextMenu(menu, v, menuInfo);
     menu.add(0, DELETE_ID, 0, R.string.menu_delete);
 }</pre>
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index dd83c3b..d283dea3 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -19,12 +19,16 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.DisplayMetrics;
+import android.util.Finalizers;
 
 import java.io.OutputStream;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 public final class Bitmap implements Parcelable {
     /**
@@ -35,9 +39,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
@@ -51,7 +59,7 @@
     private static volatile Matrix sScaleMatrix;
 
     private static volatile int sDefaultDensity = -1;
-    
+
     /**
      * For backwards compatibility, allows the app layer to change the default
      * density when running old apps.
@@ -77,8 +85,7 @@
 
         This can be called from JNI code.
     */
-    private Bitmap(int nativeBitmap, boolean isMutable, byte[] ninePatchChunk,
-            int density) {
+    private Bitmap(int nativeBitmap, boolean isMutable, byte[] ninePatchChunk, int density) {
         if (nativeBitmap == 0) {
             throw new RuntimeException("internal error: native bitmap is 0");
         }
@@ -90,6 +97,13 @@
         if (density >= 0) {
             mDensity = density;
         }
+
+        // If the finalizers queue is null, we are running in zygote and the
+        // bitmap will never be reclaimed, so we don't need to run our native
+        // destructor
+        if (Finalizers.getQueue() != null) {
+            new BitmapFinalizer(this);
+        }
     }
 
     /**
@@ -172,6 +186,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.
      */
@@ -428,28 +455,30 @@
         Rect srcR = new Rect(x, y, x + width, y + height);
         RectF dstR = new RectF(0, 0, width, height);
 
+        final Config newConfig = source.getConfig() == Config.ARGB_8888 ?
+                Config.ARGB_8888 : Config.RGB_565;
+
         if (m == null || m.isIdentity()) {
-            bitmap = createBitmap(neww, newh,
-                    source.hasAlpha() ? Config.ARGB_8888 : Config.RGB_565);
+            bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
             paint = null;   // not needed
         } else {
-            /*  the dst should have alpha if the src does, or if our matrix
-                doesn't preserve rectness
-            */
-            boolean hasAlpha = source.hasAlpha() || !m.rectStaysRect();
+            final boolean transformed = !m.rectStaysRect();
+
             RectF deviceR = new RectF();
             m.mapRect(deviceR, dstR);
+
             neww = Math.round(deviceR.width());
             newh = Math.round(deviceR.height());
-            bitmap = createBitmap(neww, newh, hasAlpha ? Config.ARGB_8888 : Config.RGB_565);
-            if (hasAlpha) {
-                bitmap.eraseColor(0);
-            }
+
+            bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
+                    transformed || source.hasAlpha());
+
             canvas.translate(-deviceR.left, -deviceR.top);
             canvas.concat(m);
+
             paint = new Paint();
             paint.setFilterBitmap(filter);
-            if (!m.rectStaysRect()) {
+            if (transformed) {
                 paint.setAntiAlias(true);
             }
         }
@@ -474,8 +503,30 @@
      * @throws IllegalArgumentException if the width or height are <= 0
      */
     public static Bitmap createBitmap(int width, int height, Config config) {
+        return createBitmap(width, height, config, true);
+    }
+
+    /**
+     * Returns a mutable bitmap with the specified width and height.  Its
+     * initial density is as per {@link #getDensity}.
+     *
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create.
+     * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the
+     *                 bitmap as opaque. Doing so will clear the bitmap in black
+     *                 instead of transparent.  
+     * 
+     * @throws IllegalArgumentException if the width or height are <= 0
+     */
+    private static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) {
         Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
-        bm.eraseColor(0);    // start with black/transparent pixels
+        if (config == Config.ARGB_8888 && !hasAlpha) {
+            bm.eraseColor(0xff000000);
+            nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha);
+        } else {
+            bm.eraseColor(0);
+        }
         return bm;
     }
 
@@ -1008,12 +1059,22 @@
         nativePrepareToDraw(mNativeBitmap);
     }
 
-    @Override
-    protected void finalize() throws Throwable {
-        try {
+    private static class BitmapFinalizer extends Finalizers.ReclaimableReference<Bitmap> {
+        private static final Set<BitmapFinalizer> sFinalizers = Collections.synchronizedSet(
+                new HashSet<BitmapFinalizer>());
+
+        private int mNativeBitmap;
+
+        BitmapFinalizer(Bitmap b) {
+            super(b, Finalizers.getQueue());
+            mNativeBitmap = b.mNativeBitmap;
+            sFinalizers.add(this);
+        }
+
+        @Override
+        public void reclaim() {
             nativeDestructor(mNativeBitmap);
-        } finally {
-            super.finalize();
+            sFinalizers.remove(this);
         }
     }
 
@@ -1050,6 +1111,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
@@ -1065,7 +1127,7 @@
     private static native void nativePrepareToDraw(int nativeBitmap);
     private static native void nativeSetHasAlpha(int nBitmap, boolean hasAlpha);
     private static native boolean nativeSameAs(int nb0, int nb1);
-
+    
     /* package */ final int ni() {
         return mNativeBitmap;
     }
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 7b49bc5..ea5ed6b 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -18,7 +18,6 @@
 
 import android.content.res.AssetManager;
 import android.content.res.Resources;
-import android.os.MemoryFile;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 
@@ -84,7 +83,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 +506,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);
@@ -530,18 +527,6 @@
      * @return the decoded bitmap, or null
      */
     public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) {
-        try {
-            if (MemoryFile.isMemoryFile(fd)) {
-                int mappedlength = MemoryFile.getSize(fd);
-                MemoryFile file = new MemoryFile(fd, mappedlength, "r");
-                InputStream is = file.getInputStream();
-                Bitmap bm = decodeStream(is, outPadding, opts);
-                return finishDecode(bm, outPadding, opts);
-            }
-        } catch (IOException ex) {
-            // invalid filedescriptor, no need to call nativeDecodeFileDescriptor()
-            return null;
-        }
         Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
         return finishDecode(bm, outPadding, opts);
     }
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
new file mode 100644
index 0000000..454eb4a
--- /dev/null
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.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 android.graphics;
+
+import android.content.res.AssetManager;
+
+import java.io.BufferedInputStream;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * BitmapRegionDecoder can be used to decode a rectangle region from an image.
+ * BitmapRegionDecoder is particularly useful when an original image is large and
+ * you only need parts of the image.
+ *
+ * <p>To create a BitmapRegionDecoder, call newInstance(...).
+ * Given a BitmapRegionDecoder, users can call decodeRegion() repeatedly
+ * to get a decoded Bitmap of the specified region.
+ *
+ */
+public final class BitmapRegionDecoder {
+    private int mNativeBitmapRegionDecoder;
+    private boolean mRecycled;
+
+    /**
+     * Create a BitmapRegionDecoder from the specified byte array.
+     * Currently only the Jpeg format is supported.
+     *
+     * @param data byte array of compressed image data.
+     * @param offset offset into data for where the decoder should begin
+     *               parsing.
+     * @param length the number of bytes, beginning at offset, to parse
+     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+     *                    shallow reference to the input. If this is false,
+     *                    then the BitmapRegionDecoder will explicitly make a copy of the
+     *                    input data, and keep that. Even if sharing is allowed,
+     *                    the implementation may still decide to make a deep
+     *                    copy of the input data. If an image is progressively encoded,
+     *                    allowing sharing may degrade the decoding speed.
+     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+     * @throws IOException if the image format is not supported or can not be decoded.
+     */
+    public static BitmapRegionDecoder newInstance(byte[] data,
+            int offset, int length, boolean isShareable) throws IOException {
+        if ((offset | length) < 0 || data.length < offset + length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        return nativeNewInstance(data, offset, length, isShareable);
+    }
+
+    /**
+     * Create a BitmapRegionDecoder from the file descriptor.
+     * The position within the descriptor will not be changed when
+     * this returns, so the descriptor can be used again as is.
+     * Currently only the Jpeg format is supported.
+     *
+     * @param fd The file descriptor containing the data to decode
+     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+     *                    shallow reference to the input. If this is false,
+     *                    then the BitmapRegionDecoder will explicitly make a copy of the
+     *                    input data, and keep that. Even if sharing is allowed,
+     *                    the implementation may still decide to make a deep
+     *                    copy of the input data. If an image is progressively encoded,
+     *                    allowing sharing may degrade the decoding speed.
+     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+     * @throws IOException if the image format is not supported or can not be decoded.
+     */
+    public static BitmapRegionDecoder newInstance(
+            FileDescriptor fd, boolean isShareable) throws IOException {
+        return nativeNewInstance(fd, isShareable);
+    }
+
+    /**
+     * Create a BitmapRegionDecoder from an input stream.
+     * The stream's position will be where ever it was after the encoded data
+     * was read.
+     * Currently only the Jpeg format is supported.
+     *
+     * @param is The input stream that holds the raw data to be decoded into a
+     *           BitmapRegionDecoder.
+     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+     *                    shallow reference to the input. If this is false,
+     *                    then the BitmapRegionDecoder will explicitly make a copy of the
+     *                    input data, and keep that. Even if sharing is allowed,
+     *                    the implementation may still decide to make a deep
+     *                    copy of the input data. If an image is progressively encoded,
+     *                    allowing sharing may degrade the decoding speed.
+     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+     * @throws IOException if the image format is not supported or can not be decoded.
+     */
+    public static BitmapRegionDecoder newInstance(InputStream is,
+            boolean isShareable) throws IOException {
+        // we need mark/reset to work properly in JNI
+
+        if (!is.markSupported()) {
+            is = new BufferedInputStream(is, 16 * 1024);
+        }
+
+        if (is instanceof AssetManager.AssetInputStream) {
+            return nativeNewInstance(
+                    ((AssetManager.AssetInputStream) is).getAssetInt(),
+                    isShareable);
+        } else {
+            // pass some temp storage down to the native code. 1024 is made up,
+            // but should be large enough to avoid too many small calls back
+            // into is.read(...).
+            byte [] tempStorage = new byte[16 * 1024];
+            return nativeNewInstance(is, tempStorage, isShareable);
+        }
+    }
+
+    /**
+     * Create a BitmapRegionDecoder from a file path.
+     * Currently only the Jpeg format is supported.
+     *
+     * @param pathName complete path name for the file to be decoded.
+     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+     *                    shallow reference to the input. If this is false,
+     *                    then the BitmapRegionDecoder will explicitly make a copy of the
+     *                    input data, and keep that. Even if sharing is allowed,
+     *                    the implementation may still decide to make a deep
+     *                    copy of the input data. If an image is progressively encoded,
+     *                    allowing sharing may degrade the decoding speed.
+     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+     * @throws IOException if the image format is not supported or can not be decoded.
+     */
+    public static BitmapRegionDecoder newInstance(String pathName,
+            boolean isShareable) throws IOException {
+        BitmapRegionDecoder decoder = null;
+        InputStream stream = null;
+
+        try {
+            stream = new FileInputStream(pathName);
+            decoder = newInstance(stream, isShareable);
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                    // do nothing here
+                }
+            }
+        }
+        return decoder;
+    }
+
+    /*  Private constructor that must receive an already allocated native
+        region decoder int (pointer).
+
+        This can be called from JNI code.
+    */
+    private BitmapRegionDecoder(int decoder) {
+        mNativeBitmapRegionDecoder = decoder;
+        mRecycled = false;
+    }
+
+    /**
+     * Decodes a rectangle region in the image specified by rect.
+     *
+     * @param rect The rectangle that specified the region to be decode.
+     * @param options null-ok; Options that control downsampling.
+     *             inPurgeable is not supported.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded.
+     */
+    public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
+        checkRecycled("decodeRegion called on recycled region decoder");
+        if (rect.left < 0 || rect.top < 0 || rect.right > getWidth()
+                || rect.bottom > getHeight())
+            throw new IllegalArgumentException("rectangle is not inside the image");
+        return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
+                rect.right - rect.left, rect.bottom - rect.top, options);
+    }
+
+    /** Returns the original image's width */
+    public int getWidth() {
+        checkRecycled("getWidth called on recycled region decoder");
+        return nativeGetWidth(mNativeBitmapRegionDecoder);
+    }
+
+    /** Returns the original image's height */
+    public int getHeight() {
+        checkRecycled("getHeight called on recycled region decoder");
+        return nativeGetHeight(mNativeBitmapRegionDecoder);
+    }
+
+    /**
+     * Frees up the memory associated with this region decoder, and mark the
+     * region decoder as "dead", meaning it will throw an exception if decodeRegion(),
+     * getWidth() or getHeight() is called.
+     *
+     * <p>This operation cannot be reversed, so it should only be called if you are
+     * sure there are no further uses for the region decoder. This is an advanced call,
+     * and normally need not be called, since the normal GC process will free up this
+     * memory when there are no more references to this region decoder.
+     */
+    public void recycle() {
+        if (!mRecycled) {
+            nativeClean(mNativeBitmapRegionDecoder);
+            mRecycled = true;
+        }
+    }
+
+    /**
+     * Returns true if this region decoder has been recycled.
+     * If so, then it is an error to try use its method.
+     *
+     * @return true if the region decoder has been recycled
+     */
+    public final boolean isRecycled() {
+        return mRecycled;
+    }
+
+    /**
+     * Called by methods that want to throw an exception if the region decoder
+     * has already been recycled.
+     */
+    private void checkRecycled(String errorMessage) {
+        if (mRecycled) {
+            throw new IllegalStateException(errorMessage);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            recycle();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private static native Bitmap nativeDecodeRegion(int lbm,
+            int start_x, int start_y, int width, int height,
+            BitmapFactory.Options options);
+    private static native int nativeGetWidth(int lbm);
+    private static native int nativeGetHeight(int lbm);
+    private static native void nativeClean(int lbm);
+
+    private static native BitmapRegionDecoder nativeNewInstance(
+            byte[] data, int offset, int length, boolean isShareable);
+    private static native BitmapRegionDecoder nativeNewInstance(
+            FileDescriptor fd, boolean isShareable);
+    private static native BitmapRegionDecoder nativeNewInstance(
+            InputStream is, byte[] storage, boolean isShareable);
+    private static native BitmapRegionDecoder nativeNewInstance(
+            int asset, boolean isShareable);
+}
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 612b0ab..4ba679b 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -16,10 +16,16 @@
 
 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;
+    /**
+     * Prevent garbage collection.
+     */
+    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+    private final Bitmap mBitmap;
 
     /**
      * Call this to create a new shader that will draw with a bitmap.
@@ -30,12 +36,13 @@
      */
     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 a587d0d1..952f2b5 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;
 
@@ -43,22 +42,39 @@
         for both to be null.
     */
     private Bitmap  mBitmap;    // if not null, mGL must be null
-    private GL      mGL;        // if not null, mBitmap must be null
     
     // optional field set by the caller
-    private DrawFilter  mDrawFilter;
+    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
@@ -89,47 +105,37 @@
         mDensity = bitmap.mDensity;
     }
     
-    /*package*/ Canvas(int nativeCanvas) {
+    Canvas(int nativeCanvas) {
         if (nativeCanvas == 0) {
             throw new IllegalStateException();
         }
         mNativeCanvas = nativeCanvas;
         mDensity = Bitmap.getDefaultDensity();
     }
-    
+
     /**
-     * Construct a canvas with the specified gl context. All drawing through
-     * this canvas will be redirected to OpenGL. Note: some features may not
-     * be supported in this mode (e.g. some GL implementations may not support
-     * antialiasing or certain effects like ColorMatrix or certain Xfermodes).
-     * However, no exception will be thrown in those cases.
+     * Returns null.
      * 
-     * <p>The initial target density of the canvas is the same as the initial
-     * density of bitmaps as per {@link Bitmap#getDensity() Bitmap.getDensity()}.
+     * @deprecated This method is not supported and should not be invoked.
      */
-    public Canvas(GL gl) {
-        mNativeCanvas = initGL();
-        mGL = gl;
-        mDensity = Bitmap.getDefaultDensity();
+    @Deprecated
+    protected GL getGL() {
+        return null;
     }
-    
+
     /**
-     * Return the GL object associated with this canvas, or null if it is not
-     * backed by GL.
+     * 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 GL getGL() {
-        return mGL;
+    public boolean isHardwareAccelerated() {
+        return false;
     }
-    
-    /**
-     * 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.
-     */
-    public static void freeGlCaches() {
-        freeCaches();
-    }
-        
+
     /**
      * Specify a bitmap for the canvas to draw into.  As a side-effect, also
      * updates the canvas's target density to match that of the bitmap.
@@ -143,7 +149,7 @@
         if (!bitmap.isMutable()) {
             throw new IllegalStateException();
         }
-        if (mGL != null) {
+        if (isHardwareAccelerated()) {
             throw new RuntimeException("Can't set a bitmap device on a GL canvas");
         }
         throwIfRecycled(bitmap);
@@ -157,13 +163,12 @@
      * Set the viewport dimensions if this canvas is GL based. If it is not,
      * this method is ignored and no exception is thrown.
      *
-     *  @param width    The width of the viewport
-     *  @param height   The height of the viewport
+     * @param width The width of the viewport
+     * @param height The height of the viewport
+     * 
+     * @hide
      */
     public void setViewport(int width, int height) {
-        if (mGL != null) {
-            nativeSetViewport(mNativeCanvas, width, height);
-        }
     }
 
     /**
@@ -377,8 +382,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 +626,11 @@
         EdgeType(int nativeInt) {
             this.nativeInt = nativeInt;
         }
-        final int nativeInt;
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
     }
 
     /**
@@ -958,6 +967,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 +1270,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 +1283,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 +1304,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 +1326,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 +1487,7 @@
         }
         native_drawTextOnPath(mNativeCanvas, text, index, count,
                               path.ni(), hOffset, vOffset,
-                              paint.mNativePaint);
+                              paint.mBidiFlags, paint.mNativePaint);
     }
 
     /**
@@ -1388,7 +1507,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);
         }
     }
 
@@ -1431,28 +1551,74 @@
         drawPicture(picture);
         restore();
     }
-    
+
+    /**
+     * <p>Acquires the Canvas context. After invoking this method, the Canvas
+     * context  can be modified by the caller. For instance, if you acquire
+     * the context of an OpenGL Canvas you can reset the GL viewport, scissor,
+     * etc.</p>
+     * 
+     * <p>A call to {@link #acquireContext()} should aways be followed by
+     * a call to {@link #releaseContext()}, preferrably using a try block:</p>
+     * 
+     * <pre>
+     * try {
+     *     if (canvas.acquireContext()) {
+     *         // Use the canvas and/or its context
+     *     }
+     * } finally {
+     *     canvas.releaseContext();
+     * }
+     * </pre>
+     * 
+     * <p>Acquiring the context can be an expensive operation and should not
+     * be done unless absolutely necessary.</p>
+     * 
+     * <p>Applications should never invoke this method directly.</p>
+     * 
+     * @return True if the context could be acquired successfully, false
+     *         otherwise (if the context is already acquired for instance.)
+     * 
+     * @see #releaseContext() 
+     * 
+     * @hide
+     */
+    public boolean acquireContext() {
+        return false;
+    }
+
+    /**
+     * <p>Release the context acquired with {@link #acquireContext()}.</p>
+     * 
+     * @see #acquireContext() 
+     * 
+     * @hide
+     */
+    public void releaseContext() {
+    }
+
+    @Override
     protected void finalize() throws Throwable {
-        super.finalize();
-        // If the constructor threw an exception before setting mNativeCanvas, the native finalizer
-        // must not be invoked.
-        if (mNativeCanvas != 0) {
-            finalizer(mNativeCanvas);
+        try {
+            super.finalize();
+        } finally {
+            // If the constructor threw an exception before setting mNativeCanvas,
+            // the native finalizer must not be invoked.
+            if (mNativeCanvas != 0) {
+                finalizer(mNativeCanvas);
+            }
         }
     }
 
     /**
-     * Free up as much memory as possible from private caches (e.g. fonts,
-     * images)
+     * Free up as much memory as possible from private caches (e.g. fonts, images)
      *
-     * @hide - for now
+     * @hide
      */
     public static native void freeCaches();
 
     private static native int initRaster(int nativeBitmapOrZero);
-    private static native int initGL();
     private static native void native_setBitmap(int nativeCanvas, int bitmap);
-    private static native void nativeSetViewport(int nCanvas, int w, int h);
     private static native int native_saveLayer(int nativeCanvas, RectF bounds,
                                                int paint, int layerFlags);
     private static native int native_saveLayer(int nativeCanvas, float l,
@@ -1555,10 +1721,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 +1745,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..8d5c913 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -20,6 +20,14 @@
     an {@link android.graphics.Xfermode} subclass.
 */
 public class ComposeShader extends Shader {
+    /**
+     * Hold onto the shaders to avoid GC.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    private final Shader mShaderA;
+    @SuppressWarnings({"UnusedDeclaration"})
+    private final Shader mShaderB;
+
     /** Create a new compose shader, given shaders A, B, and a combining mode.
         When the mode is applied, it will be given the result from shader A as its
         "dst", and the result of from shader B as its "src".
@@ -29,8 +37,18 @@
                         is null, then SRC_OVER is assumed.
     */
     public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) {
+        mShaderA = shaderA;
+        mShaderB = shaderB;
         native_instance = nativeCreate1(shaderA.native_instance, shaderB.native_instance,
-                                        (mode != null) ? mode.native_instance : 0);
+                (mode != null) ? mode.native_instance : 0);
+        if (mode instanceof PorterDuffXfermode) {
+            PorterDuff.Mode pdMode = ((PorterDuffXfermode) mode).mode;
+            native_shader = nativePostCreate2(native_instance, shaderA.native_shader,
+                    shaderB.native_shader, pdMode != null ? pdMode.nativeInt : 0);
+        } else {
+            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.
@@ -41,11 +59,20 @@
         @param mode     The PorterDuff mode that combines the colors from the two shaders.
     */
     public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
+        mShaderA = shaderA;
+        mShaderB = shaderB;
         native_instance = nativeCreate2(shaderA.native_instance, shaderB.native_instance,
-                                        mode.nativeInt);
+                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..66ed104 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
@@ -419,6 +422,10 @@
      * the transformed vectors into the array of vectors specified by dst. The
      * two arrays represent their "vectors" as pairs of floats [x, y].
      *
+     * Note: this method does not apply the translation associated with the matrix. Use
+     * {@link Matrix#mapPoints(float[], int, float[], int, int)} if you want the translation
+     * to be applied.
+     *
      * @param dst   The array of dst vectors (x,y pairs)
      * @param dstIndex The index of the first [x,y] pair of dst floats
      * @param src   The array of src vectors (x,y pairs)
@@ -452,6 +459,9 @@
      * the transformed vectors into the array of vectors specified by dst. The
      * two arrays represent their "vectors" as pairs of floats [x, y].
      *
+     * Note: this method does not apply the translation associated with the matrix. Use
+     * {@link Matrix#mapPoints(float[], float[])} if you want the translation to be applied.
+     *
      * @param dst   The array of dst vectors (x,y pairs)
      * @param src   The array of src vectors (x,y pairs)
      */
@@ -475,6 +485,10 @@
     /**
      * Apply this matrix to the array of 2D vectors, and write the transformed
      * vectors back into the array.
+     *
+     * Note: this method does not apply the translation associated with the matrix. Use
+     * {@link Matrix#mapPoints(float[])} if you want the translation to be applied.
+     *
      * @param vecs The array [x0, y0, x1, y1, ...] of vectors to transform.
      */
     public void mapVectors(float[] vecs) {
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..62fbfb4 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,32 @@
     private boolean     mHasCompatScaling;
     private float       mCompatScaling;
     private float       mInvCompatScaling;
+
+    /**
+     * @hide
+     */
+    public boolean hasShadow;
+    /**
+     * @hide
+     */
+    public float shadowDx;
+    /**
+     * @hide
+     */
+    public float shadowDy;
+    /**
+     * @hide
+     */
+    public float shadowRadius;
+    /**
+     * @hide
+     */
+    public int shadowColor;
+
+    /**
+     * @hide
+     */
+    public  int         mBidiFlags = BIDI_DEFAULT_LTR;
     
     private static final Style[] sStyleArray = {
         Style.FILL, Style.STROKE, Style.FILL_AND_STROKE
@@ -76,8 +106,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 +231,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 +348,7 @@
         mHasCompatScaling = paint.mHasCompatScaling;
         mCompatScaling = paint.mCompatScaling;
         mInvCompatScaling = paint.mInvCompatScaling;
+        mBidiFlags = paint.mBidiFlags;
     }
 
     /** Restores the paint to its default settings. */
@@ -216,6 +357,7 @@
         setFlags(DEFAULT_PAINT_FLAGS);
         mHasCompatScaling = false;
         mCompatScaling = mInvCompatScaling = 1;
+        mBidiFlags = BIDI_DEFAULT_LTR;
     }
     
     /**
@@ -238,6 +380,7 @@
             mHasCompatScaling = src.mHasCompatScaling;
             mCompatScaling    = src.mCompatScaling;
             mInvCompatScaling = src.mInvCompatScaling;
+            mBidiFlags      = src.mBidiFlags;
         }
     }
 
@@ -252,10 +395,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,18 +953,27 @@
     }
     
     /**
-     * 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 void setShadowLayer(float radius, float dx, float dy, int color) {
+        hasShadow = radius > 0.0f;
+        shadowRadius = radius;
+        shadowDx = dx;
+        shadowDy = dy;
+        shadowColor = color;
+        nSetShadowLayer(radius, dx, dy, color);
+    }
+    
+    private native void nSetShadowLayer(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);
+        hasShadow = false;
+        nSetShadowLayer(0, 0, 0, 0);
     }
 
     /**
@@ -1232,10 +1407,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 +1457,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 +1752,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 +1774,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 +1859,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 +1882,3 @@
                                 char[] text, int index, int count, Rect bounds);
     private static native void finalizer(int nativePaint);
 }
-
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 281823a..1324431 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -16,6 +16,8 @@
 
 package android.graphics;
 
+import android.view.HardwareRenderer;
+
 /**
  * The Path class encapsulates compound (multiple contour) geometric paths
  * consisting of straight line segments, quadratic curves, and cubic curves.
@@ -24,12 +26,27 @@
  * text on a path.
  */
 public class Path {
+    /**
+     * @hide
+     */
+    public final int mNativePath;
+
+    /**
+     * @hide
+     */
+    public boolean isSimplePath = true;
+    /**
+     * @hide
+     */
+    public Region rects;
+    private boolean mDetectSimplePaths;
 
     /**
      * Create an empty path
      */
     public Path() {
         mNativePath = init1();
+        mDetectSimplePaths = HardwareRenderer.isAvailable();
     }
 
     /**
@@ -43,6 +60,7 @@
             valNative = src.mNativePath;
         }
         mNativePath = init2(valNative);
+        mDetectSimplePaths = HardwareRenderer.isAvailable();
     }
     
     /**
@@ -50,6 +68,10 @@
      * This does NOT change the fill-type setting.
      */
     public void reset() {
+        isSimplePath = true;
+        if (mDetectSimplePaths) {
+            if (rects != null) rects.setEmpty();
+        }
         native_reset(mNativePath);
     }
 
@@ -58,6 +80,10 @@
      * keeps the internal data structure for faster reuse.
      */
     public void rewind() {
+        isSimplePath = true;
+        if (mDetectSimplePaths) {
+            if (rects != null) rects.setEmpty();
+        }
         native_rewind(mNativePath);
     }
 
@@ -65,6 +91,7 @@
     */
     public void set(Path src) {
         if (this != src) {
+            isSimplePath = src.isSimplePath;
             native_set(mNativePath, src.mNativePath);
         }
     }
@@ -160,6 +187,7 @@
      * @param bounds Returns the computed bounds of the path's control points.
      * @param exact This parameter is no longer used.
      */
+    @SuppressWarnings({"UnusedDeclaration"})
     public void computeBounds(RectF bounds, boolean exact) {
         native_computeBounds(mNativePath, bounds);
     }
@@ -208,6 +236,7 @@
      * @param y The y-coordinate of the end of a line
      */
     public void lineTo(float x, float y) {
+        isSimplePath = false;
         native_lineTo(mNativePath, x, y);
     }
 
@@ -222,6 +251,7 @@
      *           this contour, to specify a line
      */
     public void rLineTo(float dx, float dy) {
+        isSimplePath = false;
         native_rLineTo(mNativePath, dx, dy);
     }
 
@@ -236,6 +266,7 @@
      * @param y2 The y-coordinate of the end point on a quadratic curve
      */
     public void quadTo(float x1, float y1, float x2, float y2) {
+        isSimplePath = false;
         native_quadTo(mNativePath, x1, y1, x2, y2);
     }
 
@@ -254,6 +285,7 @@
      *            this contour, for the end point of a quadratic curve
      */
     public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
+        isSimplePath = false;
         native_rQuadTo(mNativePath, dx1, dy1, dx2, dy2);
     }
 
@@ -271,6 +303,7 @@
      */
     public void cubicTo(float x1, float y1, float x2, float y2,
                         float x3, float y3) {
+        isSimplePath = false;
         native_cubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
     }
 
@@ -281,6 +314,7 @@
      */
     public void rCubicTo(float x1, float y1, float x2, float y2,
                          float x3, float y3) {
+        isSimplePath = false;
         native_rCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
     }
 
@@ -299,6 +333,7 @@
      */
     public void arcTo(RectF oval, float startAngle, float sweepAngle,
                       boolean forceMoveTo) {
+        isSimplePath = false;
         native_arcTo(mNativePath, oval, startAngle, sweepAngle, forceMoveTo);
     }
     
@@ -314,6 +349,7 @@
      * @param sweepAngle  Sweep angle (in degrees) measured clockwise
      */
     public void arcTo(RectF oval, float startAngle, float sweepAngle) {
+        isSimplePath = false;
         native_arcTo(mNativePath, oval, startAngle, sweepAngle, false);
     }
     
@@ -322,6 +358,7 @@
      * first point of the contour, a line segment is automatically added.
      */
     public void close() {
+        isSimplePath = false;
         native_close(mNativePath);
     }
 
@@ -351,6 +388,11 @@
         if (rect == null) {
             throw new NullPointerException("need rect parameter");
         }
+        if (mDetectSimplePaths) {
+            if (rects == null) rects = new Region();
+            rects.op((int) rect.left, (int) rect.top, (int) rect.right, (int) rect.bottom,
+                    Region.Op.UNION);
+        }
         native_addRect(mNativePath, rect, dir.nativeInt);
     }
 
@@ -363,8 +405,11 @@
      * @param bottom The bottom of a rectangle to add to the path
      * @param dir    The direction to wind the rectangle's contour
      */
-    public void addRect(float left, float top, float right, float bottom,
-                        Direction dir) {
+    public void addRect(float left, float top, float right, float bottom, Direction dir) {
+        if (mDetectSimplePaths) {
+            if (rects == null) rects = new Region();
+            rects.op((int) left, (int) top, (int) right, (int) bottom, Region.Op.UNION);
+        }
         native_addRect(mNativePath, left, top, right, bottom, dir.nativeInt);
     }
 
@@ -378,6 +423,7 @@
         if (oval == null) {
             throw new NullPointerException("need oval parameter");
         }
+        isSimplePath = false;
         native_addOval(mNativePath, oval, dir.nativeInt);
     }
 
@@ -390,6 +436,7 @@
      * @param dir    The direction to wind the circle's contour
      */
     public void addCircle(float x, float y, float radius, Direction dir) {
+        isSimplePath = false;
         native_addCircle(mNativePath, x, y, radius, dir.nativeInt);
     }
 
@@ -404,6 +451,7 @@
         if (oval == null) {
             throw new NullPointerException("need oval parameter");
         }
+        isSimplePath = false;
         native_addArc(mNativePath, oval, startAngle, sweepAngle);
     }
 
@@ -419,6 +467,7 @@
         if (rect == null) {
             throw new NullPointerException("need rect parameter");
         }
+        isSimplePath = false;
         native_addRoundRect(mNativePath, rect, rx, ry, dir.nativeInt);
     }
     
@@ -438,6 +487,7 @@
         if (radii.length < 8) {
             throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
         }
+        isSimplePath = false;
         native_addRoundRect(mNativePath, rect, radii, dir.nativeInt);
     }
     
@@ -448,6 +498,7 @@
      * @param dx  The amount to translate the path in X as it is added
      */
     public void addPath(Path src, float dx, float dy) {
+        isSimplePath = false;
         native_addPath(mNativePath, src.mNativePath, dx, dy);
     }
 
@@ -457,6 +508,7 @@
      * @param src The path that is appended to the current path
      */
     public void addPath(Path src) {
+        isSimplePath = false;
         native_addPath(mNativePath, src.mNativePath);
     }
 
@@ -466,6 +518,7 @@
      * @param src The path to add as a new contour
      */
     public void addPath(Path src, Matrix matrix) {
+        if (!src.isSimplePath) isSimplePath = false;
         native_addPath(mNativePath, src.mNativePath, matrix.native_instance);
     }
 
@@ -502,6 +555,7 @@
      * @param dy The new Y coordinate for the last point
      */
     public void setLastPoint(float dx, float dy) {
+        isSimplePath = false;
         native_setLastPoint(mNativePath, dx, dy);
     }
 
@@ -537,8 +591,8 @@
             super.finalize();
         }
     }
-    
-    /*package*/ final int ni() {
+
+    final int ni() {
         return mNativePath;
     }
 
@@ -592,6 +646,4 @@
                                                 int dst_path);
     private static native void native_transform(int nPath, int matrix);
     private static native void finalizer(int nPath);
-
-    private final int mNativePath;
 }
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/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java
index cb127fd..6ba064c 100644
--- a/graphics/java/android/graphics/PorterDuffXfermode.java
+++ b/graphics/java/android/graphics/PorterDuffXfermode.java
@@ -18,11 +18,17 @@
 
 public class PorterDuffXfermode extends Xfermode {
     /**
+     * @hide
+     */
+    public final PorterDuff.Mode mode;
+
+    /**
      * Create an xfermode that uses the specified porter-duff mode.
      *
      * @param mode           The porter-duff mode that is applied
      */
     public PorterDuffXfermode(PorterDuff.Mode mode) {
+        this.mode = mode;
         native_instance = nativeCreateXfermode(mode.nativeInt);
     }
     
diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java
index b4e902d..897762c 100644
--- a/graphics/java/android/graphics/RadialGradient.java
+++ b/graphics/java/android/graphics/RadialGradient.java
@@ -40,6 +40,8 @@
             throw new IllegalArgumentException("color and position arrays must be of equal length");
         }
         native_instance = nativeCreate1(x, y, radius, colors, positions, tile.nativeInt);
+        native_shader = nativePostCreate1(native_instance, x, y, radius, colors, positions,
+                tile.nativeInt);
     }
 
 	/**	Create a shader that draws a radial gradient given the center and radius.
@@ -56,11 +58,18 @@
             throw new IllegalArgumentException("radius must be > 0");
         }
         native_instance = nativeCreate2(x, y, radius, color0, color1, tile.nativeInt);
+        native_shader = nativePostCreate2(native_instance, x, y, radius, color0, color1,
+                tile.nativeInt);
     }
 
 	private static native int nativeCreate1(float x, float y, float radius,
-                                            int colors[], float positions[], int tileMode);
+            int colors[], float positions[], int tileMode);
 	private static native int nativeCreate2(float x, float y, float radius,
-                                            int color0, int color1, int tileMode);
+            int color0, int color1, int tileMode);
+
+    private static native int nativePostCreate1(int native_shader, float x, float y, float radius,
+            int colors[], float positions[], int tileMode);
+    private static native int nativePostCreate2(int native_shader, float x, float y, float radius,
+            int color0, int color1, int tileMode);
 }
 
diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java
index 2b080aa..e540806 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -20,6 +20,10 @@
 import android.os.Parcelable;
 
 public class Region implements Parcelable {
+    /**
+     * @hide
+     */
+    public final int mNativeRegion;
 
     // the native values for these must match up with the enum in SkRegion.h
     public enum Op {
@@ -33,7 +37,11 @@
         Op(int nativeInt) {
             this.nativeInt = nativeInt;
         }
-        final int nativeInt;
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
     }
 
     /** Create an empty region
@@ -325,10 +333,14 @@
     }
 
     protected void finalize() throws Throwable {
-        nativeDestructor(mNativeRegion);
+        try {
+            nativeDestructor(mNativeRegion);
+        } finally {
+            super.finalize();
+        }
     }
     
-    /*package*/ Region(int ni) {
+    Region(int ni) {
         if (ni == 0) {
             throw new RuntimeException();
         }
@@ -341,7 +353,7 @@
         this(ni);
     }
 
-    /*package*/ final int ni() {
+    final int ni() {
         return mNativeRegion;
     }
 
@@ -370,6 +382,4 @@
                                                       Parcel p);
 
     private static native boolean nativeEquals(int native_r1, int native_r2);
-
-    private final int mNativeRegion;
 }
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index ae0304e..0400b5c 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,21 @@
      * @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 == null ? 0 : 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/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java
index 7456993..2afdd4d 100644
--- a/graphics/java/android/graphics/SweepGradient.java
+++ b/graphics/java/android/graphics/SweepGradient.java
@@ -42,6 +42,7 @@
                         "color and position arrays must be of equal length");
         }
         native_instance = nativeCreate1(cx, cy, colors, positions);
+        native_shader = nativePostCreate1(native_instance, cx, cy, colors, positions);
     }
 
     /**
@@ -54,11 +55,15 @@
      */
     public SweepGradient(float cx, float cy, int color0, int color1) {
         native_instance = nativeCreate2(cx, cy, color0, color1);
+        native_shader = nativePostCreate2(native_instance, cx, cy, color0, color1);
     }
 
-    private static native int nativeCreate1(float x, float y,
-                                            int colors[], float positions[]);
-    private static native int nativeCreate2(float x, float y,
-                                            int color0, int color1);
+    private static native int nativeCreate1(float x, float y, int colors[], float positions[]);
+    private static native int nativeCreate2(float x, float y, int color0, int color1);
+
+    private static native int nativePostCreate1(int native_shader, float cx, float cy,
+            int[] colors, float[] positions);    
+    private static native int nativePostCreate2(int native_shader, float cx, float cy,
+            int color0, int color1);
 }
 
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/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index 42c410e..2467bdc 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -31,7 +31,11 @@
 public class Xfermode {
 
     protected void finalize() throws Throwable {
-        finalizer(native_instance);
+        try {
+            finalizer(native_instance);
+        } finally {
+            super.finalize();
+        }
     }
 
     private static native void finalizer(int native_instance);
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 604c602..a25fad4 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -26,10 +26,8 @@
 import java.io.IOException;
 
 /**
- * A specialized Drawable that fills the Canvas with a specified color,
- * with respect to the clip region. Note that a ColorDrawable ignores the ColorFilter.
- * It also ignores the Bounds, meaning it will draw everywhere in the current clip,
- * even if setBounds(...) was called with a smaller area.
+ * A specialized Drawable that fills the Canvas with a specified color.
+ * Note that a ColorDrawable ignores the ColorFilter.
  *
  * <p>It can be defined in an XML file with the <code>&lt;color></code> element.</p>
  *
@@ -37,6 +35,7 @@
  */
 public class ColorDrawable extends Drawable {
     private ColorState mState;
+    private final Paint mPaint = new Paint();
 
     /**
      * Creates a new black ColorDrawable.
@@ -66,7 +65,10 @@
 
     @Override
     public void draw(Canvas canvas) {
-        canvas.drawColor(mState.mUseColor);
+        if ((mState.mUseColor >>> 24) != 0) {
+            mPaint.setColor(mState.mUseColor);
+            canvas.drawRect(getBounds(), mPaint);
+        }
     }
 
     /**
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/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 33ecbea..88f6d43 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -313,18 +313,16 @@
             case RECTANGLE:
                 if (st.mRadiusArray != null) {
                     mPath.reset();
-                    mPath.addRoundRect(mRect, st.mRadiusArray,
-                                       Path.Direction.CW);
+                    mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
                     canvas.drawPath(mPath, mFillPaint);
                     if (haveStroke) {
                         canvas.drawPath(mPath, mStrokePaint);
                     }
-                }
-                else {
+                } else if (st.mRadius > 0.0f) {
                     // since the caller is only giving us 1 value, we will force
                     // it to be square if the rect is too small in one dimension
                     // to show it. If we did nothing, Skia would clamp the rad
-                    // independently along each axis, giving us a thin ellips
+                    // independently along each axis, giving us a thin ellipse
                     // if the rect were very wide but not very tall
                     float rad = st.mRadius;
                     float r = Math.min(mRect.width(), mRect.height()) * 0.5f;
@@ -335,6 +333,11 @@
                     if (haveStroke) {
                         canvas.drawRoundRect(mRect, rad, rad, mStrokePaint);
                     }
+                } else {
+                    canvas.drawRect(mRect, mFillPaint);
+                    if (haveStroke) {
+                        canvas.drawRect(mRect, mStrokePaint);
+                    }
                 }
                 break;
             case OVAL:
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 00416d8..23efcd1 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -177,16 +177,9 @@
             }
         }
     }
-    
-    // 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/graphics/drawable/shapes/RoundRectShape.java b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
index f4cf15c..b469d2a 100644
--- a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
@@ -57,13 +57,11 @@
      */
     public RoundRectShape(float[] outerRadii, RectF inset,
                           float[] innerRadii) {
-        if (outerRadii.length < 8) {
-            throw new ArrayIndexOutOfBoundsException(
-                                        "outer radii must have >= 8 values");
+        if (outerRadii != null && outerRadii.length < 8) {
+            throw new ArrayIndexOutOfBoundsException("outer radii must have >= 8 values");
         }
         if (innerRadii != null && innerRadii.length < 8) {
-            throw new ArrayIndexOutOfBoundsException(
-                                        "inner radii must have >= 8 values");
+            throw new ArrayIndexOutOfBoundsException("inner radii must have >= 8 values");
         }
         mOuterRadii = outerRadii;
         mInset = inset;
@@ -97,8 +95,7 @@
                            r.right - mInset.right, r.bottom - mInset.bottom);
             if (mInnerRect.width() < w && mInnerRect.height() < h) {
                 if (mInnerRadii != null) {
-                    mPath.addRoundRect(mInnerRect, mInnerRadii,
-                                       Path.Direction.CCW);
+                    mPath.addRoundRect(mInnerRect, mInnerRadii, Path.Direction.CCW);
                 } else {
                     mPath.addRect(mInnerRect, Path.Direction.CCW);
                 }
@@ -109,8 +106,8 @@
     @Override
     public RoundRectShape clone() throws CloneNotSupportedException {
         RoundRectShape shape = (RoundRectShape) super.clone();
-        shape.mOuterRadii = mOuterRadii.clone();
-        shape.mInnerRadii = mInnerRadii.clone();
+        shape.mOuterRadii = mOuterRadii != null ? mOuterRadii.clone() : null;
+        shape.mInnerRadii = mInnerRadii != null ? mInnerRadii.clone() : null;
         shape.mInset = new RectF(mInset);
         shape.mInnerRect = new RectF(mInnerRect);
         shape.mPath = new Path(mPath);
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index b27c7f5..2c076b3 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -35,11 +35,25 @@
     Bitmap mBitmap;
 
     Allocation(int id, RenderScript rs, Type t) {
-        super(rs);
-        mID = id;
+        super(id, rs);
         mType = t;
     }
 
+    Allocation(int id, RenderScript rs) {
+        super(id, rs);
+    }
+
+    @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 +90,50 @@
         subData1D(0, mType.getElementCount(), d);
     }
 
+    public void subData(int xoff, 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(xoff, count, data.length, data.length);
+        mRS.nAllocationSubData1D(mID, xoff, count, data, data.length);
+    }
+
+
+    public void subElementData(int xoff, int component_number, FieldPacker fp) {
+        if (component_number >= mType.mElement.mElements.length) {
+            throw new IllegalArgumentException("Component_number " + component_number + " out of range.");
+        }
+        if(xoff < 0) {
+            throw new IllegalArgumentException("Offset must be >= 0.");
+        }
+
+        final byte[] data = fp.getData();
+        int eSize = mType.mElement.mElements[component_number].getSizeBytes();
+
+        if (data.length != eSize) {
+            throw new IllegalArgumentException("Field packer sizelength " + data.length +
+                                               " does not match component size " + eSize + ".");
+        }
+
+        mRS.nAllocationSubElementData1D(mID, xoff, component_number, 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.");
@@ -108,7 +162,6 @@
     }
 
 
-
     public void subData2D(int xoff, int yoff, int w, int h, int[] d) {
         mRS.validate();
         mRS.nAllocationSubData2D(mID, xoff, yoff, w, h, d, d.length * 4);
@@ -129,25 +182,28 @@
         mRS.nAllocationRead(mID, d);
     }
 
-    public void data(Object o) {
-        mRS.validate();
-        mRS.nAllocationSubDataFromObject(mID, mType, 0, o);
+    public void resize(int dimX) {
+        if ((mType.getY() > 0)|| (mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) {
+            throw new IllegalStateException("Resize only support for 1D allocations at this time.");
+        }
+        mRS.nAllocationResize1D(mID, dimX);
     }
 
-    public void read(Object o) {
-        mRS.validate();
-        mRS.nAllocationSubReadFromObject(mID, mType, 0, o);
+    /*
+    public void resize(int dimX, int dimY) {
+        if ((mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) {
+            throw new IllegalStateException("Resize only support for 2D allocations at this time.");
+        }
+        if (mType.getY() == 0) {
+            throw new IllegalStateException("Resize only support for 2D allocations at this time.");
+        }
+        mRS.nAllocationResize2D(mID, dimX, dimY);
     }
-
-    public void subData(int offset, Object o) {
-        mRS.validate();
-        mRS.nAllocationSubDataFromObject(mID, mType, offset, o);
-    }
+    */
 
     public class Adapter1D extends BaseObj {
         Adapter1D(int id, RenderScript rs) {
-            super(rs);
-            mID = id;
+            super(id, rs);
         }
 
         public void setConstraint(Dimension dim, int value) {
@@ -189,8 +245,7 @@
 
     public class Adapter2D extends BaseObj {
         Adapter2D(int id, RenderScript rs) {
-            super(rs);
-            mID = id;
+            super(id, rs);
         }
 
         public void setConstraint(Dimension dim, int value) {
@@ -377,6 +432,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..715e3fb 100644
--- a/graphics/java/android/renderscript/BaseObj.java
+++ b/graphics/java/android/renderscript/BaseObj.java
@@ -24,14 +24,17 @@
  **/
 class BaseObj {
 
-    BaseObj(RenderScript rs) {
+    BaseObj(int id, RenderScript rs) {
         rs.validate();
         mRS = rs;
-        mID = 0;
+        mID = id;
         mDestroyed = false;
     }
 
     public int getID() {
+        if (mDestroyed) {
+            throw new IllegalStateException("using a destroyed object.");
+        }
         return mID;
     }
 
@@ -62,7 +65,7 @@
     {
         if (!mDestroyed) {
             if(mID != 0 && mRS.isAlive()) {
-                mRS.nObjDestroyOOB(mID);
+                mRS.nObjDestroy(mID);
             }
             mRS = null;
             mID = 0;
@@ -81,5 +84,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..91824e6 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
@@ -26,6 +27,7 @@
     int mSize;
     Element[] mElements;
     String[] mElementNames;
+    int[] mArraySizes;
 
     DataType mType;
     DataKind mKind;
@@ -37,30 +39,36 @@
     public enum DataType {
         //FLOAT_16 (1, 2),
         FLOAT_32 (2, 4),
-        //FLOAT_64 (3, 8),
+        FLOAT_64 (3, 8),
         SIGNED_8 (4, 1),
         SIGNED_16 (5, 2),
         SIGNED_32 (6, 4),
-        //SIGNED_64 (7, 8),
+        SIGNED_64 (7, 8),
         UNSIGNED_8 (8, 1),
         UNSIGNED_16 (9, 2),
         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),
+
+        MATRIX_4X4 (16, 64),
+        MATRIX_3X3 (17, 36),
+        MATRIX_2X2 (18, 16),
+
+        RS_ELEMENT (1000, 4),
+        RS_TYPE (1001, 4),
+        RS_ALLOCATION (1002, 4),
+        RS_SAMPLER (1003, 4),
+        RS_SCRIPT (1004, 4),
+        RS_MESH (1005, 4),
+        RS_PROGRAM_FRAGMENT (1006, 4),
+        RS_PROGRAM_VERTEX (1007, 4),
+        RS_PROGRAM_RASTER (1008, 4),
+        RS_PROGRAM_STORE (1009, 4);
 
         int mID;
         int mSize;
@@ -72,12 +80,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 +93,147 @@
         }
     }
 
-    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 I64(RenderScript rs) {
+        if(rs.mElement_I64 == null) {
+            rs.mElement_I64 = createUser(rs, DataType.SIGNED_64);
+        }
+        return rs.mElement_I64;
+    }
+
+    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 F64(RenderScript rs) {
+        if(rs.mElement_F64 == null) {
+            rs.mElement_F64 = createUser(rs, DataType.FLOAT_64);
+        }
+        return rs.mElement_F64;
+    }
+
+    public static Element ELEMENT(RenderScript rs) {
+        if(rs.mElement_ELEMENT == null) {
+            rs.mElement_ELEMENT = createUser(rs, DataType.RS_ELEMENT);
+        }
+        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,168 +276,141 @@
         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);
+    public static Element MATRIX_4X4(RenderScript rs) {
+        if(rs.mElement_MATRIX_4X4 == null) {
+            rs.mElement_MATRIX_4X4 = createUser(rs, DataType.MATRIX_4X4);
         }
-        return rs.mElement_NORMAL_3;
+        return rs.mElement_MATRIX_4X4;
+    }
+    public static Element MATRIX4X4(RenderScript rs) {
+        return MATRIX_4X4(rs);
     }
 
-    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);
+    public static Element MATRIX_3X3(RenderScript rs) {
+        if(rs.mElement_MATRIX_3X3 == null) {
+            rs.mElement_MATRIX_3X3 = createUser(rs, DataType.MATRIX_3X3);
         }
-        return rs.mElement_COLOR_U8_4;
+        return rs.mElement_MATRIX_4X4;
     }
 
-    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);
+    public static Element MATRIX_2X2(RenderScript rs) {
+        if(rs.mElement_MATRIX_2X2 == null) {
+            rs.mElement_MATRIX_2X2 = createUser(rs, DataType.MATRIX_2X2);
         }
-        return rs.mElement_COLOR_F32_4;
+        return rs.mElement_MATRIX_2X2;
     }
 
-    Element(RenderScript rs, Element[] e, String[] n) {
-        super(rs);
+    Element(int id, RenderScript rs, Element[] e, String[] n, int[] as) {
+        super(id, rs);
         mSize = 0;
         mElements = e;
         mElementNames = n;
-        int[] ids = new int[mElements.length];
+        mArraySizes = as;
         for (int ct = 0; ct < mElements.length; ct++ ) {
             mSize += mElements[ct].mSize;
-            ids[ct] = mElements[ct].mID;
         }
-        mID = rs.nElementCreate2(ids, mElementNames);
     }
 
-    Element(RenderScript rs, DataType dt, DataKind dk, boolean norm, int size) {
-        super(rs);
+    Element(int id, RenderScript rs, DataType dt, DataKind dk, boolean norm, int size) {
+        super(id, rs);
         mSize = dt.mSize * size;
         mType = dt;
         mKind = dk;
         mNormalized = norm;
         mVectorSize = size;
-        mID = rs.nElementCreate(dt.mID, dk.mID, norm, size);
+    }
+
+    Element(int id, RenderScript rs) {
+        super(id, rs);
+    }
+
+    @Override
+    void updateFromNative() {
+
+        // we will pack mType; mKind; mNormalized; mVectorSize; NumSubElements
+        int[] dataBuffer = new int[5];
+        mRS.nElementGetNativeData(mID, dataBuffer);
+
+        mNormalized = dataBuffer[2] == 1 ? true : false;
+        mVectorSize = dataBuffer[3];
+        mSize = 0;
+        for (DataType dt: DataType.values()) {
+            if(dt.mID == dataBuffer[0]){
+                mType = dt;
+                mSize = mType.mSize * mVectorSize;
+            }
+        }
+        for (DataKind dk: DataKind.values()) {
+            if(dk.mID == dataBuffer[1]){
+                mKind = dk;
+            }
+        }
+
+        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(subElementIds[i], mRS);
+                mElements[i].updateFromNative();
+                mSize += mElements[i].mSize;
+            }
+        }
+
     }
 
     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);
+        DataKind dk = DataKind.USER;
+        boolean norm = false;
+        int vecSize = 1;
+        int id = rs.nElementCreate(dt.mID, dk.mID, norm, vecSize);
+        return new Element(id, rs, dt, dk, norm, vecSize);
     }
 
     public static Element createVector(RenderScript rs, DataType dt, int size) {
         if (size < 2 || size > 4) {
             throw new IllegalArgumentException("Bad size");
         }
-        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");
-        }
-
+        DataKind dk = DataKind.USER;
         boolean norm = false;
-        if (dk == DataKind.COLOR && dt == DataType.UNSIGNED_8) {
-            norm = true;
-        }
-
-        return new Element(rs, dt, dk, norm, size);
+        int id = rs.nElementCreate(dt.mID, dk.mID, norm, size);
+        return new Element(id, rs, dt, dk, norm, size);
     }
 
     public static Element createPixel(RenderScript rs, DataType dt, DataKind dk) {
@@ -367,13 +448,16 @@
             size = 4;
         }
 
-        return new Element(rs, dt, dk, true, size);
+        boolean norm = true;
+        int id = rs.nElementCreate(dt.mID, dk.mID, norm, size);
+        return new Element(id, rs, dt, dk, norm, size);
     }
 
     public static class Builder {
         RenderScript mRS;
         Element[] mElements;
         String[] mElementNames;
+        int[] mArraySizes;
         int mCount;
 
         public Builder(RenderScript rs) {
@@ -381,29 +465,49 @@
             mCount = 0;
             mElements = new Element[8];
             mElementNames = new String[8];
+            mArraySizes = new int[8];
         }
 
-        public void add(Element element, String name) {
+        public void add(Element element, String name, int arraySize) {
+            if (arraySize < 1) {
+                throw new IllegalArgumentException("Array size cannot be less than 1.");
+            }
             if(mCount == mElements.length) {
                 Element[] e = new Element[mCount + 8];
                 String[] s = new String[mCount + 8];
+                int[] as = new int[mCount + 8];
                 System.arraycopy(mElements, 0, e, 0, mCount);
                 System.arraycopy(mElementNames, 0, s, 0, mCount);
+                System.arraycopy(mArraySizes, 0, as, 0, mCount);
                 mElements = e;
                 mElementNames = s;
+                mArraySizes = as;
             }
             mElements[mCount] = element;
             mElementNames[mCount] = name;
+            mArraySizes[mCount] = arraySize;
             mCount++;
         }
 
+        public void add(Element element, String name) {
+            add(element, name, 1);
+        }
+
         public Element create() {
             mRS.validate();
             Element[] ein = new Element[mCount];
             String[] sin = new String[mCount];
+            int[] asin = new int[mCount];
             java.lang.System.arraycopy(mElements, 0, ein, 0, mCount);
             java.lang.System.arraycopy(mElementNames, 0, sin, 0, mCount);
-            return new Element(mRS, ein, sin);
+            java.lang.System.arraycopy(mArraySizes, 0, asin, 0, mCount);
+
+            int[] ids = new int[ein.length];
+            for (int ct = 0; ct < ein.length; ct++ ) {
+                ids[ct] = ein[ct].mID;
+            }
+            int id = mRS.nElementCreate2(ids, sin, asin);
+            return new Element(id, mRS, ein, sin, asin);
         }
     }
 
diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java
index b26e47d..ff3e22b 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,15 +74,17 @@
         mData[mPos++] = (byte)((v >> 56) & 0xff);
     }
 
-    void addU8(short v) {
+    public void addU8(short v) {
         if ((v < 0) || (v > 0xff)) {
+            android.util.Log.e("rs", "FieldPacker.addU8( " + v + " )");
             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)) {
+            android.util.Log.e("rs", "FieldPacker.addU16( " + v + " )");
             throw new IllegalArgumentException("Saving value out of range for type");
         }
         align(2);
@@ -83,8 +92,9 @@
         mData[mPos++] = (byte)(v >> 8);
     }
 
-    void addU32(long v) {
-        if ((v < 0) || (v > 0xffffffff)) {
+    public void addU32(long v) {
+        if ((v < 0) || (v > 0xffffffffL)) {
+            android.util.Log.e("rs", "FieldPacker.addU32( " + v + " )");
             throw new IllegalArgumentException("Saving value out of range for type");
         }
         align(4);
@@ -94,8 +104,9 @@
         mData[mPos++] = (byte)((v >> 24) & 0xff);
     }
 
-    void addU64(long v) {
+    public void addU64(long v) {
         if (v < 0) {
+            android.util.Log.e("rs", "FieldPacker.addU64( " + v + " )");
             throw new IllegalArgumentException("Saving value out of range for type");
         }
         align(8);
@@ -109,15 +120,157 @@
         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(double 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 addObj(Matrix4f v) {
+        for (int i=0; i < v.mMat.length; i++) {
+            addF32(v.mMat[i]);
+        }
+    }
+
+    public void addObj(Matrix3f v) {
+        for (int i=0; i < v.mMat.length; i++) {
+            addF32(v.mMat[i]);
+        }
+    }
+
+    public void addObj(Matrix2f v) {
+        for (int i=0; i < v.mMat.length; i++) {
+            addF32(v.mMat[i]);
+        }
+    }
+
+    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..2414062
--- /dev/null
+++ b/graphics/java/android/renderscript/FileA3D.java
@@ -0,0 +1,217 @@
+/*
+ * 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(id, rs);
+    }
+
+    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..889bf7b
--- /dev/null
+++ b/graphics/java/android/renderscript/Float2.java
@@ -0,0 +1,42 @@
+/*
+ * 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 Float2(float initX, float initY) {
+        x = initX;
+        y = initY;
+    }
+
+    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..ebe140d
--- /dev/null
+++ b/graphics/java/android/renderscript/Float3.java
@@ -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.
+ */
+
+package android.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Float3 {
+    public Float3() {
+    }
+    public Float3(float initX, float initY, float initZ) {
+        x = initX;
+        y = initY;
+        z = initZ;
+    }
+
+    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..847732f
--- /dev/null
+++ b/graphics/java/android/renderscript/Float4.java
@@ -0,0 +1,45 @@
+/*
+ * 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 Float4(float initX, float initY, float initZ, float initW) {
+        x = initX;
+        y = initY;
+        z = initZ;
+        w = initW;
+    }
+
+    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..de25014
--- /dev/null
+++ b/graphics/java/android/renderscript/Font.java
@@ -0,0 +1,165 @@
+/*
+ * 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 java.util.Map;
+import java.util.HashMap;
+
+import android.content.res.Resources;
+import android.content.res.AssetManager;
+import android.util.Log;
+import android.util.TypedValue;
+
+/**
+ * @hide
+ *
+ **/
+public class Font extends BaseObj {
+
+    //These help us create a font by family name
+    private static final String[] sSansNames = {
+        "sans-serif", "arial", "helvetica", "tahoma", "verdana"
+    };
+
+    private static final String[] sSerifNames = {
+        "serif", "times", "times new roman", "palatino", "georgia", "baskerville",
+        "goudy", "fantasy", "cursive", "ITC Stone Serif"
+    };
+
+    private static final String[] sMonoNames = {
+        "monospace", "courier", "courier new", "monaco"
+    };
+
+    private static class FontFamily {
+        String[] mNames;
+        String mNormalFileName;
+        String mBoldFileName;
+        String mItalicFileName;
+        String mBoldItalicFileName;
+    }
+
+    private static Map<String, FontFamily> sFontFamilyMap;
+
+    public enum Style {
+        NORMAL,
+        BOLD,
+        ITALIC,
+        BOLD_ITALIC;
+    }
+
+    private static void addFamilyToMap(FontFamily family) {
+        for(int i = 0; i < family.mNames.length; i ++) {
+            sFontFamilyMap.put(family.mNames[i], family);
+        }
+    }
+
+    private static void initFontFamilyMap() {
+        sFontFamilyMap = new HashMap<String, FontFamily>();
+
+        FontFamily sansFamily = new FontFamily();
+        sansFamily.mNames = sSansNames;
+        sansFamily.mNormalFileName = "DroidSans.ttf";
+        sansFamily.mBoldFileName = "DroidSans-Bold.ttf";
+        sansFamily.mItalicFileName = "DroidSans.ttf";
+        sansFamily.mBoldItalicFileName = "DroidSans-Bold.ttf";
+        addFamilyToMap(sansFamily);
+
+        FontFamily serifFamily = new FontFamily();
+        serifFamily.mNames = sSerifNames;
+        serifFamily.mNormalFileName = "DroidSerif-Regular.ttf";
+        serifFamily.mBoldFileName = "DroidSerif-Bold.ttf";
+        serifFamily.mItalicFileName = "DroidSerif-Italic.ttf";
+        serifFamily.mBoldItalicFileName = "DroidSerif-BoldItalic.ttf";
+        addFamilyToMap(serifFamily);
+
+        FontFamily monoFamily = new FontFamily();
+        monoFamily.mNames = sMonoNames;
+        monoFamily.mNormalFileName = "DroidSansMono.ttf";
+        monoFamily.mBoldFileName = "DroidSansMono.ttf";
+        monoFamily.mItalicFileName = "DroidSansMono.ttf";
+        monoFamily.mBoldItalicFileName = "DroidSansMono.ttf";
+        addFamilyToMap(monoFamily);
+    }
+
+    static {
+        initFontFamilyMap();
+    }
+
+    static String getFontFileName(String familyName, Style style) {
+        FontFamily family = sFontFamilyMap.get(familyName);
+        if(family != null) {
+            switch(style) {
+                case NORMAL:
+                    return family.mNormalFileName;
+                case BOLD:
+                    return family.mBoldFileName;
+                case ITALIC:
+                    return family.mItalicFileName;
+                case BOLD_ITALIC:
+                    return family.mBoldItalicFileName;
+            }
+        }
+        // Fallback if we could not find the desired family
+        return "DroidSans.ttf";
+    }
+
+    Font(int id, RenderScript rs) {
+        super(id, rs);
+    }
+
+    /**
+     * Takes a specific file name as an argument
+     */
+    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("Failed loading a font");
+            }
+            Font rsFont = new Font(fontId, rs);
+
+            return rsFont;
+
+        } catch (Exception e) {
+            // Ignore
+        }
+
+        return null;
+    }
+
+    /**
+     * Accepts one of the following family names as an argument
+     * and will attemp to produce the best match with a system font
+     * "sans-serif" "arial" "helvetica" "tahoma" "verdana"
+     * "serif" "times" "times new roman" "palatino" "georgia" "baskerville"
+     * "goudy" "fantasy" "cursive" "ITC Stone Serif"
+     * "monospace" "courier" "courier new" "monaco"
+     * Returns default font if no match could be found
+     */
+    static public Font createFromFamily(RenderScript rs, Resources res, String familyName, Style fontStyle, int size)
+    throws IllegalArgumentException {
+        String fileName = getFontFileName(familyName, fontStyle);
+        return create(rs, res, fileName, size);
+    }
+}
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/Light.java b/graphics/java/android/renderscript/Light.java
deleted file mode 100644
index aab656f..0000000
--- a/graphics/java/android/renderscript/Light.java
+++ /dev/null
@@ -1,76 +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 Light extends BaseObj {
-    Light(int id, RenderScript rs) {
-        super(rs);
-        mID = id;
-    }
-
-    public void setColor(float r, float g, float b) {
-        mRS.validate();
-        mRS.nLightSetColor(mID, r, g, b);
-    }
-
-    public void setPosition(float x, float y, float z) {
-        mRS.validate();
-        mRS.nLightSetPosition(mID, x, y, z);
-    }
-
-    public static class Builder {
-        RenderScript mRS;
-        boolean mIsMono;
-        boolean mIsLocal;
-
-        public Builder(RenderScript rs) {
-            mRS = rs;
-            mIsMono = false;
-            mIsLocal = false;
-        }
-
-        public void lightSetIsMono(boolean isMono) {
-            mIsMono = isMono;
-        }
-
-        public void lightSetIsLocal(boolean isLocal) {
-            mIsLocal = isLocal;
-        }
-
-        static synchronized Light internalCreate(RenderScript rs, Builder b) {
-            rs.nSamplerBegin();
-            rs.nLightSetIsMono(b.mIsMono);
-            rs.nLightSetIsLocal(b.mIsLocal);
-            int id = rs.nLightCreate();
-            return new Light(id, rs);
-        }
-
-        public Light create() {
-            mRS.validate();
-            return internalCreate(mRS, this);
-        }
-    }
-
-}
-
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..5ffc21a 100644
--- a/graphics/java/android/renderscript/Matrix4f.java
+++ b/graphics/java/android/renderscript/Matrix4f.java
@@ -179,6 +179,85 @@
         tmp.loadTranslate(x, y, z);
         multiply(tmp);
     }
+    private float computeCofactor(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 = (mMat[c0 + 4*r0] * (mMat[c1 + 4*r1] * mMat[c2 + 4*r2] -
+                                            mMat[c1 + 4*r2] * mMat[c2 + 4*r1]))
+                     - (mMat[c0 + 4*r1] * (mMat[c1 + 4*r0] * mMat[c2 + 4*r2] -
+                                            mMat[c1 + 4*r2] * mMat[c2 + 4*r0]))
+                     + (mMat[c0 + 4*r2] * (mMat[c1 + 4*r0] * mMat[c2 + 4*r1] -
+                                            mMat[c1 + 4*r1] * mMat[c2 + 4*r0]));
+
+        float cofactor = ((i+j) & 1) != 0 ? -minor : minor;
+        return cofactor;
+    }
+
+    public boolean inverse() {
+
+        Matrix4f result = new Matrix4f();
+
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0; j < 4; ++j) {
+                result.mMat[4*i + j] = computeCofactor(i, j);
+            }
+        }
+
+        // Dot product of 0th column of source and 0th row of result
+        float det = mMat[0]*result.mMat[0] + mMat[4]*result.mMat[1] +
+                     mMat[8]*result.mMat[2] + mMat[12]*result.mMat[3];
+
+        if (Math.abs(det) < 1e-6) {
+            return false;
+        }
+
+        det = 1.0f / det;
+        for (int i = 0; i < 16; ++i) {
+            mMat[i] = result.mMat[i] * det;
+        }
+
+        return true;
+    }
+
+    public boolean inverseTranspose() {
+
+        Matrix4f result = new Matrix4f();
+
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0; j < 4; ++j) {
+                result.mMat[4*j + i] = computeCofactor(i, j);
+            }
+        }
+
+        float det = mMat[0]*result.mMat[0] + mMat[4]*result.mMat[4] +
+                     mMat[8]*result.mMat[8] + mMat[12]*result.mMat[12];
+
+        if (Math.abs(det) < 1e-6) {
+            return false;
+        }
+
+        det = 1.0f / det;
+        for (int i = 0; i < 16; ++i) {
+            mMat[i] = result.mMat[i] * det;
+        }
+
+        return true;
+    }
+
+    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..d36b2f1
--- /dev/null
+++ b/graphics/java/android/renderscript/Mesh.java
@@ -0,0 +1,479 @@
+/*
+ * 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(id, rs);
+    }
+
+    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, idxCount);
+
+        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..ffcdbbc5 100644
--- a/graphics/java/android/renderscript/Program.java
+++ b/graphics/java/android/renderscript/Program.java
@@ -17,6 +17,11 @@
 package android.renderscript;
 
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import android.content.res.Resources;
 import android.util.Config;
 import android.util.Log;
 
@@ -38,8 +43,7 @@
     String mShader;
 
     Program(int id, RenderScript rs) {
-        super(rs);
-        mID = id;
+        super(id, rs);
     }
 
     public void bindConstants(Allocation a, int slot) {
@@ -91,8 +95,47 @@
             mTextureCount = 0;
         }
 
-        public void setShader(String s) {
+        public BaseProgramBuilder setShader(String s) {
             mShader = s;
+            return this;
+        }
+
+        public BaseProgramBuilder setShader(Resources resources, int resourceID) {
+            byte[] str;
+            int strLength;
+            InputStream is = resources.openRawResource(resourceID);
+            try {
+                try {
+                    str = new byte[1024];
+                    strLength = 0;
+                    while(true) {
+                        int bytesLeft = str.length - strLength;
+                        if (bytesLeft == 0) {
+                            byte[] buf2 = new byte[str.length * 2];
+                            System.arraycopy(str, 0, buf2, 0, str.length);
+                            str = buf2;
+                            bytesLeft = str.length - strLength;
+                        }
+                        int bytesRead = is.read(str, strLength, bytesLeft);
+                        if (bytesRead <= 0) {
+                            break;
+                        }
+                        strLength += bytesRead;
+                    }
+                } finally {
+                    is.close();
+                }
+            } catch(IOException e) {
+                throw new Resources.NotFoundException();
+            }
+
+            try {
+                mShader = new String(str, 0, strLength, "UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                Log.e("Renderscript shader creation", "Could not decode shader string");
+            }
+
+            return this;
         }
 
         public void addInput(Element e) throws IllegalStateException {
@@ -111,6 +154,13 @@
             mOutputs[mOutputCount++] = e;
         }
 
+        void resetConstant() {
+            mConstantCount = 0;
+            for(int i = 0; i < MAX_CONSTANT; i ++) {
+                mConstants[i] = null;
+            }
+        }
+
         public int addConstant(Type t) throws IllegalStateException {
             // Should check for consistant and non-conflicting names...
             if(mConstantCount >= MAX_CONSTANT) {
@@ -120,12 +170,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) {
+            if(count >= MAX_TEXTURE) {
                 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..8858b74 100644
--- a/graphics/java/android/renderscript/ProgramFragment.java
+++ b/graphics/java/android/renderscript/ProgramFragment.java
@@ -55,17 +55,18 @@
             tmp[idx++] = 3;
             tmp[idx++] = mTextureCount;
 
-            int id = mRS.nProgramFragmentCreate2(mShader, tmp);
+            int id = mRS.nProgramFragmentCreate(mShader, tmp);
             ProgramFragment pf = new ProgramFragment(id, mRS);
             initProgram(pf);
             return pf;
         }
     }
 
-    public static class Builder {
+    public static class Builder extends ShaderBuilder {
         public static final int MAX_TEXTURE = 2;
-        RenderScript mRS;
+        int mNumTextures;
         boolean mPointSpriteEnable;
+        boolean mVaryingColorEnable;
 
         public enum EnvMode {
             REPLACE (1),
@@ -100,39 +101,126 @@
         }
         Slot[] mSlots;
 
+        private void buildShaderString() {
+            mShader  = "//rs_shader_internal\n";
+            mShader += "varying lowp vec4 varColor;\n";
+            mShader += "varying vec2 varTex0;\n";
+
+            mShader += "void main() {\n";
+            if (mVaryingColorEnable) {
+                mShader += "  lowp vec4 col = varColor;\n";
+            } else {
+                mShader += "  lowp vec4 col = UNI_Color;\n";
+            }
+
+            if (mNumTextures != 0) {
+                if (mPointSpriteEnable) {
+                    mShader += "  vec2 t0 = gl_PointCoord;\n";
+                } else {
+                    mShader += "  vec2 t0 = varTex0.xy;\n";
+                }
+            }
+
+            for(int i = 0; i < mNumTextures; i ++) {
+                switch(mSlots[i].env) {
+                case REPLACE:
+                    switch (mSlots[i].format) {
+                    case ALPHA:
+                        mShader += "  col.a = texture2D(UNI_Tex0, t0).a;\n";
+                        break;
+                    case LUMINANCE_ALPHA:
+                        mShader += "  col.rgba = texture2D(UNI_Tex0, t0).rgba;\n";
+                        break;
+                    case RGB:
+                        mShader += "  col.rgb = texture2D(UNI_Tex0, t0).rgb;\n";
+                        break;
+                    case RGBA:
+                        mShader += "  col.rgba = texture2D(UNI_Tex0, t0).rgba;\n";
+                        break;
+                    }
+                    break;
+                case MODULATE:
+                    switch (mSlots[i].format) {
+                    case ALPHA:
+                        mShader += "  col.a *= texture2D(UNI_Tex0, t0).a;\n";
+                        break;
+                    case LUMINANCE_ALPHA:
+                        mShader += "  col.rgba *= texture2D(UNI_Tex0, t0).rgba;\n";
+                        break;
+                    case RGB:
+                        mShader += "  col.rgb *= texture2D(UNI_Tex0, t0).rgb;\n";
+                        break;
+                    case RGBA:
+                        mShader += "  col.rgba *= texture2D(UNI_Tex0, t0).rgba;\n";
+                        break;
+                    }
+                    break;
+                case DECAL:
+                    mShader += "  col = texture2D(UNI_Tex0, t0);\n";
+                    break;
+                }
+            }
+
+            mShader += "  gl_FragColor = col;\n";
+            mShader += "}\n";
+        }
+
         public Builder(RenderScript rs) {
+            super(rs);
             mRS = rs;
             mSlots = new Slot[MAX_TEXTURE];
             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;
+        }
+
+        @Override
         public ProgramFragment create() {
-            mRS.validate();
-            int[] tmp = new int[MAX_TEXTURE * 2 + 1];
-            if (mSlots[0] != null) {
-                tmp[0] = mSlots[0].env.mID;
-                tmp[1] = mSlots[0].format.mID;
+            mNumTextures = 0;
+            for(int i = 0; i < MAX_TEXTURE; i ++) {
+                if(mSlots[i] != null) {
+                    mNumTextures ++;
+                }
             }
-            if (mSlots[1] != null) {
-                tmp[2] = mSlots[1].env.mID;
-                tmp[3] = mSlots[1].format.mID;
+            resetConstant();
+            buildShaderString();
+            Type constType = null;
+            if (!mVaryingColorEnable) {
+                Element.Builder b = new Element.Builder(mRS);
+                b.add(Element.F32_4(mRS), "Color");
+                Type.Builder typeBuilder = new Type.Builder(mRS, b.create());
+                typeBuilder.add(Dimension.X, 1);
+                constType = typeBuilder.create();
+                addConstant(constType);
             }
-            tmp[4] = mPointSpriteEnable ? 1 : 0;
-            int id = mRS.nProgramFragmentCreate(tmp);
-            ProgramFragment pf = new ProgramFragment(id, mRS);
+            setTextureCount(mNumTextures);
+
+            ProgramFragment pf = super.create();
             pf.mTextureCount = MAX_TEXTURE;
+            if (!mVaryingColorEnable) {
+                Allocation constantData = Allocation.createTyped(mRS,constType);
+                float[] data = new float[4];
+                data[0] = data[1] = data[2] = data[3] = 1.0f;
+                constantData.data(data);
+                pf.bindConstants(constantData, 0);
+            }
             return pf;
         }
     }
diff --git a/graphics/java/android/renderscript/ProgramRaster.java b/graphics/java/android/renderscript/ProgramRaster.java
index 56f9bf4..791dac8 100644
--- a/graphics/java/android/renderscript/ProgramRaster.java
+++ b/graphics/java/android/renderscript/ProgramRaster.java
@@ -26,76 +26,113 @@
  *
  **/
 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;
+        super(id, rs);
 
-        mPointSize = 1.0f;
         mLineWidth = 1.0f;
         mPointSmooth = false;
         mLineSmooth = false;
         mPointSprite = false;
+
+        mCullMode = CullMode.BACK;
     }
 
-    public void setLineWidth(float w) {
+    void setLineWidth(float w) {
         mRS.validate();
         mLineWidth = w;
         mRS.nProgramRasterSetLineWidth(mID, w);
     }
 
-    public void setPointSize(float s) {
+    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;
+    public static ProgramRaster CULL_BACK(RenderScript rs) {
+        if(rs.mProgramRaster_CULL_BACK == null) {
+            ProgramRaster.Builder builder = new ProgramRaster.Builder(rs);
+            builder.setCullMode(CullMode.BACK);
+            rs.mProgramRaster_CULL_BACK = builder.create();
         }
-        if (mOut != null) {
-            outID = mOut.mID;
-        }
-        mID = mRS.nProgramRasterCreate(inID, outID, mPointSmooth, mLineSmooth, mPointSprite);
+        return rs.mProgramRaster_CULL_BACK;
     }
 
+    public static ProgramRaster CULL_FRONT(RenderScript rs) {
+        if(rs.mProgramRaster_CULL_FRONT == null) {
+            ProgramRaster.Builder builder = new ProgramRaster.Builder(rs);
+            builder.setCullMode(CullMode.FRONT);
+            rs.mProgramRaster_CULL_FRONT = builder.create();
+        }
+        return rs.mProgramRaster_CULL_FRONT;
+    }
+
+    public static ProgramRaster CULL_NONE(RenderScript rs) {
+        if(rs.mProgramRaster_CULL_NONE == null) {
+            ProgramRaster.Builder builder = new ProgramRaster.Builder(rs);
+            builder.setCullMode(CullMode.NONE);
+            rs.mProgramRaster_CULL_NONE = builder.create();
+        }
+        return rs.mProgramRaster_CULL_NONE;
+    }
 
     public static class Builder {
         RenderScript mRS;
-        ProgramRaster mPR;
+        boolean mPointSprite;
+        boolean mPointSmooth;
+        boolean mLineSmooth;
+        CullMode mCullMode;
 
-        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;
+            mCullMode = CullMode.BACK;
         }
 
-        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;
         }
 
+        public Builder setCullMode(CullMode m) {
+            mCullMode = m;
+            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);
+            pr.setCullMode(b.mCullMode);
             return pr;
         }
 
@@ -111,3 +148,4 @@
 
 
 
+
diff --git a/graphics/java/android/renderscript/ProgramStore.java b/graphics/java/android/renderscript/ProgramStore.java
index 69be245..32c0d01 100644
--- a/graphics/java/android/renderscript/ProgramStore.java
+++ b/graphics/java/android/renderscript/ProgramStore.java
@@ -76,11 +76,143 @@
 
 
     ProgramStore(int id, RenderScript rs) {
-        super(rs);
-        mID = id;
+        super(id, rs);
     }
 
+    public static ProgramStore BLEND_NONE_DEPTH_TEST(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_NONE_DEPTH_TEST == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.LESS);
+            builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ZERO);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(true);
+            rs.mProgramStore_BLEND_NONE_DEPTH_TEST = builder.create();
+        }
+        return rs.mProgramStore_BLEND_NONE_DEPTH_TEST;
+    }
+    public static ProgramStore BLEND_NONE_DEPTH_NO_DEPTH(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_NONE_DEPTH_NO_DEPTH == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+            builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ZERO);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(false);
+            rs.mProgramStore_BLEND_NONE_DEPTH_NO_DEPTH = builder.create();
+        }
+        return rs.mProgramStore_BLEND_NONE_DEPTH_NO_DEPTH;
+    }
+    public static ProgramStore BLEND_NONE_DEPTH_NO_TEST(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_NONE_DEPTH_NO_TEST == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+            builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ZERO);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(true);
+            rs.mProgramStore_BLEND_NONE_DEPTH_NO_TEST = builder.create();
+        }
+        return rs.mProgramStore_BLEND_NONE_DEPTH_NO_TEST;
+    }
+    public static ProgramStore BLEND_NONE_DEPTH_NO_WRITE(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_NONE_DEPTH_NO_WRITE == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.LESS);
+            builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ZERO);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(false);
+            rs.mProgramStore_BLEND_NONE_DEPTH_NO_WRITE = builder.create();
+        }
+        return rs.mProgramStore_BLEND_NONE_DEPTH_NO_WRITE;
+    }
 
+    public static ProgramStore BLEND_ALPHA_DEPTH_TEST(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_ALPHA_DEPTH_TEST == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.LESS);
+            builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(true);
+            rs.mProgramStore_BLEND_ALPHA_DEPTH_TEST = builder.create();
+        }
+        return rs.mProgramStore_BLEND_ALPHA_DEPTH_TEST;
+    }
+    public static ProgramStore BLEND_ALPHA_DEPTH_NO_DEPTH(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_ALPHA_DEPTH_NO_DEPTH == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+            builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(false);
+            rs.mProgramStore_BLEND_ALPHA_DEPTH_NO_DEPTH = builder.create();
+        }
+        return rs.mProgramStore_BLEND_ALPHA_DEPTH_NO_DEPTH;
+    }
+    public static ProgramStore BLEND_ALPHA_DEPTH_NO_TEST(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_ALPHA_DEPTH_NO_TEST == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+            builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(true);
+            rs.mProgramStore_BLEND_ALPHA_DEPTH_NO_TEST = builder.create();
+        }
+        return rs.mProgramStore_BLEND_ALPHA_DEPTH_NO_TEST;
+    }
+    public static ProgramStore BLEND_ALPHA_DEPTH_NO_WRITE(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_ALPHA_DEPTH_NO_WRITE == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.LESS);
+            builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(false);
+            rs.mProgramStore_BLEND_ALPHA_DEPTH_NO_WRITE = builder.create();
+        }
+        return rs.mProgramStore_BLEND_ALPHA_DEPTH_NO_WRITE;
+    }
+
+    public static ProgramStore BLEND_ADD_DEPTH_TEST(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_ADD_DEPTH_TEST == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.LESS);
+            builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(true);
+            rs.mProgramStore_BLEND_ADD_DEPTH_TEST = builder.create();
+        }
+        return rs.mProgramStore_BLEND_ADD_DEPTH_TEST;
+    }
+    public static ProgramStore BLEND_ADD_DEPTH_NO_DEPTH(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_ADD_DEPTH_NO_DEPTH == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+            builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(false);
+            rs.mProgramStore_BLEND_ADD_DEPTH_NO_DEPTH = builder.create();
+        }
+        return rs.mProgramStore_BLEND_ADD_DEPTH_NO_DEPTH;
+    }
+    public static ProgramStore BLEND_ADD_DEPTH_NO_TEST(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_ADD_DEPTH_NO_TEST == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+            builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(true);
+            rs.mProgramStore_BLEND_ADD_DEPTH_NO_DEPTH = builder.create();
+        }
+        return rs.mProgramStore_BLEND_ADD_DEPTH_NO_TEST;
+    }
+    public static ProgramStore BLEND_ADD_DEPTH_NO_WRITE(RenderScript rs) {
+        if(rs.mProgramStore_BLEND_ADD_DEPTH_NO_WRITE == null) {
+            ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+            builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+            builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE);
+            builder.setDitherEnable(false);
+            builder.setDepthMask(false);
+            rs.mProgramStore_BLEND_ADD_DEPTH_NO_WRITE = builder.create();
+        }
+        return rs.mProgramStore_BLEND_ADD_DEPTH_NO_WRITE;
+    }
 
     public static class Builder {
         RenderScript mRS;
@@ -110,32 +242,49 @@
             mColorMaskA = true;
             mBlendSrc = BlendSrcFunc.ONE;
             mBlendDst = BlendDstFunc.ZERO;
-
-
         }
 
-        public void setDepthFunc(DepthFunc func) {
+        public Builder(RenderScript rs) {
+            mRS = rs;
+            mIn = null;
+            mOut = null;
+            mDepthFunc = DepthFunc.ALWAYS;
+            mDepthMask = false;
+            mColorMaskR = true;
+            mColorMaskG = true;
+            mColorMaskB = true;
+            mColorMaskA = true;
+            mBlendSrc = BlendSrcFunc.ONE;
+            mBlendDst = BlendDstFunc.ZERO;
+        }
+
+        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 +296,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..65a0af2 100644
--- a/graphics/java/android/renderscript/ProgramVertex.java
+++ b/graphics/java/android/renderscript/ProgramVertex.java
@@ -17,6 +17,7 @@
 package android.renderscript;
 
 
+import android.graphics.Matrix;
 import android.util.Config;
 import android.util.Log;
 
@@ -38,25 +39,6 @@
         bindConstants(va.mAlloc, 0);
     }
 
-
-    public static class Builder {
-        RenderScript mRS;
-        boolean mTextureMatrixEnable;
-
-        public Builder(RenderScript rs, Element in, Element out) {
-            mRS = rs;
-        }
-
-        public void setTextureMatrixEnable(boolean enable) {
-            mTextureMatrixEnable = enable;
-        }
-
-        public ProgramVertex create() {
-            int id = mRS.nProgramVertexCreate(mTextureMatrixEnable);
-            return new ProgramVertex(id, mRS);
-        }
-    }
-
     public static class ShaderBuilder extends BaseProgramBuilder {
         public ShaderBuilder(RenderScript rs) {
             super(rs);
@@ -82,13 +64,75 @@
             tmp[idx++] = 3;
             tmp[idx++] = mTextureCount;
 
-            int id = mRS.nProgramVertexCreate2(mShader, tmp);
+            int id = mRS.nProgramVertexCreate(mShader, tmp);
             ProgramVertex pv = new ProgramVertex(id, mRS);
             initProgram(pv);
             return pv;
         }
     }
 
+    public static class Builder extends ShaderBuilder {
+        boolean mTextureMatrixEnable;
+
+        public Builder(RenderScript rs, Element in, Element out) {
+            super(rs);
+        }
+        public Builder(RenderScript rs) {
+            super(rs);
+        }
+
+        public Builder setTextureMatrixEnable(boolean enable) {
+            mTextureMatrixEnable = enable;
+            return this;
+        }
+        static Type getConstantInputType(RenderScript rs) {
+            Element.Builder b = new Element.Builder(rs);
+            b.add(Element.MATRIX4X4(rs), "MV");
+            b.add(Element.MATRIX4X4(rs), "P");
+            b.add(Element.MATRIX4X4(rs), "TexMatrix");
+            b.add(Element.MATRIX4X4(rs), "MVP");
+
+            Type.Builder typeBuilder = new Type.Builder(rs, b.create());
+            typeBuilder.add(Dimension.X, 1);
+            return typeBuilder.create();
+        }
+
+        private void buildShaderString() {
+
+            mShader  = "//rs_shader_internal\n";
+            mShader += "varying vec4 varColor;\n";
+            mShader += "varying vec2 varTex0;\n";
+
+            mShader += "void main() {\n";
+            mShader += "  gl_Position = UNI_MVP * ATTRIB_position;\n";
+            mShader += "  gl_PointSize = 1.0;\n";
+
+            mShader += "  varColor = ATTRIB_color;\n";
+            if (mTextureMatrixEnable) {
+                mShader += "  varTex0 = (UNI_TexMatrix * vec4(ATTRIB_texture0, 0.0, 1.0)).xy;\n";
+            } else {
+                mShader += "  varTex0 = ATTRIB_texture0;\n";
+            }
+            mShader += "}\n";
+        }
+
+        @Override
+        public ProgramVertex create() {
+            buildShaderString();
+
+            addConstant(getConstantInputType(mRS));
+
+            Element.Builder b = new Element.Builder(mRS);
+            b.add(Element.F32_4(mRS), "position");
+            b.add(Element.F32_4(mRS), "color");
+            b.add(Element.F32_3(mRS), "normal");
+            b.add(Element.F32_2(mRS), "texture0");
+            addInput(b.create());
+
+            return super.create();
+        }
+    }
+
 
 
     public static class MatrixAllocation {
@@ -101,16 +145,17 @@
         Matrix4f mTexture;
 
         public Allocation mAlloc;
+        private FieldPacker mIOBuffer;
 
         public MatrixAllocation(RenderScript rs) {
-            mModel = new Matrix4f();
-            mProjection = new Matrix4f();
-            mTexture = new Matrix4f();
-
-            mAlloc = Allocation.createSized(rs, Element.createUser(rs, Element.DataType.FLOAT_32), 48);
-            mAlloc.subData1D(MODELVIEW_OFFSET, 16, mModel.mMat);
-            mAlloc.subData1D(PROJECTION_OFFSET, 16, mProjection.mMat);
-            mAlloc.subData1D(TEXTURE_OFFSET, 16, mTexture.mMat);
+            Type constInputType = ProgramVertex.Builder.getConstantInputType(rs);
+            mAlloc = Allocation.createTyped(rs, constInputType);
+            int bufferSize = constInputType.getElement().getSizeBytes()*
+                             constInputType.getElementCount();
+            mIOBuffer = new FieldPacker(bufferSize);
+            loadModelview(new Matrix4f());
+            loadProjection(new Matrix4f());
+            loadTexture(new Matrix4f());
         }
 
         public void destroy() {
@@ -118,24 +163,32 @@
             mAlloc = null;
         }
 
+        private void addToBuffer(int offset, Matrix4f m) {
+            mIOBuffer.reset(offset);
+            for(int i = 0; i < 16; i ++) {
+                mIOBuffer.addF32(m.mMat[i]);
+            }
+            mAlloc.data(mIOBuffer.getData());
+        }
+
         public void loadModelview(Matrix4f m) {
             mModel = m;
-            mAlloc.subData1D(MODELVIEW_OFFSET, 16, m.mMat);
+            addToBuffer(MODELVIEW_OFFSET*4, m);
         }
 
         public void loadProjection(Matrix4f m) {
             mProjection = m;
-            mAlloc.subData1D(PROJECTION_OFFSET, 16, m.mMat);
+            addToBuffer(PROJECTION_OFFSET*4, m);
         }
 
         public void loadTexture(Matrix4f m) {
             mTexture = m;
-            mAlloc.subData1D(TEXTURE_OFFSET, 16, m.mMat);
+            addToBuffer(TEXTURE_OFFSET*4, m);
         }
 
         public void setupOrthoWindow(int w, int h) {
             mProjection.loadOrtho(0,w, h,0, -1,1);
-            mAlloc.subData1D(PROJECTION_OFFSET, 16, mProjection.mMat);
+            addToBuffer(PROJECTION_OFFSET*4, mProjection);
         }
 
         public void setupOrthoNormalized(int w, int h) {
@@ -147,7 +200,7 @@
                 float aspect = ((float)h) / w;
                 mProjection.loadOrtho(-1,1, -aspect,aspect,  -1,1);
             }
-            mAlloc.subData1D(PROJECTION_OFFSET, 16, mProjection.mMat);
+            addToBuffer(PROJECTION_OFFSET*4, mProjection);
         }
 
         public void setupProjectionNormalized(int w, int h) {
@@ -173,7 +226,7 @@
             m1.loadMultiply(m1, m2);
 
             mProjection = m1;
-            mAlloc.subData1D(PROJECTION_OFFSET, 16, mProjection.mMat);
+            addToBuffer(PROJECTION_OFFSET*4, mProjection);
         }
 
     }
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index a935243..0088373 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -57,151 +57,474 @@
         }
     }
 
+    // Non-threadsafe functions.
     native void nInitElements(int a8, int rgba4444, int rgba8888, int rgb565);
-
     native int  nDeviceCreate();
     native void nDeviceDestroy(int dev);
     native void nDeviceSetConfig(int dev, int param, int value);
-    native int  nContextCreateGL(int dev, int ver, boolean useDepth);
-    native int  nContextCreate(int dev, int ver);
-    native void nContextDestroy(int con);
-    native void nContextSetSurface(int w, int h, Surface sur);
-    native void nContextSetPriority(int p);
-    native void nContextDump(int bits);
-
-    native void nContextBindRootScript(int script);
-    native void nContextBindSampler(int sampler, int slot);
-    native void nContextBindProgramFragmentStore(int pfs);
-    native void nContextBindProgramFragment(int pf);
-    native void nContextBindProgramVertex(int pf);
-    native void nContextBindProgramRaster(int pr);
-    native void nContextPause();
-    native void nContextResume();
-    native int nContextGetMessage(int[] data, boolean wait);
-    native void nContextInitToClient();
-    native void nContextDeinitToClient();
-
-    native void nAssignName(int obj, byte[] name);
-    native void nObjDestroy(int id);
-    native void nObjDestroyOOB(int id);
-    native int  nFileOpen(byte[] name);
+    native int  nContextGetMessage(int con, int[] data, boolean wait);
+    native void nContextInitToClient(int con);
+    native void nContextDeinitToClient(int con);
 
 
-    native int  nElementCreate(int type, int kind, boolean norm, int vecSize);
-    native int  nElementCreate2(int[] elements, String[] names);
+    // Methods below are wrapped to protect the non-threadsafe
+    // lockless fifo.
+    native int  rsnContextCreateGL(int dev, int ver, boolean useDepth);
+    synchronized int nContextCreateGL(int dev, int ver, boolean useDepth) {
+        return rsnContextCreateGL(dev, ver, useDepth);
+    }
+    native int  rsnContextCreate(int dev, int ver);
+    synchronized int nContextCreate(int dev, int ver) {
+        return rsnContextCreate(dev, ver);
+    }
+    native void rsnContextDestroy(int con);
+    synchronized void nContextDestroy() {
+        rsnContextDestroy(mContext);
+    }
+    native void rsnContextSetSurface(int con, int w, int h, Surface sur);
+    synchronized void nContextSetSurface(int w, int h, Surface sur) {
+        rsnContextSetSurface(mContext, w, h, sur);
+    }
+    native void rsnContextSetPriority(int con, int p);
+    synchronized void nContextSetPriority(int p) {
+        rsnContextSetPriority(mContext, p);
+    }
+    native void rsnContextDump(int con, int bits);
+    synchronized void nContextDump(int bits) {
+        rsnContextDump(mContext, bits);
+    }
+    native void rsnContextFinish(int con);
+    synchronized void nContextFinish() {
+        rsnContextFinish(mContext);
+    }
 
-    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 rsnContextBindRootScript(int con, int script);
+    synchronized void nContextBindRootScript(int script) {
+        rsnContextBindRootScript(mContext, script);
+    }
+    native void rsnContextBindSampler(int con, int sampler, int slot);
+    synchronized void nContextBindSampler(int sampler, int slot) {
+        rsnContextBindSampler(mContext, sampler, slot);
+    }
+    native void rsnContextBindProgramStore(int con, int pfs);
+    synchronized void nContextBindProgramStore(int pfs) {
+        rsnContextBindProgramStore(mContext, pfs);
+    }
+    native void rsnContextBindProgramFragment(int con, int pf);
+    synchronized void nContextBindProgramFragment(int pf) {
+        rsnContextBindProgramFragment(mContext, pf);
+    }
+    native void rsnContextBindProgramVertex(int con, int pv);
+    synchronized void nContextBindProgramVertex(int pv) {
+        rsnContextBindProgramVertex(mContext, pv);
+    }
+    native void rsnContextBindProgramRaster(int con, int pr);
+    synchronized void nContextBindProgramRaster(int pr) {
+        rsnContextBindProgramRaster(mContext, pr);
+    }
+    native void rsnContextPause(int con);
+    synchronized void nContextPause() {
+        rsnContextPause(mContext);
+    }
+    native void rsnContextResume(int con);
+    synchronized void nContextResume() {
+        rsnContextResume(mContext);
+    }
 
-    native int  nAllocationCreateTyped(int type);
-    native int  nAllocationCreateFromBitmap(int dstFmt, boolean genMips, Bitmap bmp);
-    native int  nAllocationCreateBitmapRef(int type, Bitmap bmp);
-    native int  nAllocationCreateFromBitmapBoxed(int dstFmt, boolean genMips, Bitmap bmp);
-    native int  nAllocationCreateFromAssetStream(int dstFmt, boolean genMips, int assetStream);
+    native void rsnAssignName(int con, int obj, byte[] name);
+    synchronized void nAssignName(int obj, byte[] name) {
+        rsnAssignName(mContext, obj, name);
+    }
+    native String rsnGetName(int con, int obj);
+    synchronized String nGetName(int obj) {
+        return rsnGetName(mContext, obj);
+    }
+    native void rsnObjDestroy(int con, int id);
+    synchronized void nObjDestroy(int id) {
+        rsnObjDestroy(mContext, id);
+    }
+    native int  rsnFileOpen(int con, byte[] name);
+    synchronized int nFileOpen(byte[] name) {
+        return rsnFileOpen(mContext, name);
+    }
 
-    native void nAllocationUploadToTexture(int alloc, boolean genMips, int baseMioLevel);
-    native void nAllocationUploadToBufferObject(int alloc);
+    native int  rsnElementCreate(int con, int type, int kind, boolean norm, int vecSize);
+    synchronized int nElementCreate(int type, int kind, boolean norm, int vecSize) {
+        return rsnElementCreate(mContext, type, kind, norm, vecSize);
+    }
+    native int  rsnElementCreate2(int con, int[] elements, String[] names, int[] arraySizes);
+    synchronized int nElementCreate2(int[] elements, String[] names, int[] arraySizes) {
+        return rsnElementCreate2(mContext, elements, names, arraySizes);
+    }
+    native void rsnElementGetNativeData(int con, int id, int[] elementData);
+    synchronized void nElementGetNativeData(int id, int[] elementData) {
+        rsnElementGetNativeData(mContext, id, elementData);
+    }
+    native void rsnElementGetSubElements(int con, int id, int[] IDs, String[] names);
+    synchronized void nElementGetSubElements(int id, int[] IDs, String[] names) {
+        rsnElementGetSubElements(mContext, id, IDs, names);
+    }
 
-    native void nAllocationSubData1D(int id, int off, int count, int[] d, int sizeBytes);
-    native void nAllocationSubData1D(int id, int off, int count, short[] d, int sizeBytes);
-    native void nAllocationSubData1D(int id, int off, int count, byte[] d, int sizeBytes);
-    native void nAllocationSubData1D(int id, int off, int count, float[] d, int sizeBytes);
+    native void rsnTypeBegin(int con, int elementID);
+    synchronized void nTypeBegin(int elementID) {
+        rsnTypeBegin(mContext, elementID);
+    }
+    native void rsnTypeAdd(int con, int dim, int val);
+    synchronized void nTypeAdd(int dim, int val) {
+        rsnTypeAdd(mContext, dim, val);
+    }
+    native int  rsnTypeCreate(int con);
+    synchronized int nTypeCreate() {
+        return rsnTypeCreate(mContext);
+    }
+    native void rsnTypeGetNativeData(int con, int id, int[] typeData);
+    synchronized void nTypeGetNativeData(int id, int[] typeData) {
+        rsnTypeGetNativeData(mContext, id, typeData);
+    }
 
-    native void nAllocationSubData2D(int id, int xoff, int yoff, int w, int h, int[] d, int sizeBytes);
-    native void nAllocationSubData2D(int id, int xoff, int yoff, int w, int h, float[] d, int sizeBytes);
-    native void nAllocationRead(int id, int[] d);
-    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  rsnAllocationCreateTyped(int con, int type);
+    synchronized int nAllocationCreateTyped(int type) {
+        return rsnAllocationCreateTyped(mContext, type);
+    }
+    native int  rsnAllocationCreateFromBitmap(int con, int dstFmt, boolean genMips, Bitmap bmp);
+    synchronized int nAllocationCreateFromBitmap(int dstFmt, boolean genMips, Bitmap bmp) {
+        return rsnAllocationCreateFromBitmap(mContext, dstFmt, genMips, bmp);
+    }
+    native int  rsnAllocationCreateBitmapRef(int con, int type, Bitmap bmp);
+    synchronized int nAllocationCreateBitmapRef(int type, Bitmap bmp) {
+        return rsnAllocationCreateBitmapRef(mContext, type, bmp);
+    }
+    native int  rsnAllocationCreateFromBitmapBoxed(int con, int dstFmt, boolean genMips, Bitmap bmp);
+    synchronized int nAllocationCreateFromBitmapBoxed(int dstFmt, boolean genMips, Bitmap bmp) {
+        return rsnAllocationCreateFromBitmapBoxed(mContext, dstFmt, genMips, bmp);
+    }
+    native int  rsnAllocationCreateFromAssetStream(int con, int dstFmt, boolean genMips, int assetStream);
+    synchronized int nAllocationCreateFromAssetStream(int dstFmt, boolean genMips, int assetStream) {
+        return rsnAllocationCreateFromAssetStream(mContext, dstFmt, genMips, assetStream);
+    }
 
-    native void nAdapter1DBindAllocation(int ad, int alloc);
-    native void nAdapter1DSetConstraint(int ad, int dim, int value);
-    native void nAdapter1DData(int ad, int[] d);
-    native void nAdapter1DData(int ad, float[] d);
-    native void nAdapter1DSubData(int ad, int off, int count, int[] d);
-    native void nAdapter1DSubData(int ad, int off, int count, float[] d);
-    native int  nAdapter1DCreate();
+    native void rsnAllocationUploadToTexture(int con, int alloc, boolean genMips, int baseMioLevel);
+    synchronized void nAllocationUploadToTexture(int alloc, boolean genMips, int baseMioLevel) {
+        rsnAllocationUploadToTexture(mContext, alloc, genMips, baseMioLevel);
+    }
+    native void rsnAllocationUploadToBufferObject(int con, int alloc);
+    synchronized void nAllocationUploadToBufferObject(int alloc) {
+        rsnAllocationUploadToBufferObject(mContext, alloc);
+    }
 
-    native void nAdapter2DBindAllocation(int ad, int alloc);
-    native void nAdapter2DSetConstraint(int ad, int dim, int value);
-    native void nAdapter2DData(int ad, int[] d);
-    native void nAdapter2DData(int ad, float[] d);
-    native void nAdapter2DSubData(int ad, int xoff, int yoff, int w, int h, int[] d);
-    native void nAdapter2DSubData(int ad, int xoff, int yoff, int w, int h, float[] d);
-    native int  nAdapter2DCreate();
+    native void rsnAllocationSubData1D(int con, int id, int off, int count, int[] d, int sizeBytes);
+    synchronized void nAllocationSubData1D(int id, int off, int count, int[] d, int sizeBytes) {
+        rsnAllocationSubData1D(mContext, id, off, count, d, sizeBytes);
+    }
+    native void rsnAllocationSubData1D(int con, int id, int off, int count, short[] d, int sizeBytes);
+    synchronized void nAllocationSubData1D(int id, int off, int count, short[] d, int sizeBytes) {
+        rsnAllocationSubData1D(mContext, id, off, count, d, sizeBytes);
+    }
+    native void rsnAllocationSubData1D(int con, int id, int off, int count, byte[] d, int sizeBytes);
+    synchronized void nAllocationSubData1D(int id, int off, int count, byte[] d, int sizeBytes) {
+        rsnAllocationSubData1D(mContext, id, off, count, d, sizeBytes);
+    }
+    native void rsnAllocationSubElementData1D(int con, int id, int xoff, int compIdx, byte[] d, int sizeBytes);
+    synchronized void nAllocationSubElementData1D(int id, int xoff, int compIdx, byte[] d, int sizeBytes) {
+        rsnAllocationSubElementData1D(mContext, id, xoff, compIdx, d, sizeBytes);
+    }
+    native void rsnAllocationSubData1D(int con, int id, int off, int count, float[] d, int sizeBytes);
+    synchronized void nAllocationSubData1D(int id, int off, int count, float[] d, int sizeBytes) {
+        rsnAllocationSubData1D(mContext, id, off, count, d, sizeBytes);
+    }
 
-    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 rsnAllocationSubData2D(int con, int id, int xoff, int yoff, int w, int h, int[] d, int sizeBytes);
+    synchronized void nAllocationSubData2D(int id, int xoff, int yoff, int w, int h, int[] d, int sizeBytes) {
+        rsnAllocationSubData2D(mContext, id, xoff, yoff, w, h, d, sizeBytes);
+    }
+    native void rsnAllocationSubData2D(int con, int id, int xoff, int yoff, int w, int h, float[] d, int sizeBytes);
+    synchronized void nAllocationSubData2D(int id, int xoff, int yoff, int w, int h, float[] d, int sizeBytes) {
+        rsnAllocationSubData2D(mContext, id, xoff, yoff, w, h, d, sizeBytes);
+    }
+    native void rsnAllocationRead(int con, int id, int[] d);
+    synchronized void nAllocationRead(int id, int[] d) {
+        rsnAllocationRead(mContext, id, d);
+    }
+    native void rsnAllocationRead(int con, int id, float[] d);
+    synchronized void nAllocationRead(int id, float[] d) {
+        rsnAllocationRead(mContext, id, d);
+    }
+    native int  rsnAllocationGetType(int con, int id);
+    synchronized int nAllocationGetType(int id) {
+        return rsnAllocationGetType(mContext, id);
+    }
 
-    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 rsnAllocationResize1D(int con, int id, int dimX);
+    synchronized void nAllocationResize1D(int id, int dimX) {
+        rsnAllocationResize1D(mContext, id, dimX);
+    }
+    native void rsnAllocationResize2D(int con, int id, int dimX, int dimY);
+    synchronized void nAllocationResize2D(int id, int dimX, int dimY) {
+        rsnAllocationResize2D(mContext, id, dimX, dimY);
+    }
 
-    native void nSamplerBegin();
-    native void nSamplerSet(int param, int value);
-    native int  nSamplerCreate();
+    native int  rsnFileA3DCreateFromAssetStream(int con, int assetStream);
+    synchronized int nFileA3DCreateFromAssetStream(int assetStream) {
+        return rsnFileA3DCreateFromAssetStream(mContext, assetStream);
+    }
+    native int  rsnFileA3DGetNumIndexEntries(int con, int fileA3D);
+    synchronized int nFileA3DGetNumIndexEntries(int fileA3D) {
+        return rsnFileA3DGetNumIndexEntries(mContext, fileA3D);
+    }
+    native void rsnFileA3DGetIndexEntries(int con, int fileA3D, int numEntries, int[] IDs, String[] names);
+    synchronized void nFileA3DGetIndexEntries(int fileA3D, int numEntries, int[] IDs, String[] names) {
+        rsnFileA3DGetIndexEntries(mContext, fileA3D, numEntries, IDs, names);
+    }
+    native int  rsnFileA3DGetEntryByIndex(int con, int fileA3D, int index);
+    synchronized int nFileA3DGetEntryByIndex(int fileA3D, int index) {
+        return rsnFileA3DGetEntryByIndex(mContext, fileA3D, index);
+    }
 
-    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 int  rsnFontCreateFromFile(int con, String fileName, int size, int dpi);
+    synchronized int nFontCreateFromFile(String fileName, int size, int dpi) {
+        return rsnFontCreateFromFile(mContext, fileName, size, dpi);
+    }
 
-    native int  nProgramRasterCreate(int in, int out, boolean pointSmooth, boolean lineSmooth, boolean pointSprite);
-    native void nProgramRasterSetLineWidth(int pr, float v);
-    native void nProgramRasterSetPointSize(int pr, float v);
+    native void rsnAdapter1DBindAllocation(int con, int ad, int alloc);
+    synchronized void nAdapter1DBindAllocation(int ad, int alloc) {
+        rsnAdapter1DBindAllocation(mContext, ad, alloc);
+    }
+    native void rsnAdapter1DSetConstraint(int con, int ad, int dim, int value);
+    synchronized void nAdapter1DSetConstraint(int ad, int dim, int value) {
+        rsnAdapter1DSetConstraint(mContext, ad, dim, value);
+    }
+    native void rsnAdapter1DData(int con, int ad, int[] d);
+    synchronized void nAdapter1DData(int ad, int[] d) {
+        rsnAdapter1DData(mContext, ad, d);
+    }
+    native void rsnAdapter1DData(int con, int ad, float[] d);
+    synchronized void nAdapter1DData(int ad, float[] d) {
+        rsnAdapter1DData(mContext, ad, d);
+    }
+    native void rsnAdapter1DSubData(int con, int ad, int off, int count, int[] d);
+    synchronized void nAdapter1DSubData(int ad, int off, int count, int[] d) {
+        rsnAdapter1DSubData(mContext, ad, off, count, d);
+    }
+    native void rsnAdapter1DSubData(int con, int ad, int off, int count, float[] d);
+    synchronized void nAdapter1DSubData(int ad, int off, int count, float[] d) {
+        rsnAdapter1DSubData(mContext, ad, off, count, d);
+    }
+    native int  rsnAdapter1DCreate(int con);
+    synchronized int nAdapter1DCreate() {
+        return rsnAdapter1DCreate(mContext);
+    }
 
-    native void nProgramBindConstants(int pv, int slot, int mID);
-    native void nProgramBindTexture(int vpf, int slot, int a);
-    native void nProgramBindSampler(int vpf, int slot, int s);
+    native void rsnAdapter2DBindAllocation(int con, int ad, int alloc);
+    synchronized void nAdapter2DBindAllocation(int ad, int alloc) {
+        rsnAdapter2DBindAllocation(mContext, ad, alloc);
+    }
+    native void rsnAdapter2DSetConstraint(int con, int ad, int dim, int value);
+    synchronized void nAdapter2DSetConstraint(int ad, int dim, int value) {
+        rsnAdapter2DSetConstraint(mContext, ad, dim, value);
+    }
+    native void rsnAdapter2DData(int con, int ad, int[] d);
+    synchronized void nAdapter2DData(int ad, int[] d) {
+        rsnAdapter2DData(mContext, ad, d);
+    }
+    native void rsnAdapter2DData(int con, int ad, float[] d);
+    synchronized void nAdapter2DData(int ad, float[] d) {
+        rsnAdapter2DData(mContext, ad, d);
+    }
+    native void rsnAdapter2DSubData(int con, int ad, int xoff, int yoff, int w, int h, int[] d);
+    synchronized void nAdapter2DSubData(int ad, int xoff, int yoff, int w, int h, int[] d) {
+        rsnAdapter2DSubData(mContext, ad, xoff, yoff, w, h, d);
+    }
+    native void rsnAdapter2DSubData(int con, int ad, int xoff, int yoff, int w, int h, float[] d);
+    synchronized void nAdapter2DSubData(int ad, int xoff, int yoff, int w, int h, float[] d) {
+        rsnAdapter2DSubData(mContext, ad, xoff, yoff, w, h, d);
+    }
+    native int  rsnAdapter2DCreate(int con);
+    synchronized int nAdapter2DCreate() {
+        return rsnAdapter2DCreate(mContext);
+    }
 
-    native int  nProgramFragmentCreate(int[] params);
-    native int  nProgramFragmentCreate2(String shader, int[] params);
+    native void rsnScriptBindAllocation(int con, int script, int alloc, int slot);
+    synchronized void nScriptBindAllocation(int script, int alloc, int slot) {
+        rsnScriptBindAllocation(mContext, script, alloc, slot);
+    }
+    native void rsnScriptSetTimeZone(int con, int script, byte[] timeZone);
+    synchronized void nScriptSetTimeZone(int script, byte[] timeZone) {
+        rsnScriptSetTimeZone(mContext, script, timeZone);
+    }
+    native void rsnScriptInvoke(int con, int id, int slot);
+    synchronized void nScriptInvoke(int id, int slot) {
+        rsnScriptInvoke(mContext, id, slot);
+    }
+    native void rsnScriptInvokeV(int con, int id, int slot, byte[] params);
+    synchronized void nScriptInvokeV(int id, int slot, byte[] params) {
+        rsnScriptInvokeV(mContext, id, slot, params);
+    }
+    native void rsnScriptSetVarI(int con, int id, int slot, int val);
+    synchronized void nScriptSetVarI(int id, int slot, int val) {
+        rsnScriptSetVarI(mContext, id, slot, val);
+    }
+    native void rsnScriptSetVarF(int con, int id, int slot, float val);
+    synchronized void nScriptSetVarF(int id, int slot, float val) {
+        rsnScriptSetVarF(mContext, id, slot, val);
+    }
+    native void rsnScriptSetVarD(int con, int id, int slot, double val);
+    synchronized void nScriptSetVarD(int id, int slot, double val) {
+        rsnScriptSetVarD(mContext, id, slot, val);
+    }
+    native void rsnScriptSetVarV(int con, int id, int slot, byte[] val);
+    synchronized void nScriptSetVarV(int id, int slot, byte[] val) {
+        rsnScriptSetVarV(mContext, id, slot, val);
+    }
 
-    native int  nProgramVertexCreate(boolean texMat);
-    native int  nProgramVertexCreate2(String shader, int[] params);
+    native void rsnScriptCBegin(int con);
+    synchronized void nScriptCBegin() {
+        rsnScriptCBegin(mContext);
+    }
+    native void rsnScriptCSetScript(int con, byte[] script, int offset, int length);
+    synchronized void nScriptCSetScript(byte[] script, int offset, int length) {
+        rsnScriptCSetScript(mContext, script, offset, length);
+    }
+    native int  rsnScriptCCreate(int con);
+    synchronized int nScriptCCreate() {
+        return rsnScriptCCreate(mContext);
+    }
 
-    native void nLightBegin();
-    native void nLightSetIsMono(boolean isMono);
-    native void nLightSetIsLocal(boolean isLocal);
-    native int  nLightCreate();
-    native void nLightSetColor(int l, float r, float g, float b);
-    native void nLightSetPosition(int l, float x, float y, float z);
+    native void rsnSamplerBegin(int con);
+    synchronized void nSamplerBegin() {
+        rsnSamplerBegin(mContext);
+    }
+    native void rsnSamplerSet(int con, int param, int value);
+    synchronized void nSamplerSet(int param, int value) {
+        rsnSamplerSet(mContext, param, value);
+    }
+    native void rsnSamplerSet2(int con, int param, float value);
+    synchronized void nSamplerSet2(int param, float value) {
+        rsnSamplerSet2(mContext, param, value);
+    }
+    native int  rsnSamplerCreate(int con);
+    synchronized int nSamplerCreate() {
+        return rsnSamplerCreate(mContext);
+    }
 
-    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 void rsnProgramStoreBegin(int con, int in, int out);
+    synchronized void nProgramStoreBegin(int in, int out) {
+        rsnProgramStoreBegin(mContext, in, out);
+    }
+    native void rsnProgramStoreDepthFunc(int con, int func);
+    synchronized void nProgramStoreDepthFunc(int func) {
+        rsnProgramStoreDepthFunc(mContext, func);
+    }
+    native void rsnProgramStoreDepthMask(int con, boolean enable);
+    synchronized void nProgramStoreDepthMask(boolean enable) {
+        rsnProgramStoreDepthMask(mContext, enable);
+    }
+    native void rsnProgramStoreColorMask(int con, boolean r, boolean g, boolean b, boolean a);
+    synchronized void nProgramStoreColorMask(boolean r, boolean g, boolean b, boolean a) {
+        rsnProgramStoreColorMask(mContext, r, g, b, a);
+    }
+    native void rsnProgramStoreBlendFunc(int con, int src, int dst);
+    synchronized void nProgramStoreBlendFunc(int src, int dst) {
+        rsnProgramStoreBlendFunc(mContext, src, dst);
+    }
+    native void rsnProgramStoreDither(int con, boolean enable);
+    synchronized void nProgramStoreDither(boolean enable) {
+        rsnProgramStoreDither(mContext, enable);
+    }
+    native int  rsnProgramStoreCreate(int con);
+    synchronized int nProgramStoreCreate() {
+        return rsnProgramStoreCreate(mContext);
+    }
 
-    native void nAnimationBegin(int attribCount, int keyframeCount);
-    native void nAnimationAdd(float time, float[] attribs);
-    native int  nAnimationCreate();
+    native int  rsnProgramRasterCreate(int con, boolean pointSmooth, boolean lineSmooth, boolean pointSprite);
+    synchronized int nProgramRasterCreate(boolean pointSmooth, boolean lineSmooth, boolean pointSprite) {
+        return rsnProgramRasterCreate(mContext, pointSmooth, lineSmooth, pointSprite);
+    }
+    native void rsnProgramRasterSetLineWidth(int con, int pr, float v);
+    synchronized void nProgramRasterSetLineWidth(int pr, float v) {
+        rsnProgramRasterSetLineWidth(mContext, pr, v);
+    }
+    native void rsnProgramRasterSetCullMode(int con, int pr, int mode);
+    synchronized void nProgramRasterSetCullMode(int pr, int mode) {
+        rsnProgramRasterSetCullMode(mContext, pr, mode);
+    }
+
+    native void rsnProgramBindConstants(int con, int pv, int slot, int mID);
+    synchronized void nProgramBindConstants(int pv, int slot, int mID) {
+        rsnProgramBindConstants(mContext, pv, slot, mID);
+    }
+    native void rsnProgramBindTexture(int con, int vpf, int slot, int a);
+    synchronized void nProgramBindTexture(int vpf, int slot, int a) {
+        rsnProgramBindTexture(mContext, vpf, slot, a);
+    }
+    native void rsnProgramBindSampler(int con, int vpf, int slot, int s);
+    synchronized void nProgramBindSampler(int vpf, int slot, int s) {
+        rsnProgramBindSampler(mContext, vpf, slot, s);
+    }
+    native int  rsnProgramFragmentCreate(int con, String shader, int[] params);
+    synchronized int nProgramFragmentCreate(String shader, int[] params) {
+        return rsnProgramFragmentCreate(mContext, shader, params);
+    }
+    native int  rsnProgramVertexCreate(int con, String shader, int[] params);
+    synchronized int nProgramVertexCreate(String shader, int[] params) {
+        return rsnProgramVertexCreate(mContext, shader, params);
+    }
+
+    native int  rsnMeshCreate(int con, int vtxCount, int indexCount);
+    synchronized int nMeshCreate(int vtxCount, int indexCount) {
+        return rsnMeshCreate(mContext, vtxCount, indexCount);
+    }
+    native void rsnMeshBindVertex(int con, int id, int alloc, int slot);
+    synchronized void nMeshBindVertex(int id, int alloc, int slot) {
+        rsnMeshBindVertex(mContext, id, alloc, slot);
+    }
+    native void rsnMeshBindIndex(int con, int id, int alloc, int prim, int slot);
+    synchronized void nMeshBindIndex(int id, int alloc, int prim, int slot) {
+        rsnMeshBindIndex(mContext, id, alloc, prim, slot);
+    }
+    native int  rsnMeshGetVertexBufferCount(int con, int id);
+    synchronized int nMeshGetVertexBufferCount(int id) {
+        return rsnMeshGetVertexBufferCount(mContext, id);
+    }
+    native int  rsnMeshGetIndexCount(int con, int id);
+    synchronized int nMeshGetIndexCount(int id) {
+        return rsnMeshGetIndexCount(mContext, id);
+    }
+    native void rsnMeshGetVertices(int con, int id, int[] vtxIds, int vtxIdCount);
+    synchronized void nMeshGetVertices(int id, int[] vtxIds, int vtxIdCount) {
+        rsnMeshGetVertices(mContext, id, vtxIds, vtxIdCount);
+    }
+    native void rsnMeshGetIndices(int con, int id, int[] idxIds, int[] primitives, int vtxIdCount);
+    synchronized void nMeshGetIndices(int id, int[] idxIds, int[] primitives, int vtxIdCount) {
+        rsnMeshGetIndices(mContext, id, idxIds, primitives, vtxIdCount);
+    }
+
 
     protected int     mDev;
     protected int     mContext;
     @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_I64;
+    Element mElement_F32;
+    Element mElement_F64;
+    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 +533,38 @@
     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;
+
+    Element mElement_MATRIX_4X4;
+    Element mElement_MATRIX_3X3;
+    Element mElement_MATRIX_2X2;
+
+    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;
+
+    ProgramStore mProgramStore_BLEND_NONE_DEPTH_TEST;
+    ProgramStore mProgramStore_BLEND_NONE_DEPTH_NO_DEPTH;
+    ProgramStore mProgramStore_BLEND_NONE_DEPTH_NO_TEST;
+    ProgramStore mProgramStore_BLEND_NONE_DEPTH_NO_WRITE;
+    ProgramStore mProgramStore_BLEND_ALPHA_DEPTH_TEST;
+    ProgramStore mProgramStore_BLEND_ALPHA_DEPTH_NO_DEPTH;
+    ProgramStore mProgramStore_BLEND_ALPHA_DEPTH_NO_TEST;
+    ProgramStore mProgramStore_BLEND_ALPHA_DEPTH_NO_WRITE;
+    ProgramStore mProgramStore_BLEND_ADD_DEPTH_TEST;
+    ProgramStore mProgramStore_BLEND_ADD_DEPTH_NO_DEPTH;
+    ProgramStore mProgramStore_BLEND_ADD_DEPTH_NO_TEST;
+    ProgramStore mProgramStore_BLEND_ADD_DEPTH_NO_WRITE;
+
+    ProgramRaster mProgramRaster_CULL_BACK;
+    ProgramRaster mProgramRaster_CULL_FRONT;
+    ProgramRaster mProgramRaster_CULL_NONE;
 
     ///////////////////////////////////////////////////////////////////////////////////
     //
@@ -264,25 +612,32 @@
             // This function is a temporary solution.  The final solution will
             // used typed allocations where the message id is the type indicator.
             int[] rbuf = new int[16];
-            mRS.nContextInitToClient();
+            mRS.nContextInitToClient(mRS.mContext);
             while(mRun) {
-                int msg = mRS.nContextGetMessage(rbuf, true);
-                if (msg == 0) {
-                    // Should only happen during teardown.
-                    // But we want to avoid starving other threads during
-                    // teardown by yielding until the next line in the destructor
-                    // can execute to set mRun = false
-                    try {
-                        sleep(1, 0);
-                    } catch(InterruptedException e) {
+                rbuf[0] = 0;
+                int msg = mRS.nContextGetMessage(mRS.mContext, rbuf, true);
+                if ((msg == 0)) {
+                    // Can happen for two reasons
+                    if (rbuf[0] > 0 && mRun) {
+                        // 1: Buffer needs to be enlarged.
+                        rbuf = new int[rbuf[0] + 2];
+                    } else {
+                        // 2: teardown.
+                        // But we want to avoid starving other threads during
+                        // teardown by yielding until the next line in the destructor
+                        // can execute to set mRun = false
+                        try {
+                            sleep(1, 0);
+                        } catch(InterruptedException e) {
+                        }
                     }
+                    continue;
                 }
                 if(mRS.mMessageCallback != null) {
                     mRS.mMessageCallback.mData = rbuf;
                     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,12 +662,20 @@
         nContextDump(bits);
     }
 
+    public void finish() {
+        nContextFinish();
+    }
+
     public void destroy() {
         validate();
-        nContextDeinitToClient();
+        nContextDeinitToClient(mContext);
         mMessageThread.mRun = false;
+        try {
+            mMessageThread.join();
+        } catch(InterruptedException e) {
+        }
 
-        nContextDestroy(mContext);
+        nContextDestroy();
         mContext = 0;
 
         nDeviceDestroy(mDev);
@@ -335,3 +698,4 @@
 }
 
 
+
diff --git a/graphics/java/android/renderscript/RenderScriptGL.java b/graphics/java/android/renderscript/RenderScriptGL.java
index d1df23d..61ecc8d 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) {
@@ -102,8 +102,7 @@
 
     public class File extends BaseObj {
         File(int id) {
-            super(RenderScriptGL.this);
-            mID = id;
+            super(id, RenderScriptGL.this);
         }
     }
 
diff --git a/graphics/java/android/renderscript/Sampler.java b/graphics/java/android/renderscript/Sampler.java
index 40ba722..b627207 100644
--- a/graphics/java/android/renderscript/Sampler.java
+++ b/graphics/java/android/renderscript/Sampler.java
@@ -47,10 +47,82 @@
     }
 
     Sampler(int id, RenderScript rs) {
-        super(rs);
-        mID = id;
+        super(id, rs);
     }
 
+    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);
+            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);
+            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;
@@ -58,6 +130,7 @@
         Value mWrapS;
         Value mWrapT;
         Value mWrapR;
+        float mAniso;
 
         public Builder(RenderScript rs) {
             mRS = rs;
@@ -66,6 +139,7 @@
             mWrapS = Value.WRAP;
             mWrapT = Value.WRAP;
             mWrapR = Value.WRAP;
+            mAniso = 1.0f;
         }
 
         public void setMin(Value v) {
@@ -110,6 +184,14 @@
             }
         }
 
+        public void setAnisotropy(float v) {
+            if(v >= 0.0f) {
+                mAniso = v;
+            } else {
+                throw new IllegalArgumentException("Invalid value");
+            }
+        }
+
         static synchronized Sampler internalCreate(RenderScript rs, Builder b) {
             rs.nSamplerBegin();
             rs.nSamplerSet(0, b.mMin.mID);
@@ -117,6 +199,7 @@
             rs.nSamplerSet(2, b.mWrapS.mID);
             rs.nSamplerSet(3, b.mWrapT.mID);
             rs.nSamplerSet(4, b.mWrapR.mID);
+            rs.nSamplerSet2(5, b.mAniso);
             int id = rs.nSamplerCreate();
             return new Sampler(id, rs);
         }
diff --git a/graphics/java/android/renderscript/Script.java b/graphics/java/android/renderscript/Script.java
index 57ccfa3..8772c4c 100644
--- a/graphics/java/android/renderscript/Script.java
+++ b/graphics/java/android/renderscript/Script.java
@@ -42,29 +42,50 @@
         }
     }
 
+    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;
+        super(id, rs);
     }
 
     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, double v) {
+        mRS.nScriptSetVarD(mID, index, v);
     }
 
-    public void setClearStencil(int stencil) {
-        mRS.validate();
-        mRS.nScriptSetClearStencil(mID, stencil);
+    public void setVar(int index, int v) {
+        mRS.nScriptSetVarI(mID, index, v);
+    }
+
+    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 +99,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..0b3db69 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
@@ -31,7 +33,6 @@
     int mElementCount;
     Element mElement;
 
-    private int mNativeCache;
     Class mJavaClass;
 
     public Element getElement() {
@@ -95,61 +96,37 @@
 
 
     Type(int id, RenderScript rs) {
-        super(rs);
-        mID = id;
-        mNativeCache = 0;
+        super(id, rs);
     }
 
     protected void finalize() throws Throwable {
-        if(mNativeCache != 0) {
-            mRS.nTypeFinalDestroy(this);
-            mNativeCache = 0;
-        }
         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(elementID, mRS);
+            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.mk b/graphics/jni/Android.mk
index 8476be1..4c4a128 100644
--- a/graphics/jni/Android.mk
+++ b/graphics/jni/Android.mk
@@ -13,14 +13,13 @@
 
 LOCAL_SHARED_LIBRARIES := \
         libandroid_runtime \
-        libacc \
         libnativehelper \
         libRS \
         libcutils \
         libskia \
         libutils \
         libui \
-        libsurfaceflinger_client 
+        libsurfaceflinger_client
 
 LOCAL_STATIC_LIBRARIES :=
 
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 45cc72e..8f1e93c 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -37,6 +37,7 @@
 #include "jni.h"
 #include "JNIHelp.h"
 #include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_view_Surface.h"
 
 #include <RenderScript.h>
 #include <RenderScriptEnv.h>
@@ -69,9 +70,6 @@
 
     jclass bitmapClass = _env->FindClass("android/graphics/Bitmap");
     gNativeBitmapID = _env->GetFieldID(bitmapClass, "mNativeBitmap", "I");
-
-    jclass typeClass = _env->FindClass("android/renderscript/Type");
-    gTypeNativeCache = _env->GetFieldID(typeClass, "mNativeCache", "I");
 }
 
 static void nInitElements(JNIEnv *_env, jobject _this, jint a8, jint rgba4444, jint rgba8888, jint rgb565)
@@ -85,41 +83,43 @@
 // ---------------------------------------------------------------------------
 
 static void
-nAssignName(JNIEnv *_env, jobject _this, jint obj, jbyteArray str)
+nContextFinish(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nAssignName, con(%p), obj(%p)", con, (void *)obj);
+    LOG_API("nContextFinish, con(%p)", con);
+    rsContextFinish(con);
+}
 
+static void
+nAssignName(JNIEnv *_env, jobject _this, RsContext con, jint obj, jbyteArray str)
+{
+    LOG_API("nAssignName, con(%p), obj(%p)", con, (void *)obj);
     jint len = _env->GetArrayLength(str);
     jbyte * cptr = (jbyte *) _env->GetPrimitiveArrayCritical(str, 0);
     rsAssignName(con, (void *)obj, (const char *)cptr, len);
     _env->ReleasePrimitiveArrayCritical(str, cptr, JNI_ABORT);
 }
 
-static void
-nObjDestroy(JNIEnv *_env, jobject _this, jint obj)
+static jstring
+nGetName(JNIEnv *_env, jobject _this, RsContext con, 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, RsContext con, jint obj)
+{
     LOG_API("nObjDestroy, con(%p) obj(%p)", con, (void *)obj);
     rsObjDestroy(con, (void *)obj);
 }
 
-static void
-nObjDestroyOOB(JNIEnv *_env, jobject _this, jint obj)
-{
-    // This function only differs from nObjDestroy in that it calls the
-    // special Out Of Band version of ObjDestroy which is thread safe.
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nObjDestroyOOB, con(%p) obj(%p)", con, (void *)obj);
-    rsObjDestroyOOB(con, (void *)obj);
-}
 
 static jint
-nFileOpen(JNIEnv *_env, jobject _this, jbyteArray str)
+nFileOpen(JNIEnv *_env, jobject _this, RsContext con, jbyteArray str)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nFileOpen, con(%p)", con);
-
     jint len = _env->GetArrayLength(str);
     jbyte * cptr = (jbyte *) _env->GetPrimitiveArrayCritical(str, 0);
     jint ret = (jint)rsFileOpen(con, (const char *)cptr, len);
@@ -165,9 +165,8 @@
 }
 
 static void
-nContextSetPriority(JNIEnv *_env, jobject _this, jint p)
+nContextSetPriority(JNIEnv *_env, jobject _this, RsContext con, jint p)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("ContextSetPriority, con(%p), priority(%i)", con, p);
     rsContextSetPriority(con, p);
 }
@@ -175,101 +174,92 @@
 
 
 static void
-nContextSetSurface(JNIEnv *_env, jobject _this, jint width, jint height, jobject wnd)
+nContextSetSurface(JNIEnv *_env, jobject _this, RsContext con, jint width, jint height, jobject wnd)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextSetSurface, con(%p), width(%i), height(%i), surface(%p)", con, width, height, (Surface *)wnd);
 
     Surface * window = NULL;
     if (wnd == NULL) {
 
     } else {
-        jclass surface_class = _env->FindClass("android/view/Surface");
-        jfieldID surfaceFieldID = _env->GetFieldID(surface_class, ANDROID_VIEW_SURFACE_JNI_ID, "I");
-        window = (Surface*)_env->GetIntField(wnd, surfaceFieldID);
+        window = (Surface*) android_Surface_getNativeWindow(_env, wnd).get();
     }
 
     rsContextSetSurface(con, width, height, window);
 }
 
 static void
-nContextDestroy(JNIEnv *_env, jobject _this, jint con)
+nContextDestroy(JNIEnv *_env, jobject _this, RsContext con)
 {
-    LOG_API("nContextDestroy, con(%p)", (RsContext)con);
-    rsContextDestroy((RsContext)con);
+    LOG_API("nContextDestroy, con(%p)", con);
+    rsContextDestroy(con);
 }
 
 static void
-nContextDump(JNIEnv *_env, jobject _this, jint bits)
+nContextDump(JNIEnv *_env, jobject _this, RsContext con, jint bits)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextDump, con(%p)  bits(%i)", (RsContext)con, bits);
     rsContextDump((RsContext)con, bits);
 }
 
 static void
-nContextPause(JNIEnv *_env, jobject _this)
+nContextPause(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextPause, con(%p)", con);
     rsContextPause(con);
 }
 
 static void
-nContextResume(JNIEnv *_env, jobject _this)
+nContextResume(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextResume, con(%p)", con);
     rsContextResume(con);
 }
 
 static jint
-nContextGetMessage(JNIEnv *_env, jobject _this, jintArray data, jboolean wait)
+nContextGetMessage(JNIEnv *_env, jobject _this, RsContext con, jintArray data, jboolean wait)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nContextGetMessage, con(%p), len(%i)", con, len);
     jint *ptr = _env->GetIntArrayElements(data, NULL);
     size_t receiveLen;
     int id = rsContextGetMessage(con, ptr, &receiveLen, len * 4, wait);
     if (!id && receiveLen) {
-        LOGE("message receive buffer too small.  %i", receiveLen);
+        LOGV("message receive buffer too small.  %i", receiveLen);
+        *ptr = (jint)receiveLen;
     }
     _env->ReleaseIntArrayElements(data, ptr, 0);
     return id;
 }
 
-static void nContextInitToClient(JNIEnv *_env, jobject _this)
+static void nContextInitToClient(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextInitToClient, con(%p)", con);
     rsContextInitToClient(con);
 }
 
-static void nContextDeinitToClient(JNIEnv *_env, jobject _this)
+static void nContextDeinitToClient(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextDeinitToClient, con(%p)", con);
     rsContextDeinitToClient(con);
 }
 
 
 static jint
-nElementCreate(JNIEnv *_env, jobject _this, jint type, jint kind, jboolean norm, jint size)
+nElementCreate(JNIEnv *_env, jobject _this, RsContext con, jint type, jint kind, jboolean norm, jint size)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nElementCreate, con(%p), type(%i), kind(%i), norm(%i), size(%i)", con, type, kind, norm, size);
     return (jint)rsElementCreate(con, (RsDataType)type, (RsDataKind)kind, norm, size);
 }
 
 static jint
-nElementCreate2(JNIEnv *_env, jobject _this, jintArray _ids, jobjectArray _names)
+nElementCreate2(JNIEnv *_env, jobject _this, RsContext con, jintArray _ids, jobjectArray _names, jintArray _arraySizes)
 {
     int fieldCount = _env->GetArrayLength(_ids);
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nElementCreate2, con(%p)", con);
 
     jint *ids = _env->GetIntArrayElements(_ids, NULL);
+    jint *arraySizes = _env->GetIntArrayElements(_arraySizes, NULL);
     const char ** nameArray = (const char **)calloc(fieldCount, sizeof(char *));
     size_t* sizeArray = (size_t*)calloc(fieldCount, sizeof(size_t));
 
@@ -278,183 +268,116 @@
         nameArray[ct] = _env->GetStringUTFChars(s, NULL);
         sizeArray[ct] = _env->GetStringUTFLength(s);
     }
-    jint id = (jint)rsElementCreate2(con, fieldCount, (RsElement *)ids, nameArray, sizeArray);
+    jint id = (jint)rsElementCreate2(con, fieldCount, (RsElement *)ids, nameArray, sizeArray, (const uint32_t *)arraySizes);
     for (int ct=0; ct < fieldCount; ct++) {
         jstring s = (jstring)_env->GetObjectArrayElement(_names, ct);
         _env->ReleaseStringUTFChars(s, nameArray[ct]);
     }
     _env->ReleaseIntArrayElements(_ids, ids, JNI_ABORT);
+    _env->ReleaseIntArrayElements(_arraySizes, arraySizes, JNI_ABORT);
     free(nameArray);
     free(sizeArray);
     return (jint)id;
 }
 
+static void
+nElementGetNativeData(JNIEnv *_env, jobject _this, RsContext con, jint id, jintArray _elementData)
+{
+    int dataSize = _env->GetArrayLength(_elementData);
+    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, RsContext con, jint id, jintArray _IDs, jobjectArray _names)
+{
+    int dataSize = _env->GetArrayLength(_IDs);
+    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
-nTypeBegin(JNIEnv *_env, jobject _this, jint eID)
+nTypeBegin(JNIEnv *_env, jobject _this, RsContext con, jint eID)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nTypeBegin, con(%p) e(%p)", con, (RsElement)eID);
     rsTypeBegin(con, (RsElement)eID);
 }
 
 static void
-nTypeAdd(JNIEnv *_env, jobject _this, jint dim, jint val)
+nTypeAdd(JNIEnv *_env, jobject _this, RsContext con, jint dim, jint val)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nTypeAdd, con(%p) dim(%i), val(%i)", con, dim, val);
     rsTypeAdd(con, (RsDimension)dim, val);
 }
 
 static jint
-nTypeCreate(JNIEnv *_env, jobject _this)
+nTypeCreate(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nTypeCreate, con(%p)", con);
     return (jint)rsTypeCreate(con);
 }
 
-static void * SF_LoadInt(JNIEnv *_env, jobject _obj, jfieldID _field, void *buffer)
-{
-    ((int32_t *)buffer)[0] = _env->GetIntField(_obj, _field);
-    return ((uint8_t *)buffer) + 4;
-}
-
-static void * SF_LoadShort(JNIEnv *_env, jobject _obj, jfieldID _field, void *buffer)
-{
-    ((int16_t *)buffer)[0] = _env->GetShortField(_obj, _field);
-    return ((uint8_t *)buffer) + 2;
-}
-
-static void * SF_LoadByte(JNIEnv *_env, jobject _obj, jfieldID _field, void *buffer)
-{
-    ((int8_t *)buffer)[0] = _env->GetByteField(_obj, _field);
-    return ((uint8_t *)buffer) + 1;
-}
-
-static void * SF_LoadFloat(JNIEnv *_env, jobject _obj, jfieldID _field, void *buffer)
-{
-    ((float *)buffer)[0] = _env->GetFloatField(_obj, _field);
-    return ((uint8_t *)buffer) + 4;
-}
-
-static void * SF_SaveInt(JNIEnv *_env, jobject _obj, jfieldID _field, void *buffer)
-{
-    _env->SetIntField(_obj, _field, ((int32_t *)buffer)[0]);
-    return ((uint8_t *)buffer) + 4;
-}
-
-static void * SF_SaveShort(JNIEnv *_env, jobject _obj, jfieldID _field, void *buffer)
-{
-    _env->SetShortField(_obj, _field, ((int16_t *)buffer)[0]);
-    return ((uint8_t *)buffer) + 2;
-}
-
-static void * SF_SaveByte(JNIEnv *_env, jobject _obj, jfieldID _field, void *buffer)
-{
-    _env->SetByteField(_obj, _field, ((int8_t *)buffer)[0]);
-    return ((uint8_t *)buffer) + 1;
-}
-
-static void * SF_SaveFloat(JNIEnv *_env, jobject _obj, jfieldID _field, void *buffer)
-{
-    _env->SetFloatField(_obj, _field, ((float *)buffer)[0]);
-    return ((uint8_t *)buffer) + 4;
-}
-
-struct TypeFieldCache {
-    jfieldID field;
-    int bits;
-    void * (*ptr)(JNIEnv *, jobject, jfieldID, void *buffer);
-    void * (*readPtr)(JNIEnv *, jobject, jfieldID, void *buffer);
-};
-
-struct TypeCache {
-    int fieldCount;
-    int size;
-    TypeFieldCache fields[1];
-};
-
-//{"nTypeFinalDestroy",              "(Landroid/renderscript/Type;)V",       (void*)nTypeFinalDestroy },
 static void
-nTypeFinalDestroy(JNIEnv *_env, jobject _this, jobject _type)
+nTypeGetNativeData(JNIEnv *_env, jobject _this, RsContext con, jint id, jintArray _typeData)
 {
-    TypeCache *tc = (TypeCache *)_env->GetIntField(_type, gTypeNativeCache);
-    free(tc);
-}
+    // We are packing 6 items: mDimX; mDimY; mDimZ;
+    // mDimLOD; mDimFaces; mElement; into typeData
+    int elementCount = _env->GetArrayLength(_typeData);
 
-// native void nTypeSetupFields(Type t, int[] types, int[] bits, Field[] IDs);
-static void
-nTypeSetupFields(JNIEnv *_env, jobject _this, jobject _type, jintArray _types, jintArray _bits, jobjectArray _IDs)
-{
-    int fieldCount = _env->GetArrayLength(_types);
-    size_t structSize = sizeof(TypeCache) + (sizeof(TypeFieldCache) * (fieldCount-1));
-    TypeCache *tc = (TypeCache *)malloc(structSize);
-    memset(tc, 0, structSize);
+    assert(elementCount == 6);
+    LOG_API("nTypeCreate, con(%p)", con);
 
-    TypeFieldCache *tfc = &tc->fields[0];
-    tc->fieldCount = fieldCount;
-    _env->SetIntField(_type, gTypeNativeCache, (jint)tc);
+    uint32_t typeData[6];
+    rsTypeGetNativeData(con, (RsType)id, typeData, 6);
 
-    jint *fType = _env->GetIntArrayElements(_types, NULL);
-    jint *fBits = _env->GetIntArrayElements(_bits, NULL);
-    for (int ct=0; ct < fieldCount; ct++) {
-        jobject field = _env->GetObjectArrayElement(_IDs, ct);
-        tfc[ct].field = _env->FromReflectedField(field);
-        tfc[ct].bits = fBits[ct];
-
-        switch(fType[ct]) {
-        case RS_TYPE_FLOAT_32:
-            tfc[ct].ptr = SF_LoadFloat;
-            tfc[ct].readPtr = SF_SaveFloat;
-            break;
-        case RS_TYPE_UNSIGNED_32:
-        case RS_TYPE_SIGNED_32:
-            tfc[ct].ptr = SF_LoadInt;
-            tfc[ct].readPtr = SF_SaveInt;
-            break;
-        case RS_TYPE_UNSIGNED_16:
-        case RS_TYPE_SIGNED_16:
-            tfc[ct].ptr = SF_LoadShort;
-            tfc[ct].readPtr = SF_SaveShort;
-            break;
-        case RS_TYPE_UNSIGNED_8:
-        case RS_TYPE_SIGNED_8:
-            tfc[ct].ptr = SF_LoadByte;
-            tfc[ct].readPtr = SF_SaveByte;
-            break;
-        }
-        tc->size += 4;
+    for(jint i = 0; i < elementCount; i ++) {
+        _env->SetIntArrayRegion(_typeData, i, 1, (const jint*)&typeData[i]);
     }
-
-    _env->ReleaseIntArrayElements(_types, fType, JNI_ABORT);
-    _env->ReleaseIntArrayElements(_bits, fBits, JNI_ABORT);
 }
 
-
 // -----------------------------------
 
 static jint
-nAllocationCreateTyped(JNIEnv *_env, jobject _this, jint e)
+nAllocationCreateTyped(JNIEnv *_env, jobject _this, RsContext con, jint e)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nAllocationCreateTyped, con(%p), e(%p)", con, (RsElement)e);
     return (jint) rsAllocationCreateTyped(con, (RsElement)e);
 }
 
 static void
-nAllocationUploadToTexture(JNIEnv *_env, jobject _this, jint a, jboolean genMip, jint mip)
+nAllocationUploadToTexture(JNIEnv *_env, jobject _this, RsContext con, jint a, jboolean genMip, jint mip)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nAllocationUploadToTexture, con(%p), a(%p), genMip(%i), mip(%i)", con, (RsAllocation)a, genMip, mip);
     rsAllocationUploadToTexture(con, (RsAllocation)a, genMip, mip);
 }
 
 static void
-nAllocationUploadToBufferObject(JNIEnv *_env, jobject _this, jint a)
+nAllocationUploadToBufferObject(JNIEnv *_env, jobject _this, RsContext con, jint a)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nAllocationUploadToBufferObject, con(%p), a(%p)", con, (RsAllocation)a);
     rsAllocationUploadToBufferObject(con, (RsAllocation)a);
 }
@@ -480,9 +403,8 @@
 }
 
 static int
-nAllocationCreateFromBitmap(JNIEnv *_env, jobject _this, jint dstFmt, jboolean genMips, jobject jbitmap)
+nAllocationCreateFromBitmap(JNIEnv *_env, jobject _this, RsContext con, jint dstFmt, jboolean genMips, jobject jbitmap)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     SkBitmap const * nativeBitmap =
             (SkBitmap const *)_env->GetIntField(jbitmap, gNativeBitmapID);
     const SkBitmap& bitmap(*nativeBitmap);
@@ -508,9 +430,8 @@
 }
 
 static int
-nAllocationCreateBitmapRef(JNIEnv *_env, jobject _this, jint type, jobject jbitmap)
+nAllocationCreateBitmapRef(JNIEnv *_env, jobject _this, RsContext con, jint type, jobject jbitmap)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     SkBitmap * nativeBitmap =
             (SkBitmap *)_env->GetIntField(jbitmap, gNativeBitmapID);
 
@@ -522,10 +443,8 @@
 }
 
 static int
-nAllocationCreateFromAssetStream(JNIEnv *_env, jobject _this, jint dstFmt, jboolean genMips, jint native_asset)
+nAllocationCreateFromAssetStream(JNIEnv *_env, jobject _this, RsContext con, jint dstFmt, jboolean genMips, jint native_asset)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-
     Asset* asset = reinterpret_cast<Asset*>(native_asset);
     SkBitmap bitmap;
     SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(),
@@ -548,9 +467,8 @@
 }
 
 static int
-nAllocationCreateFromBitmapBoxed(JNIEnv *_env, jobject _this, jint dstFmt, jboolean genMips, jobject jbitmap)
+nAllocationCreateFromBitmapBoxed(JNIEnv *_env, jobject _this, RsContext con, jint dstFmt, jboolean genMips, jobject jbitmap)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     SkBitmap const * nativeBitmap =
             (SkBitmap const *)_env->GetIntField(jbitmap, gNativeBitmapID);
     const SkBitmap& bitmap(*nativeBitmap);
@@ -572,9 +490,8 @@
 
 
 static void
-nAllocationSubData1D_i(JNIEnv *_env, jobject _this, jint alloc, jint offset, jint count, jintArray data, int sizeBytes)
+nAllocationSubData1D_i(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint offset, jint count, jintArray data, int sizeBytes)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAllocation1DSubData_i, con(%p), adapter(%p), offset(%i), count(%i), len(%i), sizeBytes(%i)", con, (RsAllocation)alloc, offset, count, len, sizeBytes);
     jint *ptr = _env->GetIntArrayElements(data, NULL);
@@ -583,9 +500,8 @@
 }
 
 static void
-nAllocationSubData1D_s(JNIEnv *_env, jobject _this, jint alloc, jint offset, jint count, jshortArray data, int sizeBytes)
+nAllocationSubData1D_s(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint offset, jint count, jshortArray data, int sizeBytes)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAllocation1DSubData_s, con(%p), adapter(%p), offset(%i), count(%i), len(%i), sizeBytes(%i)", con, (RsAllocation)alloc, offset, count, len, sizeBytes);
     jshort *ptr = _env->GetShortArrayElements(data, NULL);
@@ -594,9 +510,8 @@
 }
 
 static void
-nAllocationSubData1D_b(JNIEnv *_env, jobject _this, jint alloc, jint offset, jint count, jbyteArray data, int sizeBytes)
+nAllocationSubData1D_b(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint offset, jint count, jbyteArray data, int sizeBytes)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAllocation1DSubData_b, con(%p), adapter(%p), offset(%i), count(%i), len(%i), sizeBytes(%i)", con, (RsAllocation)alloc, offset, count, len, sizeBytes);
     jbyte *ptr = _env->GetByteArrayElements(data, NULL);
@@ -605,9 +520,8 @@
 }
 
 static void
-nAllocationSubData1D_f(JNIEnv *_env, jobject _this, jint alloc, jint offset, jint count, jfloatArray data, int sizeBytes)
+nAllocationSubData1D_f(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint offset, jint count, jfloatArray data, int sizeBytes)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAllocation1DSubData_f, con(%p), adapter(%p), offset(%i), count(%i), len(%i), sizeBytes(%i)", con, (RsAllocation)alloc, offset, count, len, sizeBytes);
     jfloat *ptr = _env->GetFloatArrayElements(data, NULL);
@@ -616,9 +530,19 @@
 }
 
 static void
-nAllocationSubData2D_i(JNIEnv *_env, jobject _this, jint alloc, jint xoff, jint yoff, jint w, jint h, jintArray data, int sizeBytes)
+//    native void rsnAllocationSubElementData1D(int con, int id, int xoff, int compIdx, byte[] d, int sizeBytes);
+nAllocationSubElementData1D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint offset, jint compIdx, jbyteArray data, int sizeBytes)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    jint len = _env->GetArrayLength(data);
+    LOG_API("nAllocationSubElementData1D, con(%p), alloc(%p), offset(%i), comp(%i), len(%i), sizeBytes(%i)", con, (RsAllocation)alloc, offset, compIdx, len, sizeBytes);
+    jbyte *ptr = _env->GetByteArrayElements(data, NULL);
+    rsAllocation1DSubElementData(con, (RsAllocation)alloc, offset, ptr, compIdx, sizeBytes);
+    _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
+}
+
+static void
+nAllocationSubData2D_i(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint xoff, jint yoff, jint w, jint h, jintArray data, int sizeBytes)
+{
     jint len = _env->GetArrayLength(data);
     LOG_API("nAllocation2DSubData_i, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i)", con, (RsAllocation)alloc, xoff, yoff, w, h, len);
     jint *ptr = _env->GetIntArrayElements(data, NULL);
@@ -627,9 +551,8 @@
 }
 
 static void
-nAllocationSubData2D_f(JNIEnv *_env, jobject _this, jint alloc, jint xoff, jint yoff, jint w, jint h, jfloatArray data, int sizeBytes)
+nAllocationSubData2D_f(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint xoff, jint yoff, jint w, jint h, jfloatArray data, int sizeBytes)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAllocation2DSubData_i, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i)", con, (RsAllocation)alloc, xoff, yoff, w, h, len);
     jfloat *ptr = _env->GetFloatArrayElements(data, NULL);
@@ -638,9 +561,8 @@
 }
 
 static void
-nAllocationRead_i(JNIEnv *_env, jobject _this, jint alloc, jintArray data)
+nAllocationRead_i(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jintArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAllocationRead_i, con(%p), alloc(%p), len(%i)", con, (RsAllocation)alloc, len);
     jint *ptr = _env->GetIntArrayElements(data, NULL);
@@ -649,9 +571,8 @@
 }
 
 static void
-nAllocationRead_f(JNIEnv *_env, jobject _this, jint alloc, jfloatArray data)
+nAllocationRead_f(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jfloatArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAllocationRead_f, con(%p), alloc(%p), len(%i)", con, (RsAllocation)alloc, len);
     jfloat *ptr = _env->GetFloatArrayElements(data, NULL);
@@ -659,70 +580,103 @@
     _env->ReleaseFloatArrayElements(data, ptr, 0);
 }
 
-
-//{"nAllocationDataFromObject",      "(ILandroid/renderscript/Type;Ljava/lang/Object;)V",   (void*)nAllocationDataFromObject },
-static void
-nAllocationSubDataFromObject(JNIEnv *_env, jobject _this, jint alloc, jobject _type, jint offset, jobject _o)
+static jint
+nAllocationGetType(JNIEnv *_env, jobject _this, RsContext con, jint a)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nAllocationDataFromObject con(%p), alloc(%p)", con, (RsAllocation)alloc);
-
-    const TypeCache *tc = (TypeCache *)_env->GetIntField(_type, gTypeNativeCache);
-
-    void * bufAlloc = malloc(tc->size);
-    void * buf = bufAlloc;
-    for (int ct=0; ct < tc->fieldCount; ct++) {
-        const TypeFieldCache *tfc = &tc->fields[ct];
-        buf = tfc->ptr(_env, _o, tfc->field, buf);
-    }
-    rsAllocation1DSubData(con, (RsAllocation)alloc, offset, 1, bufAlloc, tc->size);
-    free(bufAlloc);
+    LOG_API("nAllocationGetType, con(%p), a(%p)", con, (RsAllocation)a);
+    return (jint) rsAllocationGetType(con, (RsAllocation)a);
 }
 
 static void
-nAllocationSubReadFromObject(JNIEnv *_env, jobject _this, jint alloc, jobject _type, jint offset, jobject _o)
+nAllocationResize1D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint dimX)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nAllocationReadFromObject con(%p), alloc(%p)", con, (RsAllocation)alloc);
+    LOG_API("nAllocationResize1D, con(%p), alloc(%p), sizeX(%i)", con, (RsAllocation)alloc, dimX);
+    rsAllocationResize1D(con, (RsAllocation)alloc, dimX);
+}
 
-    assert(offset == 0);
+static void
+nAllocationResize2D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint dimX, jint dimY)
+{
+    LOG_API("nAllocationResize1D, con(%p), alloc(%p), sizeX(%i), sizeY(%i)", con, (RsAllocation)alloc, dimX, dimY);
+    rsAllocationResize2D(con, (RsAllocation)alloc, dimX, dimY);
+}
 
-    const TypeCache *tc = (TypeCache *)_env->GetIntField(_type, gTypeNativeCache);
+// -----------------------------------
 
-    void * bufAlloc = malloc(tc->size);
-    void * buf = bufAlloc;
-    rsAllocationRead(con, (RsAllocation)alloc, bufAlloc);
+static int
+nFileA3DCreateFromAssetStream(JNIEnv *_env, jobject _this, RsContext con, jint native_asset)
+{
+    LOGV("______nFileA3D %u", (uint32_t) native_asset);
 
-    for (int ct=0; ct < tc->fieldCount; ct++) {
-        const TypeFieldCache *tfc = &tc->fields[ct];
-        buf = tfc->readPtr(_env, _o, tfc->field, buf);
+    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, RsContext con, jint fileA3D)
+{
+    int32_t numEntries = 0;
+    rsFileA3DGetNumIndexEntries(con, &numEntries, (RsFile)fileA3D);
+    return numEntries;
+}
+
+static void
+nFileA3DGetIndexEntries(JNIEnv *_env, jobject _this, RsContext con, jint fileA3D, jint numEntries, jintArray _ids, jobjectArray _entries)
+{
+    LOGV("______nFileA3D %u", (uint32_t) fileA3D);
+    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(bufAlloc);
+
+    free(fileEntries);
+}
+
+static int
+nFileA3DGetEntryByIndex(JNIEnv *_env, jobject _this, RsContext con, jint fileA3D, jint index)
+{
+    LOGV("______nFileA3D %u", (uint32_t) fileA3D);
+    jint id = (jint)rsFileA3DGetEntryByIndex(con, (uint32_t)index, (RsFile)fileA3D);
+    return id;
+}
+
+// -----------------------------------
+
+static int
+nFontCreateFromFile(JNIEnv *_env, jobject _this, RsContext con, jstring fileName, jint fontSize, jint dpi)
+{
+    const char* fileNameUTF = _env->GetStringUTFChars(fileName, NULL);
+
+    jint id = (jint)rsFontCreateFromFile(con, fileNameUTF, fontSize, dpi);
+    return id;
 }
 
 
 // -----------------------------------
 
 static void
-nAdapter1DBindAllocation(JNIEnv *_env, jobject _this, jint adapter, jint alloc)
+nAdapter1DBindAllocation(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jint alloc)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nAdapter1DBindAllocation, con(%p), adapter(%p), alloc(%p)", con, (RsAdapter1D)adapter, (RsAllocation)alloc);
     rsAdapter1DBindAllocation(con, (RsAdapter1D)adapter, (RsAllocation)alloc);
 }
 
 static void
-nAdapter1DSetConstraint(JNIEnv *_env, jobject _this, jint adapter, jint dim, jint value)
+nAdapter1DSetConstraint(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jint dim, jint value)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nAdapter1DSetConstraint, con(%p), adapter(%p), dim(%i), value(%i)", con, (RsAdapter1D)adapter, dim, value);
     rsAdapter1DSetConstraint(con, (RsAdapter1D)adapter, (RsDimension)dim, value);
 }
 
 static void
-nAdapter1DData_i(JNIEnv *_env, jobject _this, jint adapter, jintArray data)
+nAdapter1DData_i(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jintArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAdapter1DData_i, con(%p), adapter(%p), len(%i)", con, (RsAdapter1D)adapter, len);
     jint *ptr = _env->GetIntArrayElements(data, NULL);
@@ -731,9 +685,8 @@
 }
 
 static void
-nAdapter1DSubData_i(JNIEnv *_env, jobject _this, jint adapter, jint offset, jint count, jintArray data)
+nAdapter1DSubData_i(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jint offset, jint count, jintArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAdapter1DSubData_i, con(%p), adapter(%p), offset(%i), count(%i), len(%i)", con, (RsAdapter1D)adapter, offset, count, len);
     jint *ptr = _env->GetIntArrayElements(data, NULL);
@@ -742,9 +695,8 @@
 }
 
 static void
-nAdapter1DData_f(JNIEnv *_env, jobject _this, jint adapter, jfloatArray data)
+nAdapter1DData_f(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jfloatArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAdapter1DData_f, con(%p), adapter(%p), len(%i)", con, (RsAdapter1D)adapter, len);
     jfloat *ptr = _env->GetFloatArrayElements(data, NULL);
@@ -753,9 +705,8 @@
 }
 
 static void
-nAdapter1DSubData_f(JNIEnv *_env, jobject _this, jint adapter, jint offset, jint count, jfloatArray data)
+nAdapter1DSubData_f(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jint offset, jint count, jfloatArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAdapter1DSubData_f, con(%p), adapter(%p), offset(%i), count(%i), len(%i)", con, (RsAdapter1D)adapter, offset, count, len);
     jfloat *ptr = _env->GetFloatArrayElements(data, NULL);
@@ -764,9 +715,8 @@
 }
 
 static jint
-nAdapter1DCreate(JNIEnv *_env, jobject _this)
+nAdapter1DCreate(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nAdapter1DCreate, con(%p)", con);
     return (jint)rsAdapter1DCreate(con);
 }
@@ -774,25 +724,22 @@
 // -----------------------------------
 
 static void
-nAdapter2DBindAllocation(JNIEnv *_env, jobject _this, jint adapter, jint alloc)
+nAdapter2DBindAllocation(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jint alloc)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nAdapter2DBindAllocation, con(%p), adapter(%p), alloc(%p)", con, (RsAdapter2D)adapter, (RsAllocation)alloc);
     rsAdapter2DBindAllocation(con, (RsAdapter2D)adapter, (RsAllocation)alloc);
 }
 
 static void
-nAdapter2DSetConstraint(JNIEnv *_env, jobject _this, jint adapter, jint dim, jint value)
+nAdapter2DSetConstraint(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jint dim, jint value)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nAdapter2DSetConstraint, con(%p), adapter(%p), dim(%i), value(%i)", con, (RsAdapter2D)adapter, dim, value);
     rsAdapter2DSetConstraint(con, (RsAdapter2D)adapter, (RsDimension)dim, value);
 }
 
 static void
-nAdapter2DData_i(JNIEnv *_env, jobject _this, jint adapter, jintArray data)
+nAdapter2DData_i(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jintArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAdapter2DData_i, con(%p), adapter(%p), len(%i)", con, (RsAdapter2D)adapter, len);
     jint *ptr = _env->GetIntArrayElements(data, NULL);
@@ -801,9 +748,8 @@
 }
 
 static void
-nAdapter2DData_f(JNIEnv *_env, jobject _this, jint adapter, jfloatArray data)
+nAdapter2DData_f(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jfloatArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAdapter2DData_f, con(%p), adapter(%p), len(%i)", con, (RsAdapter2D)adapter, len);
     jfloat *ptr = _env->GetFloatArrayElements(data, NULL);
@@ -812,9 +758,8 @@
 }
 
 static void
-nAdapter2DSubData_i(JNIEnv *_env, jobject _this, jint adapter, jint xoff, jint yoff, jint w, jint h, jintArray data)
+nAdapter2DSubData_i(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jint xoff, jint yoff, jint w, jint h, jintArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAdapter2DSubData_i, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i)",
             con, (RsAdapter2D)adapter, xoff, yoff, w, h, len);
@@ -824,9 +769,8 @@
 }
 
 static void
-nAdapter2DSubData_f(JNIEnv *_env, jobject _this, jint adapter, jint xoff, jint yoff, jint w, jint h, jfloatArray data)
+nAdapter2DSubData_f(JNIEnv *_env, jobject _this, RsContext con, jint adapter, jint xoff, jint yoff, jint w, jint h, jfloatArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     jint len = _env->GetArrayLength(data);
     LOG_API("nAdapter2DSubData_f, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i)",
             con, (RsAdapter2D)adapter, xoff, yoff, w, h, len);
@@ -836,9 +780,8 @@
 }
 
 static jint
-nAdapter2DCreate(JNIEnv *_env, jobject _this)
+nAdapter2DCreate(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nAdapter2DCreate, con(%p)", con);
     return (jint)rsAdapter2DCreate(con);
 }
@@ -846,41 +789,47 @@
 // -----------------------------------
 
 static void
-nScriptBindAllocation(JNIEnv *_env, jobject _this, jint script, jint alloc, jint slot)
+nScriptBindAllocation(JNIEnv *_env, jobject _this, RsContext con, jint script, jint alloc, jint slot)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nScriptBindAllocation, con(%p), script(%p), alloc(%p), slot(%i)", con, (RsScript)script, (RsAllocation)alloc, slot);
     rsScriptBindAllocation(con, (RsScript)script, (RsAllocation)alloc, slot);
 }
 
 static void
-nScriptSetClearColor(JNIEnv *_env, jobject _this, jint script, jfloat r, jfloat g, jfloat b, jfloat a)
+nScriptSetVarI(JNIEnv *_env, jobject _this, RsContext con, 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)", 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, RsContext con, 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("nScriptSetVarF, con(%p), s(%p), slot(%i), val(%f)", con, (void *)script, slot, val);
+    rsScriptSetVarF(con, (RsScript)script, slot, val);
 }
 
 static void
-nScriptSetClearStencil(JNIEnv *_env, jobject _this, jint script, jint stencil)
+nScriptSetVarD(JNIEnv *_env, jobject _this, RsContext con, jint script, jint slot, double val)
 {
-    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("nScriptSetVarD, con(%p), s(%p), slot(%i), val(%lf)", con, (void *)script, slot, val);
+    rsScriptSetVarD(con, (RsScript)script, slot, val);
 }
 
 static void
-nScriptSetTimeZone(JNIEnv *_env, jobject _this, jint script, jbyteArray timeZone)
+nScriptSetVarV(JNIEnv *_env, jobject _this, RsContext con, jint script, jint slot, jbyteArray data)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    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, RsContext con, jint script, jbyteArray timeZone)
+{
     LOG_API("nScriptCSetTimeZone, con(%p), s(%p), timeZone(%s)", con, (void *)script, (const char *)timeZone);
 
     jint length = _env->GetArrayLength(timeZone);
@@ -895,66 +844,36 @@
 }
 
 static void
-nScriptSetType(JNIEnv *_env, jobject _this, jint type, jboolean writable, jstring _str, jint slot)
+nScriptInvoke(JNIEnv *_env, jobject _this, RsContext con, jint obj, 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));
     LOG_API("nScriptInvoke, con(%p), script(%p)", con, (void *)obj);
     rsScriptInvoke(con, (RsScript)obj, slot);
 }
 
 static void
-nScriptSetRoot(JNIEnv *_env, jobject _this, jboolean isRoot)
+nScriptInvokeV(JNIEnv *_env, jobject _this, RsContext con, 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
-nScriptCBegin(JNIEnv *_env, jobject _this)
+nScriptCBegin(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nScriptCBegin, con(%p)", con);
     rsScriptCBegin(con);
 }
 
 static void
-nScriptCSetScript(JNIEnv *_env, jobject _this, jbyteArray scriptRef,
+nScriptCSetScript(JNIEnv *_env, jobject _this, RsContext con, jbyteArray scriptRef,
                   jint offset, jint length)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("!!! nScriptCSetScript, con(%p)", con);
     jint _exception = 0;
     jint remaining;
@@ -995,114 +914,82 @@
 }
 
 static jint
-nScriptCCreate(JNIEnv *_env, jobject _this)
+nScriptCCreate(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nScriptCCreate, con(%p)", con);
     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, RsContext con, 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, RsContext con, 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, RsContext con, 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, RsContext con, 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, RsContext con, 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, RsContext con, 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 con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramFragmentStoreCreate, con(%p)", con);
-
-    return (jint)rsProgramFragmentStoreCreate(con);
+    LOG_API("nProgramStoreCreate, con(%p)", con);
+    return (jint)rsProgramStoreCreate(con);
 }
 
 // ---------------------------------------------------------------------------
 
 static void
-nProgramBindConstants(JNIEnv *_env, jobject _this, jint vpv, jint slot, jint a)
+nProgramBindConstants(JNIEnv *_env, jobject _this, RsContext con, jint vpv, jint slot, jint a)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nProgramBindConstants, con(%p), vpf(%p), sloat(%i), a(%p)", con, (RsProgramVertex)vpv, slot, (RsAllocation)a);
     rsProgramBindConstants(con, (RsProgram)vpv, slot, (RsAllocation)a);
 }
 
 static void
-nProgramBindTexture(JNIEnv *_env, jobject _this, jint vpf, jint slot, jint a)
+nProgramBindTexture(JNIEnv *_env, jobject _this, RsContext con, jint vpf, jint slot, jint a)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nProgramBindTexture, con(%p), vpf(%p), slot(%i), a(%p)", con, (RsProgramFragment)vpf, slot, (RsAllocation)a);
     rsProgramBindTexture(con, (RsProgramFragment)vpf, slot, (RsAllocation)a);
 }
 
 static void
-nProgramBindSampler(JNIEnv *_env, jobject _this, jint vpf, jint slot, jint a)
+nProgramBindSampler(JNIEnv *_env, jobject _this, RsContext con, jint vpf, jint slot, jint a)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nProgramBindSampler, con(%p), vpf(%p), slot(%i), a(%p)", con, (RsProgramFragment)vpf, slot, (RsSampler)a);
     rsProgramBindSampler(con, (RsProgramFragment)vpf, slot, (RsSampler)a);
 }
@@ -1110,31 +997,16 @@
 // ---------------------------------------------------------------------------
 
 static jint
-nProgramFragmentCreate(JNIEnv *_env, jobject _this, jintArray params)
+nProgramFragmentCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    jint *paramPtr = _env->GetIntArrayElements(params, NULL);
-    jint paramLen = _env->GetArrayLength(params);
-
-    LOG_API("nProgramFragmentCreate, con(%p), paramLen(%i)", con, paramLen);
-
-    jint ret = (jint)rsProgramFragmentCreate(con, (uint32_t *)paramPtr, paramLen);
-    _env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT);
-    return ret;
-}
-
-static jint
-nProgramFragmentCreate2(JNIEnv *_env, jobject _this, jstring shader, jintArray params)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     const char* shaderUTF = _env->GetStringUTFChars(shader, NULL);
     jint shaderLen = _env->GetStringUTFLength(shader);
     jint *paramPtr = _env->GetIntArrayElements(params, NULL);
     jint paramLen = _env->GetArrayLength(params);
 
-    LOG_API("nProgramFragmentCreate2, con(%p), shaderLen(%i), paramLen(%i)", con, shaderLen, paramLen);
+    LOG_API("nProgramFragmentCreate, con(%p), shaderLen(%i), paramLen(%i)", con, shaderLen, paramLen);
 
-    jint ret = (jint)rsProgramFragmentCreate2(con, shaderUTF, shaderLen, (uint32_t *)paramPtr, paramLen);
+    jint ret = (jint)rsProgramFragmentCreate(con, shaderUTF, shaderLen, (uint32_t *)paramPtr, paramLen);
     _env->ReleaseStringUTFChars(shader, shaderUTF);
     _env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT);
     return ret;
@@ -1144,25 +1016,16 @@
 // ---------------------------------------------------------------------------
 
 static jint
-nProgramVertexCreate(JNIEnv *_env, jobject _this, jboolean texMat)
+nProgramVertexCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramVertexCreate, con(%p), texMat(%i)", con, texMat);
-    return (jint)rsProgramVertexCreate(con, texMat);
-}
-
-static jint
-nProgramVertexCreate2(JNIEnv *_env, jobject _this, jstring shader, jintArray params)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     const char* shaderUTF = _env->GetStringUTFChars(shader, NULL);
     jint shaderLen = _env->GetStringUTFLength(shader);
     jint *paramPtr = _env->GetIntArrayElements(params, NULL);
     jint paramLen = _env->GetArrayLength(params);
 
-    LOG_API("nProgramVertexCreate2, con(%p), shaderLen(%i), paramLen(%i)", con, shaderLen, paramLen);
+    LOG_API("nProgramVertexCreate, con(%p), shaderLen(%i), paramLen(%i)", con, shaderLen, paramLen);
 
-    jint ret = (jint)rsProgramVertexCreate2(con, shaderUTF, shaderLen, (uint32_t *)paramPtr, paramLen);
+    jint ret = (jint)rsProgramVertexCreate(con, shaderUTF, shaderLen, (uint32_t *)paramPtr, paramLen);
     _env->ReleaseStringUTFChars(shader, shaderUTF);
     _env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT);
     return ret;
@@ -1171,70 +1034,61 @@
 // ---------------------------------------------------------------------------
 
 static jint
-nProgramRasterCreate(JNIEnv *_env, jobject _this, jint in, jint out,
-                     jboolean pointSmooth, jboolean lineSmooth, jboolean pointSprite)
+nProgramRasterCreate(JNIEnv *_env, jobject _this, RsContext con, 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);
+    LOG_API("nProgramRasterCreate, con(%p), pointSmooth(%i), lineSmooth(%i), pointSprite(%i)",
+            con, pointSmooth, lineSmooth, pointSprite);
+    return (jint)rsProgramRasterCreate(con, pointSmooth, lineSmooth, pointSprite);
 }
 
 static void
-nProgramRasterSetPointSize(JNIEnv *_env, jobject _this, jint vpr, jfloat v)
+nProgramRasterSetLineWidth(JNIEnv *_env, jobject _this, RsContext con, 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);
-}
-
-static void
-nProgramRasterSetLineWidth(JNIEnv *_env, jobject _this, jint vpr, jfloat v)
-{
-    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, RsContext con, jint vpr, jint v)
+{
+    LOG_API("nProgramRasterSetCullMode, con(%p), vpf(%p), value(%i)", con, (RsProgramRaster)vpr, v);
+    rsProgramRasterSetCullMode(con, (RsProgramRaster)vpr, (RsCullMode)v);
 }
 
 
 // ---------------------------------------------------------------------------
 
 static void
-nContextBindRootScript(JNIEnv *_env, jobject _this, jint script)
+nContextBindRootScript(JNIEnv *_env, jobject _this, RsContext con, jint script)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextBindRootScript, con(%p), script(%p)", con, (RsScript)script);
     rsContextBindRootScript(con, (RsScript)script);
 }
 
 static void
-nContextBindProgramFragmentStore(JNIEnv *_env, jobject _this, jint pfs)
+nContextBindProgramStore(JNIEnv *_env, jobject _this, RsContext con, 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
-nContextBindProgramFragment(JNIEnv *_env, jobject _this, jint pf)
+nContextBindProgramFragment(JNIEnv *_env, jobject _this, RsContext con, jint pf)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextBindProgramFragment, con(%p), pf(%p)", con, (RsProgramFragment)pf);
     rsContextBindProgramFragment(con, (RsProgramFragment)pf);
 }
 
 static void
-nContextBindProgramVertex(JNIEnv *_env, jobject _this, jint pf)
+nContextBindProgramVertex(JNIEnv *_env, jobject _this, RsContext con, jint pf)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextBindProgramVertex, con(%p), pf(%p)", con, (RsProgramVertex)pf);
     rsContextBindProgramVertex(con, (RsProgramVertex)pf);
 }
 
 static void
-nContextBindProgramRaster(JNIEnv *_env, jobject _this, jint pf)
+nContextBindProgramRaster(JNIEnv *_env, jobject _this, RsContext con, jint pf)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nContextBindProgramRaster, con(%p), pf(%p)", con, (RsProgramRaster)pf);
     rsContextBindProgramRaster(con, (RsProgramRaster)pf);
 }
@@ -1243,108 +1097,107 @@
 // ---------------------------------------------------------------------------
 
 static void
-nSamplerBegin(JNIEnv *_env, jobject _this)
+nSamplerBegin(JNIEnv *_env, jobject _this, RsContext con)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nSamplerBegin, con(%p)", con);
     rsSamplerBegin(con);
 }
 
 static void
-nSamplerSet(JNIEnv *_env, jobject _this, jint p, jint v)
+nSamplerSet(JNIEnv *_env, jobject _this, RsContext con, jint p, jint v)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nSamplerSet, con(%p), param(%i), value(%i)", con, p, v);
     rsSamplerSet(con, (RsSamplerParam)p, (RsSamplerValue)v);
 }
 
-static jint
-nSamplerCreate(JNIEnv *_env, jobject _this)
+static void
+nSamplerSet2(JNIEnv *_env, jobject _this, RsContext con, jint p, jfloat v)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nSamplerSet2, con(%p), param(%i), value(%f)", con, p, v);
+    rsSamplerSet2(con, (RsSamplerParam)p, v);
+}
+
+static jint
+nSamplerCreate(JNIEnv *_env, jobject _this, RsContext con)
+{
     LOG_API("nSamplerCreate, con(%p)", con);
     return (jint)rsSamplerCreate(con);
 }
 
 // ---------------------------------------------------------------------------
 
-static void
-nLightBegin(JNIEnv *_env, jobject _this)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nLightBegin, con(%p)", con);
-    rsLightBegin(con);
-}
-
-static void
-nLightSetIsMono(JNIEnv *_env, jobject _this, jboolean isMono)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nLightSetIsMono, con(%p), isMono(%i)", con, isMono);
-    rsLightSetMonochromatic(con, isMono);
-}
-
-static void
-nLightSetIsLocal(JNIEnv *_env, jobject _this, jboolean isLocal)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nLightSetIsLocal, con(%p), isLocal(%i)", con, isLocal);
-    rsLightSetLocal(con, isLocal);
-}
-
 static jint
-nLightCreate(JNIEnv *_env, jobject _this)
+nMeshCreate(JNIEnv *_env, jobject _this, RsContext con, jint vtxCount, jint idxCount)
 {
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nLightCreate, con(%p)", con);
-    return (jint)rsLightCreate(con);
-}
-
-static void
-nLightSetColor(JNIEnv *_env, jobject _this, jint light, float r, float g, float b)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nLightSetColor, con(%p), light(%p), r(%f), g(%f), b(%f)", con, (RsLight)light, r, g, b);
-    rsLightSetColor(con, (RsLight)light, r, g, b);
-}
-
-static void
-nLightSetPosition(JNIEnv *_env, jobject _this, jint light, float x, float y, float z)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nLightSetPosition, con(%p), light(%p), x(%f), y(%f), z(%f)", con, (RsLight)light, x, y, z);
-    rsLightSetPosition(con, (RsLight)light, x, y, z);
-}
-
-// ---------------------------------------------------------------------------
-
-static jint
-nSimpleMeshCreate(JNIEnv *_env, jobject _this, jint batchID, jint indexID, jintArray vtxIDs, jint primID)
-{
-    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, RsContext con, 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, RsContext con, 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, RsContext con, jint mesh)
+{
+    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, RsContext con, jint mesh)
+{
+    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, RsContext con, jint mesh, jintArray _ids, int numVtxIDs)
+{
+    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, RsContext con, jint mesh, jintArray _idxIds, jintArray _primitives, int numIndices)
+{
+    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);
 }
 
 // ---------------------------------------------------------------------------
@@ -1359,124 +1212,131 @@
 {"nDeviceCreate",                  "()I",                                  (void*)nDeviceCreate },
 {"nDeviceDestroy",                 "(I)V",                                 (void*)nDeviceDestroy },
 {"nDeviceSetConfig",               "(III)V",                               (void*)nDeviceSetConfig },
-{"nContextCreate",                 "(II)I",                                (void*)nContextCreate },
-{"nContextCreateGL",               "(IIZ)I",                               (void*)nContextCreateGL },
-{"nContextSetPriority",            "(I)V",                                 (void*)nContextSetPriority },
-{"nContextSetSurface",             "(IILandroid/view/Surface;)V",          (void*)nContextSetSurface },
-{"nContextDestroy",                "(I)V",                                 (void*)nContextDestroy },
-{"nContextDump",                   "(I)V",                                 (void*)nContextDump },
-{"nContextPause",                  "()V",                                  (void*)nContextPause },
-{"nContextResume",                 "()V",                                  (void*)nContextResume },
-{"nAssignName",                    "(I[B)V",                               (void*)nAssignName },
-{"nObjDestroy",                    "(I)V",                                 (void*)nObjDestroy },
-{"nObjDestroyOOB",                 "(I)V",                                 (void*)nObjDestroyOOB },
-{"nContextGetMessage",             "([IZ)I",                               (void*)nContextGetMessage },
-{"nContextInitToClient",           "()V",                                  (void*)nContextInitToClient },
-{"nContextDeinitToClient",         "()V",                                  (void*)nContextDeinitToClient },
+{"nContextGetMessage",             "(I[IZ)I",                               (void*)nContextGetMessage },
+{"nContextInitToClient",           "(I)V",                                  (void*)nContextInitToClient },
+{"nContextDeinitToClient",         "(I)V",                                  (void*)nContextDeinitToClient },
 
-{"nFileOpen",                      "([B)I",                                (void*)nFileOpen },
 
-{"nElementCreate",                 "(IIZI)I",                              (void*)nElementCreate },
-{"nElementCreate2",                "([I[Ljava/lang/String;)I",             (void*)nElementCreate2 },
+// All methods below are thread protected in java.
+{"rsnContextCreate",                 "(II)I",                                 (void*)nContextCreate },
+{"rsnContextCreateGL",               "(IIZ)I",                                (void*)nContextCreateGL },
+{"rsnContextFinish",                 "(I)V",                                  (void*)nContextFinish },
+{"rsnContextSetPriority",            "(II)V",                                 (void*)nContextSetPriority },
+{"rsnContextSetSurface",             "(IIILandroid/view/Surface;)V",          (void*)nContextSetSurface },
+{"rsnContextDestroy",                "(I)V",                                  (void*)nContextDestroy },
+{"rsnContextDump",                   "(II)V",                                 (void*)nContextDump },
+{"rsnContextPause",                  "(I)V",                                  (void*)nContextPause },
+{"rsnContextResume",                 "(I)V",                                  (void*)nContextResume },
+{"rsnAssignName",                    "(II[B)V",                               (void*)nAssignName },
+{"rsnGetName",                       "(II)Ljava/lang/String;",                (void*)nGetName },
+{"rsnObjDestroy",                    "(II)V",                                 (void*)nObjDestroy },
 
-{"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 },
+{"rsnFileOpen",                      "(I[B)I",                                (void*)nFileOpen },
+{"rsnFileA3DCreateFromAssetStream",  "(II)I",                                 (void*)nFileA3DCreateFromAssetStream },
+{"rsnFileA3DGetNumIndexEntries",     "(II)I",                                 (void*)nFileA3DGetNumIndexEntries },
+{"rsnFileA3DGetIndexEntries",        "(III[I[Ljava/lang/String;)V",           (void*)nFileA3DGetIndexEntries },
+{"rsnFileA3DGetEntryByIndex",        "(III)I",                                (void*)nFileA3DGetEntryByIndex },
 
-{"nAllocationCreateTyped",         "(I)I",                                 (void*)nAllocationCreateTyped },
-{"nAllocationCreateFromBitmap",    "(IZLandroid/graphics/Bitmap;)I",       (void*)nAllocationCreateFromBitmap },
-{"nAllocationCreateBitmapRef",     "(ILandroid/graphics/Bitmap;)I",        (void*)nAllocationCreateBitmapRef },
-{"nAllocationCreateFromBitmapBoxed","(IZLandroid/graphics/Bitmap;)I",      (void*)nAllocationCreateFromBitmapBoxed },
-{"nAllocationCreateFromAssetStream","(IZI)I",                              (void*)nAllocationCreateFromAssetStream },
-{"nAllocationUploadToTexture",     "(IZI)V",                               (void*)nAllocationUploadToTexture },
-{"nAllocationUploadToBufferObject","(I)V",                                 (void*)nAllocationUploadToBufferObject },
-{"nAllocationSubData1D",           "(III[II)V",                            (void*)nAllocationSubData1D_i },
-{"nAllocationSubData1D",           "(III[SI)V",                            (void*)nAllocationSubData1D_s },
-{"nAllocationSubData1D",           "(III[BI)V",                            (void*)nAllocationSubData1D_b },
-{"nAllocationSubData1D",           "(III[FI)V",                            (void*)nAllocationSubData1D_f },
-{"nAllocationSubData2D",           "(IIIII[II)V",                          (void*)nAllocationSubData2D_i },
-{"nAllocationSubData2D",           "(IIIII[FI)V",                          (void*)nAllocationSubData2D_f },
-{"nAllocationRead",                "(I[I)V",                               (void*)nAllocationRead_i },
-{"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 },
+{"rsnFontCreateFromFile",            "(ILjava/lang/String;II)I",              (void*)nFontCreateFromFile },
 
-{"nAdapter1DBindAllocation",       "(II)V",                                (void*)nAdapter1DBindAllocation },
-{"nAdapter1DSetConstraint",        "(III)V",                               (void*)nAdapter1DSetConstraint },
-{"nAdapter1DData",                 "(I[I)V",                               (void*)nAdapter1DData_i },
-{"nAdapter1DData",                 "(I[F)V",                               (void*)nAdapter1DData_f },
-{"nAdapter1DSubData",              "(III[I)V",                             (void*)nAdapter1DSubData_i },
-{"nAdapter1DSubData",              "(III[F)V",                             (void*)nAdapter1DSubData_f },
-{"nAdapter1DCreate",               "()I",                                  (void*)nAdapter1DCreate },
+{"rsnElementCreate",                 "(IIIZI)I",                              (void*)nElementCreate },
+{"rsnElementCreate2",                "(I[I[Ljava/lang/String;[I)I",           (void*)nElementCreate2 },
+{"rsnElementGetNativeData",          "(II[I)V",                               (void*)nElementGetNativeData },
+{"rsnElementGetSubElements",         "(II[I[Ljava/lang/String;)V",            (void*)nElementGetSubElements },
 
-{"nAdapter2DBindAllocation",       "(II)V",                                (void*)nAdapter2DBindAllocation },
-{"nAdapter2DSetConstraint",        "(III)V",                               (void*)nAdapter2DSetConstraint },
-{"nAdapter2DData",                 "(I[I)V",                               (void*)nAdapter2DData_i },
-{"nAdapter2DData",                 "(I[F)V",                               (void*)nAdapter2DData_f },
-{"nAdapter2DSubData",              "(IIIII[I)V",                           (void*)nAdapter2DSubData_i },
-{"nAdapter2DSubData",              "(IIIII[F)V",                           (void*)nAdapter2DSubData_f },
-{"nAdapter2DCreate",               "()I",                                  (void*)nAdapter2DCreate },
+{"rsnTypeBegin",                     "(II)V",                                 (void*)nTypeBegin },
+{"rsnTypeAdd",                       "(III)V",                                (void*)nTypeAdd },
+{"rsnTypeCreate",                    "(I)I",                                  (void*)nTypeCreate },
+{"rsnTypeGetNativeData",             "(II[I)V",                               (void*)nTypeGetNativeData },
 
-{"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 },
+{"rsnAllocationCreateTyped",         "(II)I",                                 (void*)nAllocationCreateTyped },
+{"rsnAllocationCreateFromBitmap",    "(IIZLandroid/graphics/Bitmap;)I",       (void*)nAllocationCreateFromBitmap },
+{"rsnAllocationCreateBitmapRef",     "(IILandroid/graphics/Bitmap;)I",        (void*)nAllocationCreateBitmapRef },
+{"rsnAllocationCreateFromBitmapBoxed","(IIZLandroid/graphics/Bitmap;)I",      (void*)nAllocationCreateFromBitmapBoxed },
+{"rsnAllocationCreateFromAssetStream","(IIZI)I",                              (void*)nAllocationCreateFromAssetStream },
+{"rsnAllocationUploadToTexture",     "(IIZI)V",                               (void*)nAllocationUploadToTexture },
+{"rsnAllocationUploadToBufferObject","(II)V",                                 (void*)nAllocationUploadToBufferObject },
+{"rsnAllocationSubData1D",           "(IIII[II)V",                            (void*)nAllocationSubData1D_i },
+{"rsnAllocationSubData1D",           "(IIII[SI)V",                            (void*)nAllocationSubData1D_s },
+{"rsnAllocationSubData1D",           "(IIII[BI)V",                            (void*)nAllocationSubData1D_b },
+{"rsnAllocationSubData1D",           "(IIII[FI)V",                            (void*)nAllocationSubData1D_f },
+{"rsnAllocationSubElementData1D",    "(IIII[BI)V",                            (void*)nAllocationSubElementData1D },
+{"rsnAllocationSubData2D",           "(IIIIII[II)V",                          (void*)nAllocationSubData2D_i },
+{"rsnAllocationSubData2D",           "(IIIIII[FI)V",                          (void*)nAllocationSubData2D_f },
+{"rsnAllocationRead",                "(II[I)V",                               (void*)nAllocationRead_i },
+{"rsnAllocationRead",                "(II[F)V",                               (void*)nAllocationRead_f },
+{"rsnAllocationGetType",             "(II)I",                                 (void*)nAllocationGetType},
+{"rsnAllocationResize1D",            "(III)V",                                (void*)nAllocationResize1D },
+{"rsnAllocationResize2D",            "(IIII)V",                               (void*)nAllocationResize2D },
 
-{"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 },
+{"rsnAdapter1DBindAllocation",       "(III)V",                                (void*)nAdapter1DBindAllocation },
+{"rsnAdapter1DSetConstraint",        "(IIII)V",                               (void*)nAdapter1DSetConstraint },
+{"rsnAdapter1DData",                 "(II[I)V",                               (void*)nAdapter1DData_i },
+{"rsnAdapter1DData",                 "(II[F)V",                               (void*)nAdapter1DData_f },
+{"rsnAdapter1DSubData",              "(IIII[I)V",                             (void*)nAdapter1DSubData_i },
+{"rsnAdapter1DSubData",              "(IIII[F)V",                             (void*)nAdapter1DSubData_f },
+{"rsnAdapter1DCreate",               "(I)I",                                  (void*)nAdapter1DCreate },
 
-{"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 },
+{"rsnAdapter2DBindAllocation",       "(III)V",                                (void*)nAdapter2DBindAllocation },
+{"rsnAdapter2DSetConstraint",        "(IIII)V",                               (void*)nAdapter2DSetConstraint },
+{"rsnAdapter2DData",                 "(II[I)V",                               (void*)nAdapter2DData_i },
+{"rsnAdapter2DData",                 "(II[F)V",                               (void*)nAdapter2DData_f },
+{"rsnAdapter2DSubData",              "(IIIIII[I)V",                           (void*)nAdapter2DSubData_i },
+{"rsnAdapter2DSubData",              "(IIIIII[F)V",                           (void*)nAdapter2DSubData_f },
+{"rsnAdapter2DCreate",               "(I)I",                                  (void*)nAdapter2DCreate },
 
-{"nProgramBindConstants",          "(III)V",                               (void*)nProgramBindConstants },
-{"nProgramBindTexture",            "(III)V",                               (void*)nProgramBindTexture },
-{"nProgramBindSampler",            "(III)V",                               (void*)nProgramBindSampler },
+{"rsnScriptBindAllocation",          "(IIII)V",                               (void*)nScriptBindAllocation },
+{"rsnScriptSetTimeZone",             "(II[B)V",                               (void*)nScriptSetTimeZone },
+{"rsnScriptInvoke",                  "(III)V",                                (void*)nScriptInvoke },
+{"rsnScriptInvokeV",                 "(III[B)V",                              (void*)nScriptInvokeV },
+{"rsnScriptSetVarI",                 "(IIII)V",                               (void*)nScriptSetVarI },
+{"rsnScriptSetVarF",                 "(IIIF)V",                               (void*)nScriptSetVarF },
+{"rsnScriptSetVarD",                 "(IIID)V",                               (void*)nScriptSetVarD },
+{"rsnScriptSetVarV",                 "(III[B)V",                              (void*)nScriptSetVarV },
 
-{"nProgramFragmentCreate",         "([I)I",                                (void*)nProgramFragmentCreate },
-{"nProgramFragmentCreate2",        "(Ljava/lang/String;[I)I",              (void*)nProgramFragmentCreate2 },
+{"rsnScriptCBegin",                  "(I)V",                                  (void*)nScriptCBegin },
+{"rsnScriptCSetScript",              "(I[BII)V",                              (void*)nScriptCSetScript },
+{"rsnScriptCCreate",                 "(I)I",                                  (void*)nScriptCCreate },
 
-{"nProgramRasterCreate",           "(IIZZZ)I",                             (void*)nProgramRasterCreate },
-{"nProgramRasterSetPointSize",     "(IF)V",                                (void*)nProgramRasterSetPointSize },
-{"nProgramRasterSetLineWidth",     "(IF)V",                                (void*)nProgramRasterSetLineWidth },
+{"rsnProgramStoreBegin",             "(III)V",                                (void*)nProgramStoreBegin },
+{"rsnProgramStoreDepthFunc",         "(II)V",                                 (void*)nProgramStoreDepthFunc },
+{"rsnProgramStoreDepthMask",         "(IZ)V",                                 (void*)nProgramStoreDepthMask },
+{"rsnProgramStoreColorMask",         "(IZZZZ)V",                              (void*)nProgramStoreColorMask },
+{"rsnProgramStoreBlendFunc",         "(III)V",                                (void*)nProgramStoreBlendFunc },
+{"rsnProgramStoreDither",            "(IZ)V",                                 (void*)nProgramStoreDither },
+{"rsnProgramStoreCreate",            "(I)I",                                  (void*)nProgramStoreCreate },
 
-{"nProgramVertexCreate",           "(Z)I",                                 (void*)nProgramVertexCreate },
-{"nProgramVertexCreate2",          "(Ljava/lang/String;[I)I",              (void*)nProgramVertexCreate2 },
+{"rsnProgramBindConstants",          "(IIII)V",                               (void*)nProgramBindConstants },
+{"rsnProgramBindTexture",            "(IIII)V",                               (void*)nProgramBindTexture },
+{"rsnProgramBindSampler",            "(IIII)V",                               (void*)nProgramBindSampler },
 
-{"nLightBegin",                    "()V",                                  (void*)nLightBegin },
-{"nLightSetIsMono",                "(Z)V",                                 (void*)nLightSetIsMono },
-{"nLightSetIsLocal",               "(Z)V",                                 (void*)nLightSetIsLocal },
-{"nLightCreate",                   "()I",                                  (void*)nLightCreate },
-{"nLightSetColor",                 "(IFFF)V",                              (void*)nLightSetColor },
-{"nLightSetPosition",              "(IFFF)V",                              (void*)nLightSetPosition },
+{"rsnProgramFragmentCreate",        "(ILjava/lang/String;[I)I",               (void*)nProgramFragmentCreate },
 
-{"nContextBindRootScript",         "(I)V",                                 (void*)nContextBindRootScript },
-{"nContextBindProgramFragmentStore","(I)V",                                (void*)nContextBindProgramFragmentStore },
-{"nContextBindProgramFragment",    "(I)V",                                 (void*)nContextBindProgramFragment },
-{"nContextBindProgramVertex",      "(I)V",                                 (void*)nContextBindProgramVertex },
-{"nContextBindProgramRaster",      "(I)V",                                 (void*)nContextBindProgramRaster },
+{"rsnProgramRasterCreate",           "(IZZZ)I",                               (void*)nProgramRasterCreate },
+{"rsnProgramRasterSetLineWidth",     "(IIF)V",                                (void*)nProgramRasterSetLineWidth },
+{"rsnProgramRasterSetCullMode",      "(III)V",                                (void*)nProgramRasterSetCullMode },
 
-{"nSamplerBegin",                  "()V",                                  (void*)nSamplerBegin },
-{"nSamplerSet",                    "(II)V",                                (void*)nSamplerSet },
-{"nSamplerCreate",                 "()I",                                  (void*)nSamplerCreate },
+{"rsnProgramVertexCreate",          "(ILjava/lang/String;[I)I",               (void*)nProgramVertexCreate },
 
-{"nSimpleMeshCreate",              "(II[II)I",                             (void*)nSimpleMeshCreate },
-{"nSimpleMeshBindVertex",          "(III)V",                               (void*)nSimpleMeshBindVertex },
-{"nSimpleMeshBindIndex",           "(II)V",                                (void*)nSimpleMeshBindIndex },
+{"rsnContextBindRootScript",         "(II)V",                                 (void*)nContextBindRootScript },
+{"rsnContextBindProgramStore",       "(II)V",                                 (void*)nContextBindProgramStore },
+{"rsnContextBindProgramFragment",    "(II)V",                                 (void*)nContextBindProgramFragment },
+{"rsnContextBindProgramVertex",      "(II)V",                                 (void*)nContextBindProgramVertex },
+{"rsnContextBindProgramRaster",      "(II)V",                                 (void*)nContextBindProgramRaster },
+
+{"rsnSamplerBegin",                  "(I)V",                                  (void*)nSamplerBegin },
+{"rsnSamplerSet",                    "(III)V",                                (void*)nSamplerSet },
+{"rsnSamplerSet2",                   "(IIF)V",                                (void*)nSamplerSet2 },
+{"rsnSamplerCreate",                 "(I)I",                                  (void*)nSamplerCreate },
+
+{"rsnMeshCreate",                    "(III)I",                                (void*)nMeshCreate },
+{"rsnMeshBindVertex",                "(IIII)V",                               (void*)nMeshBindVertex },
+{"rsnMeshBindIndex",                 "(IIIII)V",                              (void*)nMeshBindIndex },
+
+{"rsnMeshGetVertexBufferCount",      "(II)I",                                 (void*)nMeshGetVertexBufferCount },
+{"rsnMeshGetIndexCount",             "(II)I",                                 (void*)nMeshGetIndexCount },
+{"rsnMeshGetVertices",               "(II[II)V",                              (void*)nMeshGetVertices },
+{"rsnMeshGetIndices",                "(II[I[II)V",                            (void*)nMeshGetIndices },
 
 };
 
@@ -1510,3 +1370,4 @@
 bail:
     return result;
 }
+
diff --git a/graphics/tests/graphicstests/src/android/graphics/BitmapFactoryTest.java b/graphics/tests/graphicstests/src/android/graphics/BitmapFactoryTest.java
new file mode 100644
index 0000000..09820ef
--- /dev/null
+++ b/graphics/tests/graphicstests/src/android/graphics/BitmapFactoryTest.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.graphics;
+
+import android.os.ParcelFileDescriptor;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+
+import junit.framework.TestCase;
+
+
+public class BitmapFactoryTest extends TestCase {
+
+    // tests that we can decode bitmaps from MemoryFiles
+    @SmallTest
+    public void testBitmapParcelFileDescriptor() throws Exception {
+        Bitmap bitmap1 = Bitmap.createBitmap(
+                new int[] { Color.BLUE }, 1, 1, Bitmap.Config.RGB_565);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        bitmap1.compress(Bitmap.CompressFormat.PNG, 100, out);
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromData(out.toByteArray(), null);
+        FileDescriptor fd = pfd.getFileDescriptor();
+        assertNotNull("Got null FileDescriptor", fd);
+        assertTrue("Got invalid FileDescriptor", fd.valid());
+        Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
+        assertNotNull("BitmapFactory returned null", bitmap);
+        assertEquals("Bitmap width", 1, bitmap.getWidth());
+        assertEquals("Bitmap height", 1, bitmap.getHeight());
+    }
+
+}
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/Camera.h b/include/camera/Camera.h
index 964700b..f7b3b42 100644
--- a/include/camera/Camera.h
+++ b/include/camera/Camera.h
@@ -22,8 +22,6 @@
 
 namespace android {
 
-class ISurface;
-
 /*
  * A set of bit masks for specifying how the received preview frames are
  * handled before the previewCallback() call.
@@ -84,6 +82,14 @@
     CAMERA_CMD_START_SMOOTH_ZOOM     = 1,
     CAMERA_CMD_STOP_SMOOTH_ZOOM      = 2,
     CAMERA_CMD_SET_DISPLAY_ORIENTATION = 3,
+
+    // cmdType to disable/enable shutter sound.
+    // In sendCommand passing arg1 = 0 will disable,
+    // while passing arg1 = 1 will enable the shutter sound.
+    CAMERA_CMD_ENABLE_SHUTTER_SOUND = 4,
+
+    // cmdType to play recording sound.
+    CAMERA_CMD_PLAY_RECORDING_SOUND = 5,
 };
 
 // camera fatal errors
@@ -152,9 +158,8 @@
 
             status_t    getStatus() { return mStatus; }
 
-            // pass the buffered ISurface to the camera service
+            // pass the buffered Surface to the camera service
             status_t    setPreviewDisplay(const sp<Surface>& surface);
-            status_t    setPreviewDisplay(const sp<ISurface>& surface);
 
             // start preview mode, must call setPreviewDisplay first
             status_t    startPreview();
diff --git a/include/camera/CameraHardwareInterface.h b/include/camera/CameraHardwareInterface.h
index 6a66e3c..3a77dd1 100644
--- a/include/camera/CameraHardwareInterface.h
+++ b/include/camera/CameraHardwareInterface.h
@@ -18,6 +18,7 @@
 #define ANDROID_HARDWARE_CAMERA_HARDWARE_INTERFACE_H
 
 #include <binder/IMemory.h>
+#include <ui/egl/android_natives.h>
 #include <utils/RefBase.h>
 #include <surfaceflinger/ISurface.h>
 #include <camera/Camera.h>
@@ -86,8 +87,8 @@
 public:
     virtual ~CameraHardwareInterface() { }
 
-    /** Return the IMemoryHeap for the preview image heap */
-    virtual sp<IMemoryHeap>         getPreviewHeap() const = 0;
+    /** Set the ANativeWindow to which preview frames are sent */
+    virtual status_t setPreviewWindow(const sp<ANativeWindow>& buf) = 0;
 
     /** Return the IMemoryHeap for the raw image heap */
     virtual sp<IMemoryHeap>         getRawHeap() const = 0;
diff --git a/include/camera/CameraParameters.h b/include/camera/CameraParameters.h
index 53039a0..60031a4 100644
--- a/include/camera/CameraParameters.h
+++ b/include/camera/CameraParameters.h
@@ -59,6 +59,27 @@
     void setPreviewSize(int width, int height);
     void getPreviewSize(int *width, int *height) const;
     void getSupportedPreviewSizes(Vector<Size> &sizes) const;
+
+    // Set the dimensions in pixels to the given width and height
+    // for video frames. The given width and height must be one
+    // of the supported dimensions returned from
+    // getSupportedVideoSizes(). Must not be called if
+    // getSupportedVideoSizes() returns an empty Vector of Size.
+    void setVideoSize(int width, int height);
+    // Retrieve the current dimensions (width and height)
+    // in pixels for video frames, which must be one of the
+    // supported dimensions returned from getSupportedVideoSizes().
+    // Must not be called if getSupportedVideoSizes() returns an
+    // empty Vector of Size.
+    void getVideoSize(int *width, int *height) const;
+    // Retrieve a Vector of supported dimensions (width and height)
+    // in pixels for video frames. If sizes returned from the method
+    // is empty, the camera does not support calls to setVideoSize()
+    // or getVideoSize(). In adddition, it also indicates that
+    // the camera only has a single output, and does not have
+    // separate output for video frames and preview frame.
+    void getSupportedVideoSizes(Vector<Size> &sizes) const;
+
     void setPreviewFrameRate(int fps);
     int getPreviewFrameRate() const;
     void getPreviewFpsRange(int *min_fps, int *max_fps) const;
@@ -281,6 +302,16 @@
     // Example value: "0.95,1.9,Infinity" or "0.049,0.05,0.051". Read only.
     static const char KEY_FOCUS_DISTANCES[];
 
+    // The current dimensions in pixels (width x height) for video frames.
+    // The width and height must be one of the supported sizes retrieved
+    // via KEY_SUPPORTED_VIDEO_SIZES.
+    // Example value: "1280x720". Read/write.
+    static const char KEY_VIDEO_SIZE[];
+    // A list of the supported dimensions in pixels (width x height)
+    // for video frames. See CAMERA_MSG_VIDEO_FRAME for details in
+    // frameworks/base/include/camera/Camera.h.
+    // Example: "176x144,1280x720". Read only.
+    static const char KEY_SUPPORTED_VIDEO_SIZES[];
     // The image format for video frames. See CAMERA_MSG_VIDEO_FRAME in
     // frameworks/base/include/camera/Camera.h.
     // Example value: "yuv420sp" or PIXEL_FORMAT_XXX constants. Read only.
@@ -354,7 +385,10 @@
     // for barcode reading.
     static const char SCENE_MODE_BARCODE[];
 
-    // Formats for setPreviewFormat and setPictureFormat.
+    // Pixel color formats for KEY_PREVIEW_FORMAT, KEY_PICTURE_FORMAT,
+    // and KEY_VIDEO_FRAME_FORMAT
+    // Planar variant of the YUV420 color format
+    static const char PIXEL_FORMAT_YUV420P[];
     static const char PIXEL_FORMAT_YUV422SP[];
     static const char PIXEL_FORMAT_YUV420SP[]; // NV21
     static const char PIXEL_FORMAT_YUV422I[]; // YUY2
diff --git a/include/camera/ICamera.h b/include/camera/ICamera.h
index 6fcf9e5..8bceea5 100644
--- a/include/camera/ICamera.h
+++ b/include/camera/ICamera.h
@@ -20,7 +20,7 @@
 #include <utils/RefBase.h>
 #include <binder/IInterface.h>
 #include <binder/Parcel.h>
-#include <surfaceflinger/ISurface.h>
+#include <surfaceflinger/Surface.h>
 #include <binder/IMemory.h>
 #include <utils/String8.h>
 #include <camera/Camera.h>
@@ -45,8 +45,8 @@
     // allow other processes to use this ICamera interface
     virtual status_t        unlock() = 0;
 
-    // pass the buffered ISurface to the camera service
-    virtual status_t        setPreviewDisplay(const sp<ISurface>& surface) = 0;
+    // pass the buffered Surface to the camera service
+    virtual status_t        setPreviewDisplay(const sp<Surface>& surface) = 0;
 
     // set the preview callback flag to affect how the received frames from
     // preview are handled.
diff --git a/include/media/IMediaPlayer.h b/include/media/IMediaPlayer.h
index af9a7ed..a1ce113 100644
--- a/include/media/IMediaPlayer.h
+++ b/include/media/IMediaPlayer.h
@@ -25,6 +25,7 @@
 
 class Parcel;
 class ISurface;
+class Surface;
 
 class IMediaPlayer: public IInterface
 {
@@ -33,7 +34,8 @@
 
     virtual void            disconnect() = 0;
 
-    virtual status_t        setVideoSurface(const sp<ISurface>& surface) = 0;
+    virtual status_t        setVideoISurface(const sp<ISurface>& surface) = 0;
+    virtual status_t        setVideoSurface(const sp<Surface>& surface) = 0;
     virtual status_t        prepareAsync() = 0;
     virtual status_t        start() = 0;
     virtual status_t        stop() = 0;
diff --git a/include/media/IMediaRecorder.h b/include/media/IMediaRecorder.h
index 54adca8..28be7c1 100644
--- a/include/media/IMediaRecorder.h
+++ b/include/media/IMediaRecorder.h
@@ -22,7 +22,7 @@
 
 namespace android {
 
-class ISurface;
+class Surface;
 class ICamera;
 class IMediaRecorderClient;
 
@@ -32,7 +32,7 @@
     DECLARE_META_INTERFACE(MediaRecorder);
 
     virtual	status_t		setCamera(const sp<ICamera>& camera) = 0;
-    virtual	status_t		setPreviewSurface(const sp<ISurface>& surface) = 0;
+    virtual	status_t		setPreviewSurface(const sp<Surface>& surface) = 0;
     virtual	status_t		setVideoSource(int vs) = 0;
     virtual	status_t		setAudioSource(int as) = 0;
     virtual	status_t		setOutputFormat(int of) = 0;
@@ -40,6 +40,7 @@
     virtual	status_t		setAudioEncoder(int ae) = 0;
     virtual	status_t		setOutputFile(const char* path) = 0;
     virtual	status_t		setOutputFile(int fd, int64_t offset, int64_t length) = 0;
+    virtual	status_t		setOutputFileAuxiliary(int fd) = 0;
     virtual	status_t		setVideoSize(int width, int height) = 0;
     virtual	status_t		setVideoFrameRate(int frames_per_second) = 0;
     virtual     status_t                setParameters(const String8& params) = 0;
@@ -68,4 +69,3 @@
 }; // namespace android
 
 #endif // ANDROID_IMEDIARECORDER_H
-
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index 2f61cbe..1f8ce71 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -19,6 +19,7 @@
 #define ANDROID_IOMX_H_
 
 #include <binder/IInterface.h>
+#include <ui/GraphicBuffer.h>
 #include <utils/List.h>
 #include <utils/String8.h>
 
@@ -78,10 +79,17 @@
             node_id node, OMX_INDEXTYPE index,
             const void *params, size_t size) = 0;
 
+    virtual status_t enableGraphicBuffers(
+            node_id node, OMX_U32 port_index, OMX_BOOL enable) = 0;
+
     virtual status_t useBuffer(
             node_id node, OMX_U32 port_index, const sp<IMemory> &params,
             buffer_id *buffer) = 0;
 
+    virtual status_t useGraphicBuffer(
+            node_id node, OMX_U32 port_index,
+            const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer) = 0;
+
     // This API clearly only makes sense if the caller lives in the
     // same process as the callee, i.e. is the media_server, as the
     // returned "buffer_data" pointer is just that, a pointer into local
diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h
index 3662983..13c73ac 100644
--- a/include/media/MediaPlayerInterface.h
+++ b/include/media/MediaPlayerInterface.h
@@ -33,6 +33,7 @@
 
 class Parcel;
 class ISurface;
+class Surface;
 
 template<typename T> class SortedVector;
 
@@ -104,7 +105,8 @@
             const KeyedVector<String8, String8> *headers = NULL) = 0;
 
     virtual status_t    setDataSource(int fd, int64_t offset, int64_t length) = 0;
-    virtual status_t    setVideoSurface(const sp<ISurface>& surface) = 0;
+    virtual status_t    setVideoISurface(const sp<ISurface>& surface) = 0;
+    virtual status_t    setVideoSurface(const sp<Surface>& surface) = 0;
     virtual status_t    prepare() = 0;
     virtual status_t    prepareAsync() = 0;
     virtual status_t    start() = 0;
diff --git a/include/media/MediaProfiles.h b/include/media/MediaProfiles.h
index c3cd361..aa97874 100644
--- a/include/media/MediaProfiles.h
+++ b/include/media/MediaProfiles.h
@@ -25,7 +25,20 @@
 
 enum camcorder_quality {
     CAMCORDER_QUALITY_LOW  = 0,
-    CAMCORDER_QUALITY_HIGH = 1
+    CAMCORDER_QUALITY_HIGH = 1,
+    CAMCORDER_QUALITY_QCIF = 2,
+    CAMCORDER_QUALITY_CIF = 3,
+    CAMCORDER_QUALITY_480P = 4,
+    CAMCORDER_QUALITY_720P = 5,
+    CAMCORDER_QUALITY_1080P = 6,
+
+    CAMCORDER_QUALITY_TIME_LAPSE_LOW  = 1000,
+    CAMCORDER_QUALITY_TIME_LAPSE_HIGH = 1001,
+    CAMCORDER_QUALITY_TIME_LAPSE_QCIF = 1002,
+    CAMCORDER_QUALITY_TIME_LAPSE_CIF = 1003,
+    CAMCORDER_QUALITY_TIME_LAPSE_480P = 1004,
+    CAMCORDER_QUALITY_TIME_LAPSE_720P = 1005,
+    CAMCORDER_QUALITY_TIME_LAPSE_1080P = 1006
 };
 
 enum video_decoder {
@@ -68,6 +81,12 @@
                                        camcorder_quality quality) const;
 
     /**
+     * Returns true if a profile for the given camera at the given quality exists,
+     * or false if not.
+     */
+    bool hasCamcorderProfile(int cameraId, camcorder_quality quality) const;
+
+    /**
      * Returns the output file formats supported.
      */
     Vector<output_format> getOutputFileFormats() const;
@@ -252,6 +271,8 @@
         Vector<int> mLevels;
     };
 
+    int getCamcorderProfileIndex(int cameraId, camcorder_quality quality) const;
+
     // Debug
     static void logVideoCodec(const VideoCodec& codec);
     static void logAudioCodec(const AudioCodec& codec);
@@ -281,8 +302,25 @@
 
     // If the xml configuration file does not exist, use hard-coded values
     static MediaProfiles* createDefaultInstance();
-    static CamcorderProfile *createDefaultCamcorderLowProfile();
-    static CamcorderProfile *createDefaultCamcorderHighProfile();
+
+    static CamcorderProfile *createDefaultCamcorderQcifProfile(camcorder_quality quality);
+    static CamcorderProfile *createDefaultCamcorderCifProfile(camcorder_quality quality);
+    static void createDefaultCamcorderLowProfiles(
+            MediaProfiles::CamcorderProfile **lowProfile,
+            MediaProfiles::CamcorderProfile **lowSpecificProfile);
+    static void createDefaultCamcorderHighProfiles(
+            MediaProfiles::CamcorderProfile **highProfile,
+            MediaProfiles::CamcorderProfile **highSpecificProfile);
+
+    static CamcorderProfile *createDefaultCamcorderTimeLapseQcifProfile(camcorder_quality quality);
+    static CamcorderProfile *createDefaultCamcorderTimeLapse480pProfile(camcorder_quality quality);
+    static void createDefaultCamcorderTimeLapseLowProfiles(
+            MediaProfiles::CamcorderProfile **lowTimeLapseProfile,
+            MediaProfiles::CamcorderProfile **lowSpecificTimeLapseProfile);
+    static void createDefaultCamcorderTimeLapseHighProfiles(
+            MediaProfiles::CamcorderProfile **highTimeLapseProfile,
+            MediaProfiles::CamcorderProfile **highSpecificTimeLapseProfile);
+
     static void createDefaultCamcorderProfiles(MediaProfiles *profiles);
     static void createDefaultVideoEncoders(MediaProfiles *profiles);
     static void createDefaultAudioEncoders(MediaProfiles *profiles);
diff --git a/include/media/MediaRecorderBase.h b/include/media/MediaRecorderBase.h
index 5e9e368..c42346e 100644
--- a/include/media/MediaRecorderBase.h
+++ b/include/media/MediaRecorderBase.h
@@ -22,7 +22,7 @@
 
 namespace android {
 
-class ISurface;
+class Surface;
 
 struct MediaRecorderBase {
     MediaRecorderBase() {}
@@ -37,9 +37,10 @@
     virtual status_t setVideoSize(int width, int height) = 0;
     virtual status_t setVideoFrameRate(int frames_per_second) = 0;
     virtual status_t setCamera(const sp<ICamera>& camera) = 0;
-    virtual status_t setPreviewSurface(const sp<ISurface>& surface) = 0;
+    virtual status_t setPreviewSurface(const sp<Surface>& surface) = 0;
     virtual status_t setOutputFile(const char *path) = 0;
     virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0;
+    virtual status_t setOutputFileAuxiliary(int fd) {return INVALID_OPERATION;}
     virtual status_t setParameters(const String8& params) = 0;
     virtual status_t setListener(const sp<IMediaRecorderClient>& listener) = 0;
     virtual status_t prepare() = 0;
diff --git a/include/media/PVMediaRecorder.h b/include/media/PVMediaRecorder.h
index c091c39..4b44ccc 100644
--- a/include/media/PVMediaRecorder.h
+++ b/include/media/PVMediaRecorder.h
@@ -23,7 +23,7 @@
 
 namespace android {
 
-class ISurface;
+class Surface;
 class ICamera;
 class AuthorDriverWrapper;
 
@@ -41,7 +41,7 @@
     virtual status_t setVideoSize(int width, int height);
     virtual status_t setVideoFrameRate(int frames_per_second);
     virtual status_t setCamera(const sp<ICamera>& camera);
-    virtual status_t setPreviewSurface(const sp<ISurface>& surface);
+    virtual status_t setPreviewSurface(const sp<Surface>& surface);
     virtual status_t setOutputFile(const char *path);
     virtual status_t setOutputFile(int fd, int64_t offset, int64_t length);
     virtual status_t setParameters(const String8& params);
@@ -66,4 +66,3 @@
 }; // namespace android
 
 #endif // ANDROID_PVMEDIARECORDER_H
-
diff --git a/include/media/PVPlayer.h b/include/media/PVPlayer.h
index df50981..657e7a6 100644
--- a/include/media/PVPlayer.h
+++ b/include/media/PVPlayer.h
@@ -43,7 +43,8 @@
             const char *url, const KeyedVector<String8, String8> *headers);
 
     virtual status_t    setDataSource(int fd, int64_t offset, int64_t length);
-    virtual status_t    setVideoSurface(const sp<ISurface>& surface);
+    virtual status_t    setVideoISurface(const sp<ISurface>& surface);
+    virtual status_t    setVideoSurface(const sp<Surface>& surface);
     virtual status_t    prepare();
     virtual status_t    prepareAsync();
     virtual status_t    start();
diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h
index 291b18a..a600f6b 100644
--- a/include/media/mediarecorder.h
+++ b/include/media/mediarecorder.h
@@ -170,6 +170,7 @@
     status_t    setAudioEncoder(int ae);
     status_t    setOutputFile(const char* path);
     status_t    setOutputFile(int fd, int64_t offset, int64_t length);
+    status_t    setOutputFileAuxiliary(int fd);
     status_t    setVideoSize(int width, int height);
     status_t    setVideoFrameRate(int frames_per_second);
     status_t    setParameters(const String8& params);
@@ -196,6 +197,7 @@
     bool                        mIsAudioEncoderSet;
     bool                        mIsVideoEncoderSet;
     bool                        mIsOutputFileSet;
+    bool                        mIsAuxiliaryOutputFileSet;
     Mutex                       mLock;
     Mutex                       mNotifyLock;
 };
diff --git a/include/media/mediascanner.h b/include/media/mediascanner.h
index 0d397ac..74c9d5d 100644
--- a/include/media/mediascanner.h
+++ b/include/media/mediascanner.h
@@ -38,8 +38,7 @@
 
     typedef bool (*ExceptionCheck)(void* env);
     virtual status_t processDirectory(
-            const char *path, const char *extensions,
-            MediaScannerClient &client,
+            const char *path, MediaScannerClient &client,
             ExceptionCheck exceptionCheck, void *exceptionEnv);
 
     void setLocale(const char *locale);
@@ -55,9 +54,8 @@
     char *mLocale;
 
     status_t doProcessDirectory(
-            char *path, int pathRemaining, const char *extensions,
-            MediaScannerClient &client, ExceptionCheck exceptionCheck,
-            void *exceptionEnv);
+            char *path, int pathRemaining, MediaScannerClient &client,
+            ExceptionCheck exceptionCheck, void *exceptionEnv);
 
     MediaScanner(const MediaScanner &);
     MediaScanner &operator=(const MediaScanner &);
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..3b303f8
--- /dev/null
+++ b/include/media/stagefright/CameraSourceTimeLapse.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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(
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate);
+
+    static CameraSourceTimeLapse *CreateFromCamera(const sp<Camera> &camera,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate);
+
+    virtual ~CameraSourceTimeLapse();
+
+    // If the frame capture interval is large, read will block for a long time.
+    // Due to the way the mediaRecorder framework works, a stop() call from
+    // mediaRecorder waits until the read returns, causing a long wait for
+    // stop() to return. To avoid this, we can make read() return a copy of the
+    // last read frame with the same time stamp frequently. This keeps the
+    // read() call from blocking too long. Calling this function quickly
+    // captures another frame, keeps its copy, and enables this mode of read()
+    // returning quickly.
+    void startQuickReadReturns();
+
+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;
+
+    // Lock for accessing mCameraIdle
+    Mutex mCameraIdleLock;
+
+    // Condition variable to wait on if camera is is not yet idle. Once the
+    // camera gets idle, this variable will be signalled.
+    Condition mCameraIdleCondition;
+
+    // True if camera is in preview mode and ready for takePicture().
+    // False after a call to takePicture() but before the final compressed
+    // data callback has been called and preview has been restarted.
+    volatile bool mCameraIdle;
+
+    // True if stop() is waiting for camera to get idle, i.e. for the last
+    // takePicture() to complete. This is needed so that dataCallbackTimestamp()
+    // can return immediately.
+    volatile bool mStopWaitingForIdleCamera;
+
+    // Lock for accessing quick stop variables.
+    Mutex mQuickStopLock;
+
+    // Condition variable to wake up still picture thread.
+    Condition mTakePictureCondition;
+
+    // mQuickStop is set to true if we use quick read() returns, otherwise it is set
+    // to false. Once in this mode read() return a copy of the last read frame
+    // with the same time stamp. See startQuickReadReturns().
+    volatile bool mQuickStop;
+
+    // Forces the next frame passed to dataCallbackTimestamp() to be read
+    // as a time lapse frame. Used by startQuickReadReturns() so that the next
+    // frame wakes up any blocking read.
+    volatile bool mForceRead;
+
+    // Stores a copy of the MediaBuffer read in the last read() call after
+    // mQuickStop was true.
+    MediaBuffer* mLastReadBufferCopy;
+
+    // Status code for last read.
+    status_t mLastReadStatus;
+
+    CameraSourceTimeLapse(const sp<Camera> &camera,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate);
+
+    // Wrapper over CameraSource::signalBufferReturned() to implement quick stop.
+    // It only handles the case when mLastReadBufferCopy is signalled. Otherwise
+    // it calls the base class' function.
+    virtual void signalBufferReturned(MediaBuffer* buffer);
+
+    // Wrapper over CameraSource::read() to implement quick stop.
+    virtual status_t read(MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+    // 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);
+
+    // Convenience function to fill mLastReadBufferCopy from the just read
+    // buffer.
+    void fillLastReadBufferCopy(MediaBuffer& sourceBuffer);
+
+    // If the passed in size (width x height) is a supported preview size,
+    // the function sets the camera's preview size to it and returns true.
+    // Otherwise returns false.
+    bool trySettingPreviewSize(int32_t width, int32_t height);
+
+    // 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/HardwareAPI.h b/include/media/stagefright/HardwareAPI.h
index 221c679..4ded5e8 100644
--- a/include/media/stagefright/HardwareAPI.h
+++ b/include/media/stagefright/HardwareAPI.h
@@ -21,10 +21,60 @@
 #include <media/stagefright/OMXPluginBase.h>
 #include <media/stagefright/VideoRenderer.h>
 #include <surfaceflinger/ISurface.h>
+#include <ui/android_native_buffer.h>
 #include <utils/RefBase.h>
 
 #include <OMX_Component.h>
 
+namespace android {
+
+// A pointer to this struct is passed to the OMX_SetParameter when the extension
+// index for the 'OMX.google.android.index.enableAndroidNativeBuffers' extension
+// is given.
+//
+// When Android native buffer use is disabled for a port (the default state),
+// the OMX node should operate as normal, and expect UseBuffer calls to set its
+// buffers.  This is the mode that will be used when CPU access to the buffer is
+// required.
+//
+// When Android native buffer use has been enabled, the OMX node must support
+// only color formats in the range [OMX_COLOR_FormatAndroidPrivateStart,
+// OMX_COLOR_FormatAndroidPrivateEnd).  The node should then expect to receive
+// UseAndroidNativeBuffer calls (via OMX_SetParameter) rather than UseBuffer
+// calls.
+struct EnableAndroidNativeBuffersParams {
+    OMX_U32 nSize;
+    OMX_VERSIONTYPE nVersion;
+    OMX_U32 nPortIndex;
+    OMX_BOOL enable;
+};
+
+// Color formats in the range [OMX_COLOR_FormatAndroidPrivateStart,
+// OMX_COLOR_FormatAndroidPrivateEnd) will be converted to a gralloc pixel
+// format when used to allocate Android native buffers via gralloc.  The
+// conversion is done by subtracting OMX_COLOR_FormatAndroidPrivateStart from
+// the color format reported by the codec.
+enum {
+    OMX_COLOR_FormatAndroidPrivateStart = 0xA0000000,
+    OMX_COLOR_FormatAndroidPrivateEnd = 0xB0000000,
+};
+
+// A pointer to this struct is passed to OMX_SetParameter when the extension
+// index for the 'OMX.google.android.index.useAndroidNativeBuffer' extension is
+// given.  This call will only be performed if a prior call was made with the
+// 'OMX.google.android.index.enableAndroidNativeBuffers' extension index,
+// enabling use of Android native buffers.
+struct UseAndroidNativeBufferParams {
+    OMX_U32 nSize;
+    OMX_VERSIONTYPE nVersion;
+    OMX_U32 nPortIndex;
+    OMX_PTR pAppPrivate;
+    OMX_BUFFERHEADERTYPE **bufferHeader;
+    const sp<android_native_buffer_t>& nativeBuffer;
+};
+
+}  // namespace android
+
 extern android::VideoRenderer *createRenderer(
         const android::sp<android::ISurface> &surface,
         const char *componentName,
@@ -35,4 +85,3 @@
 extern android::OMXPluginBase *createOMXPlugin();
 
 #endif  // HARDWARE_API_H_
-
diff --git a/include/media/stagefright/MediaSourceSplitter.h b/include/media/stagefright/MediaSourceSplitter.h
new file mode 100644
index 0000000..568f4c2
--- /dev/null
+++ b/include/media/stagefright/MediaSourceSplitter.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.
+ */
+
+// This class provides a way to split a single media source into multiple sources.
+// The constructor takes in the real mediaSource and createClient() can then be
+// used to create multiple sources served from this real mediaSource.
+//
+// Usage:
+// - Create MediaSourceSplitter by passing in a real mediaSource from which
+// multiple duplicate channels are needed.
+// - Create a client using createClient() and use it as any other mediaSource.
+//
+// Note that multiple clients can be created using createClient() and
+// started/stopped in any order. MediaSourceSplitter stops the real source only
+// when all clients have been stopped.
+//
+// If a new client is created/started after some existing clients have already
+// started, the new client will start getting its read frames from the current
+// time.
+
+#ifndef MEDIA_SOURCE_SPLITTER_H_
+
+#define MEDIA_SOURCE_SPLITTER_H_
+
+#include <media/stagefright/MediaSource.h>
+#include <utils/threads.h>
+#include <utils/Vector.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+class MediaBuffer;
+class MetaData;
+
+class MediaSourceSplitter : public RefBase {
+public:
+    // Constructor
+    // mediaSource: The real mediaSource. The class keeps a reference to it to
+    // implement the various clients.
+    MediaSourceSplitter(sp<MediaSource> mediaSource);
+
+    ~MediaSourceSplitter();
+
+    // Creates a new client of base type MediaSource. Multiple clients can be
+    // created which get their data through the same real mediaSource. These
+    // clients can then be used like any other MediaSource, all of which provide
+    // data from the same real source.
+    sp<MediaSource> createClient();
+
+private:
+    // Total number of clients created through createClient().
+    int32_t mNumberOfClients;
+
+    // reference to the real MediaSource passed to the constructor.
+    sp<MediaSource> mSource;
+
+    // Stores pointer to the MediaBuffer read from the real MediaSource.
+    // All clients use this to implement the read() call.
+    MediaBuffer *mLastReadMediaBuffer;
+
+    // Status code for read from the real MediaSource. All clients return
+    // this for their read().
+    status_t mLastReadStatus;
+
+    // Boolean telling whether the real MediaSource has started.
+    bool mSourceStarted;
+
+    // List of booleans, one for each client, storing whether the corresponding
+    // client's start() has been called.
+    Vector<bool> mClientsStarted;
+
+    // Stores the number of clients which are currently started.
+    int32_t mNumberOfClientsStarted;
+
+    // Since different clients call read() asynchronously, we need to keep track
+    // of what data is currently read into the mLastReadMediaBuffer.
+    // mCurrentReadBit stores the bit for the current read buffer. This bit
+    // flips each time a new buffer is read from the source.
+    // mClientsDesiredReadBit stores the bit for the next desired read buffer
+    // for each client. This bit flips each time read() is completed for this
+    // client.
+    bool mCurrentReadBit;
+    Vector<bool> mClientsDesiredReadBit;
+
+    // Number of clients whose current read has been completed.
+    int32_t mNumberOfCurrentReads;
+
+    // Boolean telling whether the last read has been completed for all clients.
+    // The variable is reset to false each time buffer is read from the real
+    // source.
+    bool mLastReadCompleted;
+
+    // A global mutex for access to critical sections.
+    Mutex mLock;
+
+    // Condition variable for waiting on read from source to complete.
+    Condition mReadFromSourceCondition;
+
+    // Condition variable for waiting on all client's last read to complete.
+    Condition mAllReadsCompleteCondition;
+
+    // Functions used by Client to implement the MediaSource interface.
+
+    // If the real source has not been started yet by any client, starts it.
+    status_t start(int clientId, MetaData *params);
+
+    // Stops the real source after all clients have called stop().
+    status_t stop(int clientId);
+
+    // returns the real source's getFormat().
+    sp<MetaData> getFormat(int clientId);
+
+    // If the client's desired buffer has already been read into
+    // mLastReadMediaBuffer, points the buffer to that. Otherwise if it is the
+    // master client, reads the buffer from source or else waits for the master
+    // client to read the buffer and uses that.
+    status_t read(int clientId,
+            MediaBuffer **buffer, const MediaSource::ReadOptions *options = NULL);
+
+    // Not implemented right now.
+    status_t pause(int clientId);
+
+    // Function which reads a buffer from the real source into
+    // mLastReadMediaBuffer
+    void readFromSource_lock(const MediaSource::ReadOptions *options);
+
+    // Waits until read from the real source has been completed.
+    // _lock means that the function should be called when the thread has already
+    // obtained the lock for the mutex mLock.
+    void waitForReadFromSource_lock(int32_t clientId);
+
+    // Waits until all clients have read the current buffer in
+    // mLastReadCompleted.
+    void waitForAllClientsLastRead_lock(int32_t clientId);
+
+    // Each client calls this after it completes its read(). Once all clients
+    // have called this for the current buffer, the function calls
+    // mAllReadsCompleteCondition.broadcast() to signal the waiting clients.
+    void signalReadComplete_lock(bool readAborted);
+
+    // Make these constructors private.
+    MediaSourceSplitter();
+    MediaSourceSplitter(const MediaSourceSplitter &);
+    MediaSourceSplitter &operator=(const MediaSourceSplitter &);
+
+    // This class implements the MediaSource interface. Each client stores a
+    // reference to the parent MediaSourceSplitter and uses it to complete the
+    // various calls.
+    class Client : public MediaSource {
+    public:
+        // Constructor stores reference to the parent MediaSourceSplitter and it
+        // client id.
+        Client(sp<MediaSourceSplitter> splitter, int32_t clientId);
+
+        // MediaSource interface
+        virtual status_t start(MetaData *params = NULL);
+
+        virtual status_t stop();
+
+        virtual sp<MetaData> getFormat();
+
+        virtual status_t read(
+                MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+        virtual status_t pause();
+
+    private:
+        // Refernce to the parent MediaSourceSplitter
+        sp<MediaSourceSplitter> mSplitter;
+
+        // Id of this client.
+        int32_t mClientId;
+    };
+
+    friend class Client;
+};
+
+}  // namespace android
+
+#endif  // MEDIA_SOURCE_SPLITTER_H_
diff --git a/include/media/stagefright/VideoSourceDownSampler.h b/include/media/stagefright/VideoSourceDownSampler.h
new file mode 100644
index 0000000..439918c
--- /dev/null
+++ b/include/media/stagefright/VideoSourceDownSampler.h
@@ -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.
+ */
+
+// VideoSourceDownSampler implements the MediaSource interface,
+// downsampling frames provided from a real video source.
+
+#ifndef VIDEO_SOURCE_DOWN_SAMPLER_H_
+
+#define VIDEO_SOURCE_DOWN_SAMPLER_H_
+
+#include <media/stagefright/MediaSource.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+class IMemory;
+class MediaBuffer;
+class MetaData;
+
+class VideoSourceDownSampler : public MediaSource {
+public:
+    virtual ~VideoSourceDownSampler();
+
+    // Constructor:
+    // videoSource: The real video source which provides the original frames.
+    // width, height: The desired width, height. These should be less than or equal
+    // to those of the real video source. We then downsample the original frames to
+    // this size.
+    VideoSourceDownSampler(const sp<MediaSource> &videoSource,
+        int32_t width, int32_t height);
+
+    // MediaSource interface
+    virtual status_t start(MetaData *params = NULL);
+
+    virtual status_t stop();
+
+    virtual sp<MetaData> getFormat();
+
+    virtual status_t read(
+            MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+    virtual status_t pause();
+
+private:
+    // Reference to the real video source.
+    sp<MediaSource> mRealVideoSource;
+
+    // Size of frames to be provided by this source.
+    int32_t mWidth;
+    int32_t mHeight;
+
+    // Size of frames provided by the real source.
+    int32_t mRealSourceWidth;
+    int32_t mRealSourceHeight;
+
+    // Down sampling paramters.
+    int32_t mDownSampleOffsetX;
+    int32_t mDownSampleOffsetY;
+    int32_t mDownSampleSkipX;
+    int32_t mDownSampleSkipY;
+
+    // True if we need to crop the still video image to get the video frame.
+    bool mNeedDownSampling;
+
+    // Meta data. This is a copy of the real source except for the width and
+    // height parameters.
+    sp<MetaData> mMeta;
+
+    // Computes the offset, skip parameters for downsampling the original frame
+    // to the desired size.
+    void computeDownSamplingParameters();
+
+    // Downsamples the frame in sourceBuffer to size (mWidth x mHeight). A new
+    // buffer is created which stores the downsampled image.
+    void downSampleYUVImage(const MediaBuffer &sourceBuffer, MediaBuffer **buffer) const;
+
+    // Disallow these.
+    VideoSourceDownSampler(const VideoSourceDownSampler &);
+    VideoSourceDownSampler &operator=(const VideoSourceDownSampler &);
+};
+
+}  // namespace android
+
+#endif  // VIDEO_SOURCE_DOWN_SAMPLER_H_
diff --git a/include/media/stagefright/YUVCanvas.h b/include/media/stagefright/YUVCanvas.h
new file mode 100644
index 0000000..ff70923
--- /dev/null
+++ b/include/media/stagefright/YUVCanvas.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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);
+
+    // Downsamples the srcImage into the canvas' target image (mYUVImage)
+    // The downsampling copies pixels from the source image starting at
+    // (srcOffsetX, srcOffsetY) to the target image, starting at (0, 0).
+    // For each X increment in the target image, skipX pixels are skipped
+    // in the source image.
+    // Similarly for each Y increment in the target image, skipY pixels
+    // are skipped in the source image.
+    void downsample(
+            int32_t srcOffsetX, int32_t srcOffsetY,
+            int32_t skipX, int32_t skipY,
+            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..4e98618
--- /dev/null
+++ b/include/media/stagefright/YUVImage.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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() const {return mWidth;}
+    int32_t height() const {return mHeight;}
+
+    // Returns true if pixel is the range [0, width-1] x [0, height-1]
+    // and false otherwise.
+    bool validPixel(int32_t x, int32_t y) const;
+
+    // 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 successful 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 successful 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/include/surfaceflinger/Surface.h b/include/surfaceflinger/Surface.h
index 22684db..cef439c 100644
--- a/include/surfaceflinger/Surface.h
+++ b/include/surfaceflinger/Surface.h
@@ -94,7 +94,7 @@
     friend class SurfaceComposerClient;
 
     // camera and camcorder need access to the ISurface binder interface for preview
-    friend class Camera;
+    friend class CameraService;
     friend class MediaRecorder;
     // mediaplayer needs access to ISurface for display
     friend class MediaPlayer;
@@ -173,11 +173,12 @@
      * (eventually this should go away and be replaced by proper APIs)
      */
     // camera and camcorder need access to the ISurface binder interface for preview
-    friend class Camera;
+    friend class CameraService;
     friend class MediaRecorder;
     // MediaPlayer needs access to ISurface for display
     friend class MediaPlayer;
     friend class IOMX;
+    friend class SoftwareRenderer;
     // this is just to be able to write some unit tests
     friend class Test;
 
@@ -314,4 +315,3 @@
 }; // namespace android
 
 #endif // ANDROID_SF_SURFACE_H
-
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index cc16012..8d4654f 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -355,6 +355,14 @@
      */
     virtual void setInputDispatchMode(bool enabled, bool frozen) = 0;
 
+    /* Transfers touch focus from the window associated with one channel to the
+     * window associated with the other channel.
+     *
+     * Returns true on success.  False if the window did not actually have touch focus.
+     */
+    virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+            const sp<InputChannel>& toChannel) = 0;
+
     /* Registers or unregister input channels that may be used as targets for input events.
      * If monitor is true, the channel will receive a copy of all input events.
      *
@@ -409,6 +417,9 @@
     virtual void setFocusedApplication(const InputApplication* inputApplication);
     virtual void setInputDispatchMode(bool enabled, bool frozen);
 
+    virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+            const sp<InputChannel>& toChannel);
+
     virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor);
     virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
 
diff --git a/include/utils/Singleton.h b/include/utils/Singleton.h
index 3b975b4..e1ee8eb 100644
--- a/include/utils/Singleton.h
+++ b/include/utils/Singleton.h
@@ -37,6 +37,11 @@
         }
         return *instance;
     }
+
+    static bool hasInstance() {
+        Mutex::Autolock _l(sLock);
+        return sInstance != 0;
+    }
     
 protected:
     ~Singleton() { };
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..bdd4dd6
--- /dev/null
+++ b/libs/binder/CursorWindow.cpp
@@ -0,0 +1,410 @@
+/*
+ * 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()) {
+        LOGV("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) {
+            LOGV("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/Camera.cpp b/libs/camera/Camera.cpp
index 7efc6d7..b5f78e8 100644
--- a/libs/camera/Camera.cpp
+++ b/libs/camera/Camera.cpp
@@ -167,32 +167,20 @@
     return c->unlock();
 }
 
-// pass the buffered ISurface to the camera service
+// pass the buffered Surface to the camera service
 status_t Camera::setPreviewDisplay(const sp<Surface>& surface)
 {
-    LOGV("setPreviewDisplay");
+    LOGV("setPreviewDisplay(%p)", surface.get());
     sp <ICamera> c = mCamera;
     if (c == 0) return NO_INIT;
     if (surface != 0) {
-        return c->setPreviewDisplay(surface->getISurface());
+        return c->setPreviewDisplay(surface);
     } else {
         LOGD("app passed NULL surface");
         return c->setPreviewDisplay(0);
     }
 }
 
-status_t Camera::setPreviewDisplay(const sp<ISurface>& surface)
-{
-    LOGV("setPreviewDisplay");
-    if (surface == 0) {
-        LOGD("app passed NULL surface");
-    }
-    sp <ICamera> c = mCamera;
-    if (c == 0) return NO_INIT;
-    return c->setPreviewDisplay(surface);
-}
-
-
 // start preview mode
 status_t Camera::startPreview()
 {
@@ -375,4 +363,3 @@
 }
 
 }; // namespace android
-
diff --git a/libs/camera/CameraParameters.cpp b/libs/camera/CameraParameters.cpp
index 83e5e57f..45b1b9a 100644
--- a/libs/camera/CameraParameters.cpp
+++ b/libs/camera/CameraParameters.cpp
@@ -73,6 +73,8 @@
 const char CameraParameters::KEY_SMOOTH_ZOOM_SUPPORTED[] = "smooth-zoom-supported";
 const char CameraParameters::KEY_FOCUS_DISTANCES[] = "focus-distances";
 const char CameraParameters::KEY_VIDEO_FRAME_FORMAT[] = "video-frame-format";
+const char CameraParameters::KEY_VIDEO_SIZE[] = "video-size";
+const char CameraParameters::KEY_SUPPORTED_VIDEO_SIZES[] = "video-size-values";
 
 const char CameraParameters::TRUE[] = "true";
 const char CameraParameters::FOCUS_DISTANCE_INFINITY[] = "Infinity";
@@ -129,7 +131,7 @@
 const char CameraParameters::SCENE_MODE_CANDLELIGHT[] = "candlelight";
 const char CameraParameters::SCENE_MODE_BARCODE[] = "barcode";
 
-// Formats for setPreviewFormat and setPictureFormat.
+const char CameraParameters::PIXEL_FORMAT_YUV420P[]  = "yuv420p";
 const char CameraParameters::PIXEL_FORMAT_YUV422SP[] = "yuv422sp";
 const char CameraParameters::PIXEL_FORMAT_YUV420SP[] = "yuv420sp";
 const char CameraParameters::PIXEL_FORMAT_YUV422I[] = "yuv422i-yuyv";
@@ -337,6 +339,27 @@
     parseSizesList(previewSizesStr, sizes);
 }
 
+void CameraParameters::setVideoSize(int width, int height)
+{
+    char str[32];
+    sprintf(str, "%dx%d", width, height);
+    set(KEY_VIDEO_SIZE, str);
+}
+
+void CameraParameters::getVideoSize(int *width, int *height) const
+{
+    *width = *height = -1;
+    const char *p = get(KEY_VIDEO_SIZE);
+    if (p == 0) return;
+    parse_pair(p, width, height, 'x');
+}
+
+void CameraParameters::getSupportedVideoSizes(Vector<Size> &sizes) const
+{
+    const char *videoSizesStr = get(KEY_SUPPORTED_VIDEO_SIZES);
+    parseSizesList(videoSizesStr, sizes);
+}
+
 void CameraParameters::setPreviewFrameRate(int fps)
 {
     set(KEY_PREVIEW_FRAME_RATE, fps);
diff --git a/libs/camera/ICamera.cpp b/libs/camera/ICamera.cpp
index 13673b5..94dc5c1 100644
--- a/libs/camera/ICamera.cpp
+++ b/libs/camera/ICamera.cpp
@@ -64,13 +64,13 @@
         remote()->transact(DISCONNECT, data, &reply);
     }
 
-    // pass the buffered ISurface to the camera service
-    status_t setPreviewDisplay(const sp<ISurface>& surface)
+    // pass the buffered Surface to the camera service
+    status_t setPreviewDisplay(const sp<Surface>& surface)
     {
         LOGV("setPreviewDisplay");
         Parcel data, reply;
         data.writeInterfaceToken(ICamera::getInterfaceDescriptor());
-        data.writeStrongBinder(surface->asBinder());
+        Surface::writeToParcel(surface, &data);
         remote()->transact(SET_PREVIEW_DISPLAY, data, &reply);
         return reply.readInt32();
     }
@@ -258,7 +258,7 @@
         case SET_PREVIEW_DISPLAY: {
             LOGV("SET_PREVIEW_DISPLAY");
             CHECK_INTERFACE(ICamera, data, reply);
-            sp<ISurface> surface = interface_cast<ISurface>(data.readStrongBinder());
+            sp<Surface> surface = Surface::readFromParcel(data);
             reply->writeInt32(setPreviewDisplay(surface));
             return NO_ERROR;
         } break;
@@ -376,4 +376,3 @@
 // ----------------------------------------------------------------------------
 
 }; // namespace android
-
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
new file mode 100644
index 0000000..43af702
--- /dev/null
+++ b/libs/hwui/Android.mk
@@ -0,0 +1,46 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Only build libhwui when USE_OPENGL_RENDERER is
+# defined in the current device/board configuration
+ifeq ($(USE_OPENGL_RENDERER),true)
+	LOCAL_SRC_FILES:= \
+		DisplayListRenderer.cpp \
+		FboCache.cpp \
+		FontRenderer.cpp \
+		GammaFontRenderer.cpp \
+		GradientCache.cpp \
+		LayerCache.cpp \
+		Matrix.cpp \
+		OpenGLDebugRenderer.cpp \
+		OpenGLRenderer.cpp \
+		Patch.cpp \
+		PatchCache.cpp \
+		PathCache.cpp \
+		Program.cpp \
+		ProgramCache.cpp \
+		SkiaColorFilter.cpp \
+		SkiaShader.cpp \
+		TextureCache.cpp \
+		TextDropShadowCache.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_CFLAGS += -DUSE_OPENGL_RENDERER
+	LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+	LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia
+	LOCAL_MODULE := libhwui
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_PRELINK_MODULE := false
+	
+	include $(BUILD_SHARED_LIBRARY)
+
+    include $(call all-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
new file mode 100644
index 0000000..2952a66
--- /dev/null
+++ b/libs/hwui/Caches.h
@@ -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.
+ */
+
+#ifndef ANDROID_UI_CACHES_H
+#define ANDROID_UI_CACHES_H
+
+#ifndef LOG_TAG
+    #define LOG_TAG "OpenGLRenderer"
+#endif
+
+#include <utils/Singleton.h>
+
+#include "TextureCache.h"
+#include "LayerCache.h"
+#include "GradientCache.h"
+#include "PatchCache.h"
+#include "GammaFontRenderer.h"
+#include "ProgramCache.h"
+#include "PathCache.h"
+#include "TextDropShadowCache.h"
+#include "FboCache.h"
+#include "Line.h"
+
+namespace android {
+namespace uirenderer {
+
+struct CacheLogger {
+    CacheLogger() {
+        LOGD("Creating caches");
+    }
+}; // struct CacheLogger
+
+class Caches: public Singleton<Caches> {
+    Caches(): Singleton<Caches>(), blend(false), lastSrcMode(GL_ZERO),
+            lastDstMode(GL_ZERO), currentProgram(NULL) {
+    }
+
+    friend class Singleton<Caches>;
+
+    CacheLogger logger;
+
+public:
+    bool blend;
+    GLenum lastSrcMode;
+    GLenum lastDstMode;
+    Program* currentProgram;
+
+    TextureCache textureCache;
+    LayerCache layerCache;
+    GradientCache gradientCache;
+    ProgramCache programCache;
+    PathCache pathCache;
+    PatchCache patchCache;
+    TextDropShadowCache dropShadowCache;
+    FboCache fboCache;
+    GammaFontRenderer fontRenderer;
+
+    Line line;
+}; // class Caches
+
+}; // namespace uirenderer
+
+#ifdef USE_OPENGL_RENDERER
+using namespace uirenderer;
+ANDROID_SINGLETON_STATIC_INSTANCE(Caches);
+#endif
+
+}; // namespace android
+
+#endif // ANDROID_UI_CACHES_H
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
new file mode 100644
index 0000000..ce85d46
--- /dev/null
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -0,0 +1,584 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "DisplayListRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+#define PATH_HEAP_SIZE 64
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+PathHeap::PathHeap(): mHeap(PATH_HEAP_SIZE * sizeof(SkPath)) {
+}
+
+PathHeap::PathHeap(SkFlattenableReadBuffer& buffer): mHeap(PATH_HEAP_SIZE * sizeof(SkPath)) {
+    int count = buffer.readS32();
+
+    mPaths.setCount(count);
+    SkPath** ptr = mPaths.begin();
+    SkPath* p = (SkPath*) mHeap.allocThrow(count * sizeof(SkPath));
+
+    for (int i = 0; i < count; i++) {
+        new (p) SkPath;
+        p->unflatten(buffer);
+        *ptr++ = p;
+        p++;
+    }
+}
+
+PathHeap::~PathHeap() {
+    SkPath** iter = mPaths.begin();
+    SkPath** stop = mPaths.end();
+    while (iter < stop) {
+        (*iter)->~SkPath();
+        iter++;
+    }
+}
+
+int PathHeap::append(const SkPath& path) {
+    SkPath* p = (SkPath*) mHeap.allocThrow(sizeof(SkPath));
+    new (p) SkPath(path);
+    *mPaths.append() = p;
+    return mPaths.count();
+}
+
+void PathHeap::flatten(SkFlattenableWriteBuffer& buffer) const {
+    int count = mPaths.count();
+
+    buffer.write32(count);
+    SkPath** iter = mPaths.begin();
+    SkPath** stop = mPaths.end();
+    while (iter < stop) {
+        (*iter)->flatten(buffer);
+        iter++;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Display list
+///////////////////////////////////////////////////////////////////////////////
+
+DisplayList::DisplayList(const DisplayListRenderer& recorder) {
+    const SkWriter32& writer = recorder.writeStream();
+    init();
+
+    if (writer.size() == 0) {
+        return;
+    }
+
+    size_t size = writer.size();
+    void* buffer = sk_malloc_throw(size);
+    writer.flatten(buffer);
+    mReader.setMemory(buffer, size);
+
+    mRCPlayback.reset(&recorder.mRCRecorder);
+    mRCPlayback.setupBuffer(mReader);
+
+    mTFPlayback.reset(&recorder.mTFRecorder);
+    mTFPlayback.setupBuffer(mReader);
+
+    const SkTDArray<const SkFlatBitmap*>& bitmaps = recorder.getBitmaps();
+    mBitmapCount = bitmaps.count();
+    if (mBitmapCount > 0) {
+        mBitmaps = new SkBitmap[mBitmapCount];
+        for (const SkFlatBitmap** flatBitmapPtr = bitmaps.begin();
+                flatBitmapPtr != bitmaps.end(); flatBitmapPtr++) {
+            const SkFlatBitmap* flatBitmap = *flatBitmapPtr;
+            int index = flatBitmap->index() - 1;
+            flatBitmap->unflatten(&mBitmaps[index], &mRCPlayback);
+        }
+    }
+
+    const SkTDArray<const SkFlatMatrix*>& matrices = recorder.getMatrices();
+    mMatrixCount = matrices.count();
+    if (mMatrixCount > 0) {
+        mMatrices = new SkMatrix[mMatrixCount];
+        for (const SkFlatMatrix** matrixPtr = matrices.begin();
+                matrixPtr != matrices.end(); matrixPtr++) {
+            const SkFlatMatrix* flatMatrix = *matrixPtr;
+            flatMatrix->unflatten(&mMatrices[flatMatrix->index() - 1]);
+        }
+    }
+
+    const SkTDArray<const SkFlatPaint*>& paints = recorder.getPaints();
+    mPaintCount = paints.count();
+    if (mPaintCount > 0) {
+        mPaints = new SkPaint[mPaintCount];
+        for (const SkFlatPaint** flatPaintPtr = paints.begin();
+                flatPaintPtr != paints.end(); flatPaintPtr++) {
+            const SkFlatPaint* flatPaint = *flatPaintPtr;
+            int index = flatPaint->index() - 1;
+            flatPaint->unflatten(&mPaints[index], &mRCPlayback, &mTFPlayback);
+        }
+    }
+
+    mPathHeap = recorder.mPathHeap;
+    mPathHeap->safeRef();
+}
+
+DisplayList::~DisplayList() {
+    sk_free((void*) mReader.base());
+
+    Caches& caches = Caches::getInstance();
+    for (int i = 0; i < mBitmapCount; i++) {
+        caches.textureCache.remove(&mBitmaps[i]);
+    }
+
+    delete[] mBitmaps;
+    delete[] mMatrices;
+    delete[] mPaints;
+
+    mPathHeap->safeUnref();
+}
+
+void DisplayList::init() {
+    mBitmaps = NULL;
+    mMatrices = NULL;
+    mPaints = NULL;
+    mPathHeap = NULL;
+    mBitmapCount = mMatrixCount = mPaintCount = 0;
+}
+
+void DisplayList::replay(OpenGLRenderer& renderer) {
+    TextContainer text;
+    mReader.rewind();
+
+    int saveCount = renderer.getSaveCount() - 1;
+
+    while (!mReader.eof()) {
+        switch (mReader.readInt()) {
+            case AcquireContext: {
+                renderer.acquireContext();
+            }
+            break;
+            case ReleaseContext: {
+                renderer.releaseContext();
+            }
+            break;
+            case Save: {
+                renderer.save(getInt());
+            }
+            break;
+            case Restore: {
+                renderer.restore();
+            }
+            break;
+            case RestoreToCount: {
+                renderer.restoreToCount(saveCount + getInt());
+            }
+            break;
+            case SaveLayer: {
+                renderer.saveLayer(getFloat(), getFloat(), getFloat(), getFloat(),
+                        getPaint(), getInt());
+            }
+            break;
+            case Translate: {
+                renderer.translate(getFloat(), getFloat());
+            }
+            break;
+            case Rotate: {
+                renderer.rotate(getFloat());
+            }
+            break;
+            case Scale: {
+                renderer.scale(getFloat(), getFloat());
+            }
+            break;
+            case SetMatrix: {
+                renderer.setMatrix(getMatrix());
+            }
+            break;
+            case ConcatMatrix: {
+                renderer.concatMatrix(getMatrix());
+            }
+            break;
+            case ClipRect: {
+                renderer.clipRect(getFloat(), getFloat(), getFloat(), getFloat(),
+                        (SkRegion::Op) getInt());
+            }
+            break;
+            case DrawBitmap: {
+                renderer.drawBitmap(getBitmap(), getFloat(), getFloat(), getPaint());
+            }
+            break;
+            case DrawBitmapMatrix: {
+                renderer.drawBitmap(getBitmap(), getMatrix(), getPaint());
+            }
+            break;
+            case DrawBitmapRect: {
+                renderer.drawBitmap(getBitmap(), getFloat(), getFloat(), getFloat(), getFloat(),
+                        getFloat(), getFloat(), getFloat(), getFloat(), getPaint());
+            }
+            break;
+            case DrawPatch: {
+                int32_t* xDivs = NULL;
+                int32_t* yDivs = NULL;
+                uint32_t xDivsCount = 0;
+                uint32_t yDivsCount = 0;
+
+                SkBitmap* bitmap = getBitmap();
+
+                xDivs = getInts(xDivsCount);
+                yDivs = getInts(yDivsCount);
+
+                renderer.drawPatch(bitmap, xDivs, yDivs, xDivsCount, yDivsCount,
+                        getFloat(), getFloat(), getFloat(), getFloat(), getPaint());
+            }
+            break;
+            case DrawColor: {
+                renderer.drawColor(getInt(), (SkXfermode::Mode) getInt());
+            }
+            break;
+            case DrawRect: {
+                renderer.drawRect(getFloat(), getFloat(), getFloat(), getFloat(), getPaint());
+            }
+            break;
+            case DrawPath: {
+                renderer.drawPath(getPath(), getPaint());
+            }
+            break;
+            case DrawLines: {
+                int count = 0;
+                float* points = getFloats(count);
+                renderer.drawLines(points, count, getPaint());
+            }
+            break;
+            case DrawText: {
+                getText(&text);
+                renderer.drawText(text.text(), text.length(), getInt(),
+                        getFloat(), getFloat(), getPaint());
+            }
+            break;
+            case ResetShader: {
+                renderer.resetShader();
+            }
+            break;
+            case SetupShader: {
+                // TODO: Implement
+            }
+            break;
+            case ResetColorFilter: {
+                renderer.resetColorFilter();
+            }
+            break;
+            case SetupColorFilter: {
+                // TODO: Implement
+            }
+            break;
+            case ResetShadow: {
+                renderer.resetShadow();
+            }
+            break;
+            case SetupShadow: {
+                renderer.setupShadow(getFloat(), getFloat(), getFloat(), getInt());
+            }
+            break;
+        }
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Base structure
+///////////////////////////////////////////////////////////////////////////////
+
+DisplayListRenderer::DisplayListRenderer():
+        mHeap(HEAP_BLOCK_SIZE), mWriter(MIN_WRITER_SIZE) {
+    mBitmapIndex = mMatrixIndex = mPaintIndex = 1;
+    mPathHeap = NULL;
+}
+
+DisplayListRenderer::~DisplayListRenderer() {
+    reset();
+}
+
+void DisplayListRenderer::reset() {
+    if (mPathHeap) {
+        mPathHeap->unref();
+        mPathHeap = NULL;
+    }
+
+    mBitmaps.reset();
+    mMatrices.reset();
+    mPaints.reset();
+
+    mWriter.reset();
+    mHeap.reset();
+
+    mRCRecorder.reset();
+    mTFRecorder.reset();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Operations
+///////////////////////////////////////////////////////////////////////////////
+
+void DisplayListRenderer::setViewport(int width, int height) {
+    mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1);
+
+    mWidth = width;
+    mHeight = height;
+}
+
+void DisplayListRenderer::prepare() {
+    mSnapshot = new Snapshot(mFirstSnapshot,
+            SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+    mSaveCount = 1;
+    mSnapshot->setClip(0.0f, 0.0f, mWidth, mHeight);
+}
+
+void DisplayListRenderer::acquireContext() {
+    addOp(DisplayList::AcquireContext);
+    OpenGLRenderer::acquireContext();
+}
+
+void DisplayListRenderer::releaseContext() {
+    addOp(DisplayList::ReleaseContext);
+    OpenGLRenderer::releaseContext();
+}
+
+int DisplayListRenderer::save(int flags) {
+    addOp(DisplayList::Save);
+    addInt(flags);
+    return OpenGLRenderer::save(flags);
+}
+
+void DisplayListRenderer::restore() {
+    addOp(DisplayList::Restore);
+    OpenGLRenderer::restore();
+}
+
+void DisplayListRenderer::restoreToCount(int saveCount) {
+    addOp(DisplayList::RestoreToCount);
+    addInt(saveCount);
+    OpenGLRenderer::restoreToCount(saveCount);
+}
+
+int DisplayListRenderer::saveLayer(float left, float top, float right, float bottom,
+        const SkPaint* p, int flags) {
+    addOp(DisplayList::SaveLayer);
+    addBounds(left, top, right, bottom);
+    addPaint(p);
+    addInt(flags);
+    return OpenGLRenderer::save(flags);
+}
+
+void DisplayListRenderer::translate(float dx, float dy) {
+    addOp(DisplayList::Translate);
+    addPoint(dx, dy);
+    OpenGLRenderer::translate(dx, dy);
+}
+
+void DisplayListRenderer::rotate(float degrees) {
+    addOp(DisplayList::Rotate);
+    addFloat(degrees);
+    OpenGLRenderer::rotate(degrees);
+}
+
+void DisplayListRenderer::scale(float sx, float sy) {
+    addOp(DisplayList::Scale);
+    addPoint(sx, sy);
+    OpenGLRenderer::scale(sx, sy);
+}
+
+void DisplayListRenderer::setMatrix(SkMatrix* matrix) {
+    addOp(DisplayList::SetMatrix);
+    addMatrix(matrix);
+    OpenGLRenderer::setMatrix(matrix);
+}
+
+void DisplayListRenderer::concatMatrix(SkMatrix* matrix) {
+    addOp(DisplayList::ConcatMatrix);
+    addMatrix(matrix);
+    OpenGLRenderer::concatMatrix(matrix);
+}
+
+bool DisplayListRenderer::clipRect(float left, float top, float right, float bottom,
+        SkRegion::Op op) {
+    addOp(DisplayList::ClipRect);
+    addBounds(left, top, right, bottom);
+    addInt(op);
+    return OpenGLRenderer::clipRect(left, top, right, bottom, op);
+}
+
+void DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top,
+        const SkPaint* paint) {
+    addOp(DisplayList::DrawBitmap);
+    addBitmap(bitmap);
+    addPoint(left, top);
+    addPaint(paint);
+}
+
+void DisplayListRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix,
+        const SkPaint* paint) {
+    addOp(DisplayList::DrawBitmapMatrix);
+    addBitmap(bitmap);
+    addMatrix(matrix);
+    addPaint(paint);
+}
+
+void DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
+        float srcRight, float srcBottom, float dstLeft, float dstTop,
+        float dstRight, float dstBottom, const SkPaint* paint) {
+    addOp(DisplayList::DrawBitmapRect);
+    addBitmap(bitmap);
+    addBounds(srcLeft, srcTop, srcRight, srcBottom);
+    addBounds(dstLeft, dstTop, dstRight, dstBottom);
+    addPaint(paint);
+}
+
+void DisplayListRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
+        uint32_t width, uint32_t height, float left, float top, float right, float bottom,
+        const SkPaint* paint) {
+    addOp(DisplayList::DrawPatch);
+    addBitmap(bitmap);
+    addInts(xDivs, width);
+    addInts(yDivs, height);
+    addBounds(left, top, right, bottom);
+    addPaint(paint);
+}
+
+void DisplayListRenderer::drawColor(int color, SkXfermode::Mode mode) {
+    addOp(DisplayList::DrawColor);
+    addInt(color);
+    addInt(mode);
+}
+
+void DisplayListRenderer::drawRect(float left, float top, float right, float bottom,
+        const SkPaint* paint) {
+    addOp(DisplayList::DrawRect);
+    addBounds(left, top, right, bottom);
+    addPaint(paint);
+}
+
+void DisplayListRenderer::drawPath(SkPath* path, SkPaint* paint) {
+    addOp(DisplayList::DrawPath);
+    addPath(path);
+    addPaint(paint);
+}
+
+void DisplayListRenderer::drawLines(float* points, int count, const SkPaint* paint) {
+    addOp(DisplayList::DrawLines);
+    addFloats(points, count);
+    addPaint(paint);
+}
+
+void DisplayListRenderer::drawText(const char* text, int bytesCount, int count,
+        float x, float y, SkPaint* paint) {
+    addOp(DisplayList::DrawText);
+    addText(text, bytesCount);
+    addInt(count);
+    addPoint(x, y);
+    addPaint(paint);
+}
+
+void DisplayListRenderer::resetShader() {
+    addOp(DisplayList::ResetShader);
+    OpenGLRenderer::resetShader();
+}
+
+void DisplayListRenderer::setupShader(SkiaShader* shader) {
+    // TODO: Implement
+    OpenGLRenderer::setupShader(shader);
+}
+
+void DisplayListRenderer::resetColorFilter() {
+    addOp(DisplayList::ResetColorFilter);
+    OpenGLRenderer::resetColorFilter();
+}
+
+void DisplayListRenderer::setupColorFilter(SkiaColorFilter* filter) {
+    // TODO: Implement
+    OpenGLRenderer::setupColorFilter(filter);
+}
+
+void DisplayListRenderer::resetShadow() {
+    addOp(DisplayList::ResetShadow);
+    OpenGLRenderer::resetShadow();
+}
+
+void DisplayListRenderer::setupShadow(float radius, float dx, float dy, int color) {
+    addOp(DisplayList::SetupShadow);
+    addFloat(radius);
+    addPoint(dx, dy);
+    addInt(color);
+    OpenGLRenderer::setupShadow(radius, dx, dy, color);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Recording management
+///////////////////////////////////////////////////////////////////////////////
+
+int DisplayListRenderer::find(SkTDArray<const SkFlatPaint*>& paints, const SkPaint* paint) {
+    if (paint == NULL) {
+        return 0;
+    }
+
+    SkFlatPaint* flat = SkFlatPaint::Flatten(&mHeap, *paint, mPaintIndex,
+            &mRCRecorder, &mTFRecorder);
+    int index = SkTSearch<SkFlatData>((const SkFlatData**) paints.begin(),
+            paints.count(), (SkFlatData*) flat, sizeof(flat), &SkFlatData::Compare);
+    if (index >= 0) {
+        (void) mHeap.unalloc(flat);
+        return paints[index]->index();
+    }
+
+    index = ~index;
+    *paints.insert(index) = flat;
+    return mPaintIndex++;
+}
+
+int DisplayListRenderer::find(SkTDArray<const SkFlatMatrix*>& matrices, const SkMatrix* matrix) {
+    if (matrix == NULL) {
+        return 0;
+    }
+
+    SkFlatMatrix* flat = SkFlatMatrix::Flatten(&mHeap, *matrix, mMatrixIndex);
+    int index = SkTSearch<SkFlatData>((const SkFlatData**) matrices.begin(),
+            matrices.count(), (SkFlatData*) flat, sizeof(flat), &SkFlatData::Compare);
+    if (index >= 0) {
+        (void) mHeap.unalloc(flat);
+        return matrices[index]->index();
+    }
+    index = ~index;
+    *matrices.insert(index) = flat;
+    return mMatrixIndex++;
+}
+
+int DisplayListRenderer::find(SkTDArray<const SkFlatBitmap*>& bitmaps, const SkBitmap& bitmap) {
+    SkFlatBitmap* flat = SkFlatBitmap::Flatten(&mHeap, bitmap, mBitmapIndex, &mRCRecorder);
+    int index = SkTSearch<SkFlatData>((const SkFlatData**) bitmaps.begin(),
+            bitmaps.count(), (SkFlatData*) flat, sizeof(flat), &SkFlatData::Compare);
+    if (index >= 0) {
+        (void) mHeap.unalloc(flat);
+        return bitmaps[index]->index();
+    }
+    index = ~index;
+    *bitmaps.insert(index) = flat;
+    return mBitmapIndex++;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
new file mode 100644
index 0000000..5d02bd7
--- /dev/null
+++ b/libs/hwui/DisplayListRenderer.h
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_DISPLAY_LIST_RENDERER_H
+#define ANDROID_UI_DISPLAY_LIST_RENDERER_H
+
+#include <SkChunkAlloc.h>
+#include <SkFlattenable.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkPictureFlat.h>
+#include <SkRefCnt.h>
+#include <SkTDArray.h>
+#include <SkTSearch.h>
+
+#include "OpenGLRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+#define MIN_WRITER_SIZE 16384
+#define HEAP_BLOCK_SIZE 4096
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+class PathHeap: public SkRefCnt {
+public:
+    PathHeap();
+    PathHeap(SkFlattenableReadBuffer& buffer);
+    ~PathHeap();
+
+    int append(const SkPath& path);
+
+    int count() const { return mPaths.count(); }
+
+    SkPath& operator[](int index) const {
+        return *mPaths[index];
+    }
+
+    void flatten(SkFlattenableWriteBuffer& buffer) const;
+
+private:
+    SkChunkAlloc mHeap;
+    SkTDArray<SkPath*> mPaths;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Display list
+///////////////////////////////////////////////////////////////////////////////
+
+class DisplayListRenderer;
+
+/**
+ * Replays recorded drawing commands.
+ */
+class DisplayList {
+public:
+    DisplayList(const DisplayListRenderer& recorder);
+    ~DisplayList();
+
+    enum Op {
+        AcquireContext,
+        ReleaseContext,
+        Save,
+        Restore,
+        RestoreToCount,
+        SaveLayer,
+        Translate,
+        Rotate,
+        Scale,
+        SetMatrix,
+        ConcatMatrix,
+        ClipRect,
+        DrawBitmap,
+        DrawBitmapMatrix,
+        DrawBitmapRect,
+        DrawPatch,
+        DrawColor,
+        DrawRect,
+        DrawPath,
+        DrawLines,
+        DrawText,
+        ResetShader,
+        SetupShader,
+        ResetColorFilter,
+        SetupColorFilter,
+        ResetShadow,
+        SetupShadow
+    };
+
+    void replay(OpenGLRenderer& renderer);
+
+private:
+    void init();
+
+    class TextContainer {
+    public:
+        size_t length() const {
+            return mByteLength;
+        }
+
+        const char* text() const {
+            return (const char*) mText;
+        }
+
+        size_t mByteLength;
+        const char* mText;
+    };
+
+    SkBitmap* getBitmap() {
+        int index = getInt();
+        return &mBitmaps[index - 1];
+    }
+
+    inline int getIndex() {
+        return mReader.readInt();
+    }
+
+    inline int getInt() {
+        return mReader.readInt();
+    }
+
+    SkMatrix* getMatrix() {
+        int index = getInt();
+        if (index == 0) {
+            return NULL;
+        }
+        return &mMatrices[index - 1];
+    }
+
+    SkPath* getPath() {
+        return &(*mPathHeap)[getInt() - 1];
+    }
+
+    SkPaint* getPaint() {
+        int index = getInt();
+        if (index == 0) {
+            return NULL;
+        }
+        return &mPaints[index - 1];
+    }
+
+    inline float getFloat() {
+        return mReader.readScalar();
+    }
+
+    int32_t* getInts(uint32_t& count) {
+        count = getInt();
+        return (int32_t*) mReader.skip(count * sizeof(int32_t));
+    }
+
+    float* getFloats(int& count) {
+        count = getInt();
+        return (float*) mReader.skip(count * sizeof(float));
+    }
+
+    void getText(TextContainer* text) {
+        size_t length = text->mByteLength = getInt();
+        text->mText = (const char*) mReader.skip(length);
+    }
+
+    PathHeap* mPathHeap;
+
+    SkBitmap* mBitmaps;
+    int mBitmapCount;
+
+    SkMatrix* mMatrices;
+    int mMatrixCount;
+
+    SkPaint* mPaints;
+    int mPaintCount;
+
+    mutable SkFlattenableReadBuffer mReader;
+
+    SkRefCntPlayback mRCPlayback;
+    SkTypefacePlayback mTFPlayback;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Renderer
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Records drawing commands in a display list for latter playback.
+ */
+class DisplayListRenderer: public OpenGLRenderer {
+public:
+    DisplayListRenderer();
+    ~DisplayListRenderer();
+
+    void setViewport(int width, int height);
+    void prepare();
+
+    void acquireContext();
+    void releaseContext();
+
+    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);
+
+    void translate(float dx, float dy);
+    void rotate(float degrees);
+    void scale(float sx, float sy);
+
+    void setMatrix(SkMatrix* matrix);
+    void concatMatrix(SkMatrix* matrix);
+
+    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, const int32_t* xDivs, const int32_t* yDivs,
+            uint32_t width, uint32_t height, 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 drawPath(SkPath* path, SkPaint* paint);
+    void drawLines(float* points, int count, const SkPaint* paint);
+    void drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint);
+
+    void resetShader();
+    void setupShader(SkiaShader* shader);
+
+    void resetColorFilter();
+    void setupColorFilter(SkiaColorFilter* filter);
+
+    void resetShadow();
+    void setupShadow(float radius, float dx, float dy, int color);
+
+    void reset();
+
+    DisplayList* getDisplayList() const {
+        return new DisplayList(*this);
+    }
+
+    const SkWriter32& writeStream() const {
+        return mWriter;
+    }
+
+    const SkTDArray<const SkFlatBitmap*>& getBitmaps() const {
+        return mBitmaps;
+    }
+
+    const SkTDArray<const SkFlatMatrix*>& getMatrices() const {
+        return mMatrices;
+    }
+
+    const SkTDArray<const SkFlatPaint*>& getPaints() const {
+        return mPaints;
+    }
+
+private:
+    inline void addOp(DisplayList::Op drawOp) {
+        mWriter.writeInt(drawOp);
+    }
+
+    inline void addInt(int value) {
+        mWriter.writeInt(value);
+    }
+
+    void addInts(const int32_t* values, uint32_t count) {
+        mWriter.writeInt(count);
+        for (uint32_t i = 0; i < count; i++) {
+            mWriter.writeInt(values[i]);
+        }
+    }
+
+    inline void addFloat(float value) {
+        mWriter.writeScalar(value);
+    }
+
+    void addFloats(const float* values, int count) {
+        mWriter.writeInt(count);
+        for (int i = 0; i < count; i++) {
+            mWriter.writeScalar(values[i]);
+        }
+    }
+
+    inline void addPoint(float x, float y) {
+        mWriter.writeScalar(x);
+        mWriter.writeScalar(y);
+    }
+
+    inline void addBounds(float left, float top, float right, float bottom) {
+        mWriter.writeScalar(left);
+        mWriter.writeScalar(top);
+        mWriter.writeScalar(right);
+        mWriter.writeScalar(bottom);
+    }
+
+    inline void addText(const void* text, size_t byteLength) {
+        mWriter.writeInt(byteLength);
+        mWriter.writePad(text, byteLength);
+    }
+
+    inline void addPath(const SkPath* path) {
+        if (mPathHeap == NULL) {
+            mPathHeap = new PathHeap();
+        }
+        addInt(mPathHeap->append(*path));
+    }
+
+    int find(SkTDArray<const SkFlatPaint*>& paints, const SkPaint* paint);
+
+    inline void addPaint(const SkPaint* paint) {
+        addInt(find(mPaints, paint));
+    }
+
+    int find(SkTDArray<const SkFlatMatrix*>& matrices, const SkMatrix* matrix);
+
+    inline void addMatrix(const SkMatrix* matrix) {
+        addInt(find(mMatrices, matrix));
+    }
+
+    int find(SkTDArray<const SkFlatBitmap*>& bitmaps, const SkBitmap& bitmap);
+
+    inline void addBitmap(const SkBitmap* bitmap) {
+        addInt(find(mBitmaps, *bitmap));
+    }
+
+    SkChunkAlloc mHeap;
+
+    int mBitmapIndex;
+    SkTDArray<const SkFlatBitmap*> mBitmaps;
+
+    int mMatrixIndex;
+    SkTDArray<const SkFlatMatrix*> mMatrices;
+
+    int mPaintIndex;
+    SkTDArray<const SkFlatPaint*> mPaints;
+
+    PathHeap* mPathHeap;
+    SkWriter32 mWriter;
+
+    SkRefCntRecorder mRCRecorder;
+    SkRefCntRecorder mTFRecorder;
+
+    friend class DisplayList;
+
+}; // class DisplayListRenderer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_DISPLAY_LIST_RENDERER_H
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
new file mode 100644
index 0000000..d50d36e
--- /dev/null
+++ b/libs/hwui/Extensions.h
@@ -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.
+ */
+
+#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 {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_EXTENSIONS 0
+
+// Debug
+#if DEBUG_EXTENSIONS
+    #define EXT_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define EXT_LOGD(...)
+#endif
+
+class Extensions {
+public:
+    Extensions() {
+        const char* buffer = (const char*) glGetString(GL_EXTENSIONS);
+        const char* current = buffer;
+        const char* head = current;
+        EXT_LOGD("Available GL extensions:");
+        do {
+            head = strchr(current, ' ');
+            String8 s(current, head ? head - current : strlen(current));
+            if (s.length()) {
+                mExtensionList.add(s);
+                EXT_LOGD("  %s", s.string());
+            }
+            current = head + 1;
+        } while (head);
+
+        mHasNPot = hasExtension("GL_OES_texture_npot");
+        mHasDrawPath = hasExtension("GL_NV_draw_path");
+        mHasCoverageSample = hasExtension("GL_NV_coverage_sample");
+        mHasFramebufferFetch = hasExtension("GL_NV_shader_framebuffer_fetch");
+
+        mExtensions = buffer;
+    }
+
+    inline bool hasNPot() const { return mHasNPot; }
+    inline bool hasDrawPath() const { return mHasDrawPath; }
+    inline bool hasCoverageSample() const { return mHasCoverageSample; }
+    inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
+
+    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;
+    bool mHasFramebufferFetch;
+}; // class Extensions
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_EXTENSIONS_H
diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp
new file mode 100644
index 0000000..2ef71c2
--- /dev/null
+++ b/libs/hwui/FboCache.cpp
@@ -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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <stdlib.h>
+
+#include "FboCache.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+FboCache::FboCache(): mMaxSize(DEFAULT_FBO_CACHE_SIZE) {
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_FBO_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting fbo cache size to %s", property);
+        mMaxSize = atoi(property);
+    } else {
+        LOGD("  Using default fbo cache size of %d", DEFAULT_FBO_CACHE_SIZE);
+    }
+}
+
+FboCache::~FboCache() {
+    clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t FboCache::getSize() {
+    return mCache.size();
+}
+
+uint32_t FboCache::getMaxSize() {
+    return mMaxSize;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void FboCache::clear() {
+    for (size_t i = 0; i < mCache.size(); i++) {
+        const GLuint fbo = mCache.itemAt(i);
+        glDeleteFramebuffers(1, &fbo);
+    }
+    mCache.clear();
+}
+
+GLuint FboCache::get() {
+    GLuint fbo;
+    if (mCache.size() > 0) {
+        fbo = mCache.itemAt(mCache.size() - 1);
+        mCache.removeAt(mCache.size() - 1);
+    } else {
+        glGenFramebuffers(1, &fbo);
+    }
+    return fbo;
+}
+
+bool FboCache::put(GLuint fbo) {
+    if (mCache.size() < mMaxSize) {
+        mCache.add(fbo);
+        return true;
+    }
+
+    glDeleteFramebuffers(1, &fbo);
+    return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FboCache.h b/libs/hwui/FboCache.h
new file mode 100644
index 0000000..ec4afe9
--- /dev/null
+++ b/libs/hwui/FboCache.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_FBO_CACHE_H
+#define ANDROID_UI_FBO_CACHE_H
+
+#include <GLES2/gl2.h>
+
+#include <utils/SortedVector.h>
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache
+///////////////////////////////////////////////////////////////////////////////
+
+class FboCache {
+public:
+    FboCache();
+    ~FboCache();
+
+    /**
+     * Returns an FBO from the cache. If no FBO is available, a new one
+     * is created. If creating a new FBO fails, 0 is returned.
+     *
+     * When an FBO is obtained from the cache, it is removed and the
+     * total number of FBOs available in the cache decreases.
+     *
+     * @return The name of the FBO, or 0 if no FBO can be obtained.
+     */
+    GLuint get();
+
+    /**
+     * Adds the specified FBO to the cache.
+     *
+     * @param fbo The FBO to add to the cache.
+     *
+     * @return True if the FBO was added, false otherwise.
+     */
+    bool put(GLuint fbo);
+
+    /**
+     * Clears the cache. This causes all FBOs to be deleted.
+     */
+    void clear();
+
+    /**
+     * Returns the current size of the cache.
+     */
+    uint32_t getSize();
+
+    /**
+     * Returns the maximum number of FBOs that the cache can hold.
+     */
+    uint32_t getMaxSize();
+
+private:
+    SortedVector<GLuint> mCache;
+    uint32_t mMaxSize;
+}; // class FboCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_FBO_CACHE_H
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
new file mode 100644
index 0000000..8389e56
--- /dev/null
+++ b/libs/hwui/FontRenderer.cpp
@@ -0,0 +1,833 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds) {
+    int nPenX = x + glyph->mBitmapLeft;
+    int nPenY = y + glyph->mBitmapTop;
+
+    int width = (int) glyph->mBitmapWidth;
+    int height = (int) glyph->mBitmapHeight;
+
+    if (bounds->bottom > nPenY) {
+        bounds->bottom = nPenY;
+    }
+    if (bounds->left > nPenX) {
+        bounds->left = nPenX;
+    }
+    if (bounds->right < nPenX + width) {
+        bounds->right = nPenX + width;
+    }
+    if (bounds->top < nPenY + height) {
+        bounds->top = nPenY + height;
+    }
+}
+
+void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
+    int nPenX = x + glyph->mBitmapLeft;
+    int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
+
+    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;
+
+    uint32_t cacheWidth = mState->getCacheWidth();
+    const uint8_t* cacheBuffer = mState->getTextTextureData();
+
+    uint32_t cacheX = 0, cacheY = 0;
+    int32_t bX = 0, bY = 0;
+    for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
+        for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
+            if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) {
+                LOGE("Skipping invalid index");
+                continue;
+            }
+            uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
+            bitmap[bY * bitmapW + bX] = tempCol;
+        }
+    }
+
+}
+
+Font::CachedGlyphInfo* Font::getCachedUTFChar(SkPaint* paint, int32_t utfChar) {
+    CachedGlyphInfo* cachedGlyph = NULL;
+    ssize_t index = mCachedGlyphs.indexOfKey(utfChar);
+    if (index >= 0) {
+        cachedGlyph = mCachedGlyphs.valueAt(index);
+    } else {
+        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 (bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
+        renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap,
+                bitmapW, bitmapH, NULL);
+    } else {
+        renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, 0, 0, NULL);
+    }
+
+}
+
+void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+        int numGlyphs, Rect *bounds) {
+    if (bounds == NULL) {
+        LOGE("No return rectangle provided to measure text");
+        return;
+    }
+    bounds->set(1e6, -1e6, -1e6, 1e6);
+    renderUTF(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds);
+}
+
+#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)
+
+void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+        int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
+        uint32_t bitmapW, uint32_t bitmapH,Rect *bounds) {
+    if (numGlyphs == 0 || text == NULL || len == 0) {
+        return;
+    }
+
+    SkFixed penX = SkIntToFixed(x);
+    int penY = y;
+    int glyphsLeft = 1;
+    if (numGlyphs > 0) {
+        glyphsLeft = numGlyphs;
+    }
+
+    SkFixed prevRsbDelta = 0;
+    penX += SK_Fixed1 / 2;
+
+    text += start;
+
+    while (glyphsLeft > 0) {
+        int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
+
+        // Reached the end of the string
+        if (utfChar < 0) {
+            break;
+        }
+
+        CachedGlyphInfo* cachedGlyph = getCachedUTFChar(paint, utfChar);
+        penX += SkAutoKern_AdjustF(prevRsbDelta, cachedGlyph->mLsbDelta);
+        prevRsbDelta = cachedGlyph->mRsbDelta;
+
+        // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
+        if (cachedGlyph->mIsValid) {
+            switch(mode) {
+            case FRAMEBUFFER:
+                drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY);
+                break;
+            case BITMAP:
+                drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bitmap, bitmapW, bitmapH);
+                break;
+            case MEASURE:
+                measureCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bounds);
+                break;
+            }
+        }
+
+        penX += 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;
+    glyph->mLsbDelta = skiaGlyph.fLsbDelta;
+    glyph->mRsbDelta = skiaGlyph.fRsbDelta;
+
+    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");
+
+    mGammaTable = NULL;
+    mInitialized = false;
+    mMaxNumberOfQuads = 1024;
+    mCurrentQuadIndex = 0;
+    mTextureId = 0;
+
+    mTextMeshPtr = NULL;
+    mTextTexture = NULL;
+
+    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();
+
+    if (mInitialized) {
+        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.fHeight > 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] = mGammaTable[tempCol];
+        }
+    }
+
+    return true;
+}
+
+void FontRenderer::initTextTexture() {
+    mTextTexture = new uint8_t[mCacheWidth * mCacheHeight];
+    memset(mTextTexture, 0, mCacheWidth * mCacheHeight * sizeof(uint8_t));
+
+    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);
+
+    mLinearFiltering = false;
+    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, 18, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 26, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 26, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 34, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 34, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 42, 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);
+    }
+}
+
+FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
+        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) {
+    checkInit();
+
+    if (!mCurrentFont) {
+        DropShadow image;
+        image.width = 0;
+        image.height = 0;
+        image.image = NULL;
+        image.penX = 0;
+        image.penY = 0;
+        return image;
+    }
+
+    Rect bounds;
+    mCurrentFont->measureUTF(paint, text, startIndex, len, numGlyphs, &bounds);
+    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
+    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
+    uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
+    for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
+        dataBuffer[i] = 0;
+    }
+
+    int penX = radius - bounds.left;
+    int penY = radius - bounds.bottom;
+
+    mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, penX, penY,
+            dataBuffer, paddedWidth, paddedHeight);
+    blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
+
+    DropShadow image;
+    image.width = paddedWidth;
+    image.height = paddedHeight;
+    image.image = dataBuffer;
+    image.penX = penX;
+    image.penY = penY;
+    return image;
+}
+
+void FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
+        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y) {
+    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.3  + 0.6
+    // The larger the radius gets, the more our gaussian blur
+    // will resemble a box blur since with large sigma
+    // the gaussian curve begins to lose its shape
+    float sigma = 0.3f * (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..f10efad
--- /dev/null
+++ b/libs/hwui/FontRenderer.h
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.
+     * If bitmap is specified, it will be used as the render target
+     */
+    void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+                     int numGlyphs, int x, int y,
+                     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;
+
+    enum RenderMode {
+        FRAMEBUFFER,
+        BITMAP,
+        MEASURE,
+    };
+
+    void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+                     int numGlyphs, int x, int y, RenderMode mode,
+                     uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+                     Rect *bounds);
+
+    void measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+                      int numGlyphs, Rect *bounds);
+
+    struct CachedGlyphInfo {
+        // Has the cache been invalidated?
+        bool mIsValid;
+        // 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;
+        // Auto-kerning
+        SkFixed mLsbDelta;
+        SkFixed mRsbDelta;
+    };
+
+    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 measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds);
+    void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
+    void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
+                          uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
+
+    CachedGlyphInfo* getCachedUTFChar(SkPaint* paint, int32_t utfChar);
+
+    FontRenderer* mState;
+    uint32_t mFontId;
+    float mFontSize;
+};
+
+class FontRenderer {
+public:
+    FontRenderer();
+    ~FontRenderer();
+
+    void init();
+    void deinit();
+
+    void setGammaTable(const uint8_t* gammaTable) {
+        mGammaTable = gammaTable;
+    }
+
+    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);
+
+    struct DropShadow {
+        DropShadow() { };
+
+        DropShadow(const DropShadow& dropShadow):
+            width(dropShadow.width), height(dropShadow.height),
+            image(dropShadow.image), penX(dropShadow.penX),
+            penY(dropShadow.penY) {
+        }
+
+        uint32_t width;
+        uint32_t height;
+        uint8_t* image;
+        int32_t penX;
+        int32_t penY;
+    };
+
+    // After renderDropShadow returns, the called owns the memory in DropShadow.image
+    // and is responsible for releasing it when it's done with it
+    DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
+            uint32_t len, int numGlyphs, uint32_t radius);
+
+    GLuint getTexture(bool linearFiltering = false) {
+        checkInit();
+        if (linearFiltering != mLinearFiltering) {
+            mLinearFiltering = linearFiltering;
+            const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST;
+
+            glBindTexture(GL_TEXTURE_2D, mTextureId);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+        }
+        return mTextureId;
+    }
+
+protected:
+    friend class Font;
+
+    const uint8_t* mGammaTable;
+
+    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 + 2 > mMaxHeight) {
+                return false;
+            }
+
+            if (mCurrentCol + glyph.fWidth + 2 < mMaxWidth) {
+                *retOriginX = mCurrentCol + 1;
+                *retOriginY = mCurrentRow + 1;
+                mCurrentCol += glyph.fWidth + 2;
+                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;
+
+    bool mLinearFiltering;
+
+    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/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp
new file mode 100644
index 0000000..6d087e3
--- /dev/null
+++ b/libs/hwui/GammaFontRenderer.cpp
@@ -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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "GammaFontRenderer.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+GammaFontRenderer::GammaFontRenderer() {
+    LOGD("Creating gamma font renderer");
+
+    // Get the renderer properties
+    char property[PROPERTY_VALUE_MAX];
+
+    // Get the gamma
+    float gamma = DEFAULT_TEXT_GAMMA;
+    if (property_get(PROPERTY_TEXT_GAMMA, property, NULL) > 0) {
+        LOGD("  Setting text gamma to %s", property);
+        gamma = atof(property);
+    } else {
+        LOGD("  Using default text gamma of %.2f", DEFAULT_TEXT_GAMMA);
+    }
+
+    // Get the black gamma threshold
+    mBlackThreshold = DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD;
+    if (property_get(PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD, property, NULL) > 0) {
+        LOGD("  Setting text black gamma threshold to %s", property);
+        mBlackThreshold = atoi(property);
+    } else {
+        LOGD("  Using default text black gamma threshold of %d",
+                DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD);
+    }
+
+    // Get the white gamma threshold
+    mWhiteThreshold = DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD;
+    if (property_get(PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD, property, NULL) > 0) {
+        LOGD("  Setting text white gamma threshold to %s", property);
+        mWhiteThreshold = atoi(property);
+    } else {
+        LOGD("  Using default white black gamma threshold of %d",
+                DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD);
+    }
+
+    // Compute the gamma tables
+    const float blackGamma = gamma;
+    const float whiteGamma = 1.0f / gamma;
+
+    for (uint32_t i = 0; i <= 255; i++) {
+        mDefault[i] = i;
+
+        const float v = i / 255.0f;
+        const float black = pow(v, blackGamma);
+        const float white = pow(v, whiteGamma);
+
+        mBlackGamma[i] = uint8_t((float)::floor(black * 255.0f + 0.5f));
+        mWhiteGamma[i] = uint8_t((float)::floor(white * 255.0f + 0.5f));
+    }
+
+    // Configure the font renderers
+    mDefaultRenderer.setGammaTable(&mDefault[0]);
+    mBlackGammaRenderer.setGammaTable(&mBlackGamma[0]);
+    mWhiteGammaRenderer.setGammaTable(&mWhiteGamma[0]);
+}
+
+FontRenderer& GammaFontRenderer::getFontRenderer(const SkPaint* paint) {
+    if (paint->getShader() == NULL) {
+        uint32_t c = paint->getColor();
+        const int r = (c >> 16) & 0xFF;
+        const int g = (c >>  8) & 0xFF;
+        const int b = (c      ) & 0xFF;
+        const int luminance = (r * 2 + g * 5 + b) >> 3;
+
+        if (luminance <= mBlackThreshold) {
+            return mBlackGammaRenderer;
+        } else if (luminance >= mWhiteThreshold) {
+            return mWhiteGammaRenderer;
+        }
+    }
+    return mDefaultRenderer;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
new file mode 100644
index 0000000..5fa45cf
--- /dev/null
+++ b/libs/hwui/GammaFontRenderer.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 ANDROID_UI_GAMMA_FONT_RENDERER_H
+#define ANDROID_UI_GAMMA_FONT_RENDERER_H
+
+#include <SkPaint.h>
+
+#include "FontRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+struct GammaFontRenderer {
+    GammaFontRenderer();
+
+    FontRenderer& getFontRenderer(const SkPaint* paint);
+
+private:
+    FontRenderer mDefaultRenderer;
+    FontRenderer mBlackGammaRenderer;
+    FontRenderer mWhiteGammaRenderer;
+
+    int mBlackThreshold;
+    int mWhiteThreshold;
+
+    uint8_t mDefault[256];
+    uint8_t mBlackGamma[256];
+    uint8_t mWhiteGamma[256];
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_GAMMA_FONT_RENDERER_H
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
new file mode 100644
index 0000000..35c6bea
--- /dev/null
+++ b/libs/hwui/GenerationCache.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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);
+    K getKeyAt(uint32_t index) const;
+    void put(K key, V value);
+    V remove(K key);
+    V removeOldest();
+    V getValueAt(uint32_t index) const;
+
+    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) {
+        for (uint32_t i = 0; i < mCache.size(); i++) {
+            sp<Entry<K, V> > entry = mCache.valueAt(i);
+            if (mListener) {
+                (*mListener)(entry->key, entry->value);
+            }
+        }
+    }
+    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>
+K GenerationCache<K, V>::getKeyAt(uint32_t index) const {
+    return mCache.keyAt(index);
+}
+
+template<typename K, typename V>
+V GenerationCache<K, V>::getValueAt(uint32_t index) const {
+    return mCache.valueAt(index);
+}
+
+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 = 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..97f4cb4
--- /dev/null
+++ b/libs/hwui/GradientCache.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 <GLES2/gl2.h>
+
+#include <SkCanvas.h>
+#include <SkGradientShader.h>
+
+#include <utils/threads.h>
+
+#include "GradientCache.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+GradientCache::GradientCache():
+        mCache(GenerationCache<SkShader*, Texture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(MB(DEFAULT_GRADIENT_CACHE_SIZE)) {
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting gradient cache size to %sMB", property);
+        setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default gradient cache size of %.2fMB", DEFAULT_GRADIENT_CACHE_SIZE);
+    }
+
+    mCache.setOnEntryRemovedListener(this);
+}
+
+GradientCache::GradientCache(uint32_t maxByteSize):
+        mCache(GenerationCache<SkShader*, Texture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(maxByteSize) {
+    mCache.setOnEntryRemovedListener(this);
+}
+
+GradientCache::~GradientCache() {
+    Mutex::Autolock _l(mLock);
+    mCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t GradientCache::getSize() {
+    Mutex::Autolock _l(mLock);
+    return mSize;
+}
+
+uint32_t GradientCache::getMaxSize() {
+    Mutex::Autolock _l(mLock);
+    return mMaxSize;
+}
+
+void GradientCache::setMaxSize(uint32_t maxSize) {
+    Mutex::Autolock _l(mLock);
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        mCache.removeOldest();
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void GradientCache::operator()(SkShader*& shader, Texture*& texture) {
+    // Already locked here
+    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) {
+    Mutex::Autolock _l(mLock);
+    return mCache.get(shader);
+}
+
+void GradientCache::remove(SkShader* shader) {
+    Mutex::Autolock _l(mLock);
+    mCache.remove(shader);
+}
+
+void GradientCache::clear() {
+    Mutex::Autolock _l(mLock);
+    mCache.clear();
+}
+
+Texture* GradientCache::addLinearGradient(SkShader* shader, 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);
+
+    mLock.lock();
+    // Asume the cache is always big enough
+    const uint32_t size = bitmap.rowBytes() * bitmap.height();
+    while (mSize + size > mMaxSize) {
+        mCache.removeOldest();
+    }
+    mLock.unlock();
+
+    Texture* texture = new Texture;
+    generateTexture(&bitmap, texture);
+
+    mLock.lock();
+    mSize += size;
+    mCache.put(shader, texture);
+    mLock.unlock();
+
+    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..c829fd4
--- /dev/null
+++ b/libs/hwui/GradientCache.h
@@ -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.
+ */
+
+#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();
+    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, uint32_t* colors, float* positions,
+            int count, SkShader::TileMode tileMode = SkShader::kClamp_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;
+
+    /**
+     * Used to access mCache and mSize. All methods are accessed from a single
+     * thread except for remove().
+     */
+    mutable Mutex mLock;
+}; // 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..6024765
--- /dev/null
+++ b/libs/hwui/Layer.h
@@ -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.
+ */
+
+#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) { }
+    LayerSize(const uint32_t width, const uint32_t height): width(width), height(height) { }
+    LayerSize(const LayerSize& size): width(size.width), height(size.height) { }
+
+    uint32_t width;
+    uint32_t height;
+
+    bool operator<(const LayerSize& rhs) const {
+        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 or FBO.
+ */
+struct Layer {
+    /**
+     * Coordinates of the layer.
+     */
+    Rect layer;
+    /**
+     * Name of the texture used to render the layer.
+     */
+    GLuint texture;
+    /**
+     * Name of the FBO used to render the layer. If the name is 0
+     * this layer is not backed by an FBO, but a simple texture.
+     */
+    GLuint fbo;
+    /**
+     * Opacity of the layer.
+     */
+    int alpha;
+    /**
+     * Blending mode of the layer.
+     */
+    SkXfermode::Mode mode;
+    /**
+     * Indicates whether this layer should be blended.
+     */
+    bool blend;
+    /**
+     * Indicates whether this layer has been used already.
+     */
+    bool empty;
+}; // 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..2183718
--- /dev/null
+++ b/libs/hwui/LayerCache.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 <utils/Log.h>
+
+#include "LayerCache.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+LayerCache::LayerCache():
+        mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting layer cache size to %sMB", property);
+        setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default layer cache size of %.2fMB", DEFAULT_LAYER_CACHE_SIZE);
+    }
+}
+
+LayerCache::LayerCache(uint32_t maxByteSize):
+        mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
+        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;
+
+        glDeleteTextures(1, &layer->texture);
+        delete layer;
+    }
+}
+
+void LayerCache::clear() {
+    mCache.setOnEntryRemovedListener(this);
+    mCache.clear();
+    mCache.setOnEntryRemovedListener(NULL);
+}
+
+Layer* LayerCache::get(LayerSize& size) {
+    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;
+        layer->empty = true;
+        layer->fbo = 0;
+
+        glGenTextures(1, &layer->texture);
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+#if DEBUG_LAYERS
+        uint32_t size = mCache.size();
+        for (uint32_t i = 0; i < size; i++) {
+            LayerSize ls = mCache.getKeyAt(i);
+            LAYER_LOGD("  Layer size %dx%d", ls.width, ls.height);
+        }
+#endif
+    }
+
+    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);
+            LAYER_LOGD("  Deleting layer %.2fx%.2f", oldest->layer.getWidth(),
+                    oldest->layer.getHeight());
+        }
+
+        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..cbb7ae2
--- /dev/null
+++ b/libs/hwui/LayerCache.h
@@ -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.
+ */
+
+#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();
+    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
+     */
+    Layer* get(LayerSize& size);
+
+    /**
+     * 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 mSize;
+    uint32_t mMaxSize;
+}; // class LayerCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_LAYER_CACHE_H
diff --git a/libs/hwui/Line.h b/libs/hwui/Line.h
new file mode 100644
index 0000000..64bdd6a
--- /dev/null
+++ b/libs/hwui/Line.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_LINE_H
+#define ANDROID_UI_LINE_H
+
+#include <GLES2/gl2.h>
+
+#include <cmath>
+
+#include <sys/types.h>
+
+#include "Patch.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Globals
+///////////////////////////////////////////////////////////////////////////////
+
+// Alpha8 texture used to perform texture anti-aliasing
+static const uint8_t gLineTexture[] = {
+        0,   0,   0,   0, 0,
+        0, 255, 255, 255, 0,
+        0, 255, 255, 255, 0,
+        0, 255, 255, 255, 0,
+        0,   0,   0,   0, 0
+};
+static const GLsizei gLineTextureWidth = 5;
+static const GLsizei gLineTextureHeight = 5;
+static const float gLineAABias = 1.0f;
+
+///////////////////////////////////////////////////////////////////////////////
+// Line
+///////////////////////////////////////////////////////////////////////////////
+
+class Line {
+public:
+    Line(): mXDivsCount(2), mYDivsCount(2) {
+        mPatch = new Patch(mXDivsCount, mYDivsCount);
+        mXDivs = new int32_t[mXDivsCount];
+        mYDivs = new int32_t[mYDivsCount];
+
+        mXDivs[0] = mYDivs[0] = 2;
+        mXDivs[1] = mYDivs[1] = 3;
+
+        glGenTextures(1, &mTexture);
+        glBindTexture(GL_TEXTURE_2D, mTexture);
+
+        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);
+
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, gLineTextureWidth, gLineTextureHeight, 0,
+                GL_ALPHA, GL_UNSIGNED_BYTE, gLineTexture);
+    }
+
+    ~Line() {
+        delete mPatch;
+        delete[] mXDivs;
+        delete[] mYDivs;
+
+        glDeleteTextures(1, &mTexture);
+    }
+
+    inline float getLength(float x1, float y1, float x2, float y2) {
+        return sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
+    }
+
+    void update(float x1, float y1, float x2, float y2, float lineWidth, float& tx, float& ty) {
+        const float length = getLength(x1, y1, x2, y2);
+        const float half = lineWidth * 0.5f;
+
+        mPatch->updateVertices(gLineTextureWidth, gLineTextureHeight,
+                -gLineAABias, -half - gLineAABias, length + gLineAABias, half + gLineAABias,
+                mXDivs, mYDivs, mXDivsCount, mYDivsCount);
+
+        tx = -gLineAABias;
+        ty = lineWidth <= 1.0f ? -gLineAABias : -half - gLineAABias;
+    }
+
+    inline GLvoid* getVertices() const {
+        return &mPatch->vertices[0].position[0];
+    }
+
+    inline GLvoid* getTexCoords() const {
+        return &mPatch->vertices[0].texture[0];
+    }
+
+    inline GLsizei getElementsCount() const {
+        return mPatch->verticesCount;
+    }
+
+    inline GLuint getTexture() const {
+        return mTexture;
+    }
+
+private:
+    uint32_t mXDivsCount;
+    uint32_t mYDivsCount;
+
+    int32_t* mXDivs;
+    int32_t* mYDivs;
+
+    Patch* mPatch;
+
+    GLuint mTexture;
+}; // class Line
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_LINE_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..219fd5e
--- /dev/null
+++ b/libs/hwui/Matrix.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 "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[kScaleX]       = 1.0f;
+    data[kSkewY]        = 0.0f;
+    data[2]             = 0.0f;
+    data[kPerspective0] = 0.0f;
+
+    data[kSkewX]        = 0.0f;
+    data[kScaleY]       = 1.0f;
+    data[6]             = 0.0f;
+    data[kPerspective1] = 0.0f;
+
+    data[8]             = 0.0f;
+    data[9]             = 0.0f;
+    data[kScaleZ]       = 1.0f;
+    data[11]            = 0.0f;
+
+    data[kTranslateX]   = 0.0f;
+    data[kTranslateY]   = 0.0f;
+    data[kTranslateZ]   = 0.0f;
+    data[kPerspective2] = 1.0f;
+
+    mSimpleMatrix = true;
+}
+
+#define EPSILON 0.00001f
+#define almost(u, v) (fabs((u) - (v)) < EPSILON)
+
+bool Matrix4::changesBounds() {
+    return !(almost(data[0], 1.0f) && almost(data[1], 0.0f) && almost(data[2], 0.0f) &&
+             almost(data[4], 0.0f) && almost(data[5], 1.0f) && almost(data[6], 0.0f) &&
+             almost(data[8], 0.0f) && almost(data[9], 0.0f) && almost(data[10], 1.0f));
+}
+
+void Matrix4::load(const float* v) {
+    memcpy(data, v, sizeof(data));
+    mSimpleMatrix = false;
+}
+
+void Matrix4::load(const Matrix4& v) {
+    memcpy(data, v.data, sizeof(data));
+    mSimpleMatrix = v.mSimpleMatrix;
+}
+
+void Matrix4::load(const SkMatrix& v) {
+    memset(data, 0, sizeof(data));
+
+    data[kScaleX]     = v[SkMatrix::kMScaleX];
+    data[kSkewX]      = v[SkMatrix::kMSkewX];
+    data[kTranslateX] = v[SkMatrix::kMTransX];
+
+    data[kSkewY]      = v[SkMatrix::kMSkewY];
+    data[kScaleY]     = v[SkMatrix::kMScaleY];
+    data[kTranslateY] = v[SkMatrix::kMTransY];
+
+    data[kPerspective0]  = v[SkMatrix::kMPersp0];
+    data[kPerspective1]  = v[SkMatrix::kMPersp1];
+    data[kPerspective2]  = v[SkMatrix::kMPersp2];
+
+    data[kScaleZ] = 1.0f;
+
+    mSimpleMatrix = (v.getType() <= SkMatrix::kScale_Mask);
+}
+
+void Matrix4::copyTo(SkMatrix& v) const {
+    v.reset();
+
+    v.set(SkMatrix::kMScaleX, data[kScaleX]);
+    v.set(SkMatrix::kMSkewX,  data[kSkewX]);
+    v.set(SkMatrix::kMTransX, data[kTranslateX]);
+
+    v.set(SkMatrix::kMSkewY,  data[kSkewY]);
+    v.set(SkMatrix::kMScaleY, data[kScaleY]);
+    v.set(SkMatrix::kMTransY, data[kTranslateY]);
+
+    v.set(SkMatrix::kMPersp0, data[kPerspective0]);
+    v.set(SkMatrix::kMPersp1, data[kPerspective1]);
+    v.set(SkMatrix::kMPersp2, data[kPerspective2]);
+}
+
+void Matrix4::loadInverse(const Matrix4& v) {
+    double scale = 1.0 /
+            (v.data[kScaleX] * ((double) v.data[kScaleY]  * v.data[kPerspective2] -
+                    (double) v.data[kTranslateY] * v.data[kPerspective1]) +
+             v.data[kSkewX] * ((double) v.data[kTranslateY] * v.data[kPerspective0] -
+                     (double) v.data[kSkewY] * v.data[kPerspective2]) +
+             v.data[kTranslateX] * ((double) v.data[kSkewY] * v.data[kPerspective1] -
+                     (double) v.data[kScaleY] * v.data[kPerspective0]));
+
+    data[kScaleX] = (v.data[kScaleY] * v.data[kPerspective2] -
+            v.data[kTranslateY] * v.data[kPerspective1])  * scale;
+    data[kSkewX] = (v.data[kTranslateX] * v.data[kPerspective1] -
+            v.data[kSkewX]  * v.data[kPerspective2]) * scale;
+    data[kTranslateX] = (v.data[kSkewX] * v.data[kTranslateY] -
+            v.data[kTranslateX] * v.data[kScaleY])  * scale;
+
+    data[kSkewY] = (v.data[kTranslateY] * v.data[kPerspective0] -
+            v.data[kSkewY]  * v.data[kPerspective2]) * scale;
+    data[kScaleY] = (v.data[kScaleX] * v.data[kPerspective2] -
+            v.data[kTranslateX] * v.data[kPerspective0])  * scale;
+    data[kTranslateY] = (v.data[kTranslateX] * v.data[kSkewY] -
+            v.data[kScaleX]  * v.data[kTranslateY]) * scale;
+
+    data[kPerspective0] = (v.data[kSkewY] * v.data[kPerspective1] -
+            v.data[kScaleY] * v.data[kPerspective0]) * scale;
+    data[kPerspective1] = (v.data[kSkewX] * v.data[kPerspective0] -
+            v.data[kScaleX] * v.data[kPerspective1]) * scale;
+    data[kPerspective2] = (v.data[kScaleX] * v.data[kScaleY] -
+            v.data[kSkewX] * v.data[kSkewY]) * scale;
+
+    mSimpleMatrix = v.mSimpleMatrix;
+}
+
+void Matrix4::copyTo(float* v) const {
+    memcpy(v, data, sizeof(data));
+}
+
+float Matrix4::getTranslateX() {
+    return data[kTranslateX];
+}
+
+float Matrix4::getTranslateY() {
+    return data[kTranslateY];
+}
+
+void Matrix4::multiply(float v) {
+    for (int i = 0; i < 16; i++) {
+        data[i] *= v;
+    }
+}
+
+void Matrix4::loadTranslate(float x, float y, float z) {
+    loadIdentity();
+    data[kTranslateX] = x;
+    data[kTranslateY] = y;
+    data[kTranslateZ] = z;
+}
+
+void Matrix4::loadScale(float sx, float sy, float sz) {
+    loadIdentity();
+    data[kScaleX] = sx;
+    data[kScaleY] = sy;
+    data[kScaleZ] = sz;
+}
+
+void Matrix4::loadRotate(float angle, float x, float y, float z) {
+    data[kPerspective0]  = 0.0f;
+    data[kPerspective1]  = 0.0f;
+    data[11]             = 0.0f;
+    data[kTranslateX]    = 0.0f;
+    data[kTranslateY]    = 0.0f;
+    data[kTranslateZ]    = 0.0f;
+    data[kPerspective2]  = 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[kScaleX] = x * x * nc +  c;
+    data[kSkewX]  =    xy * nc - zs;
+    data[8]       =    zx * nc + ys;
+    data[kSkewY]  =    xy * nc + zs;
+    data[kScaleY] = y * y * nc +  c;
+    data[9]       =    yz * nc - xs;
+    data[2]       =    zx * nc - ys;
+    data[6]       =    yz * nc + xs;
+    data[kScaleZ] = z * z * nc +  c;
+
+    mSimpleMatrix = false;
+}
+
+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);
+    }
+
+    mSimpleMatrix = u.mSimpleMatrix && v.mSimpleMatrix;
+}
+
+void Matrix4::loadOrtho(float left, float right, float bottom, float top, float near, float far) {
+    loadIdentity();
+    data[kScaleX] = 2.0f / (right - left);
+    data[kScaleY] = 2.0f / (top - bottom);
+    data[kScaleZ] = -2.0f / (far - near);
+    data[kTranslateX] = -(right + left) / (right - left);
+    data[kTranslateY] = -(top + bottom) / (top - bottom);
+    data[kTranslateZ] = -(far + near) / (far - near);
+}
+
+#define MUL_ADD_STORE(a, b, c) a = (a) * (b) + (c)
+
+void Matrix4::mapPoint(float& x, float& y) const {
+    if (mSimpleMatrix) {
+        MUL_ADD_STORE(x, data[kScaleX], data[kTranslateX]);
+        MUL_ADD_STORE(y, data[kScaleY], data[kTranslateY]);
+        return;
+    }
+
+    float dx = x * data[kScaleX] + y * data[kSkewX] + data[kTranslateX];
+    float dy = x * data[kSkewY] + y * data[kScaleY] + data[kTranslateY];
+    float dz = x * data[kPerspective0] + y * data[kPerspective1] + data[kPerspective2];
+    if (dz) dz = 1.0f / dz;
+
+    x = dx * dz;
+    y = dy * dz;
+}
+
+void Matrix4::mapRect(Rect& r) const {
+    if (mSimpleMatrix) {
+        MUL_ADD_STORE(r.left, data[kScaleX], data[kTranslateX]);
+        MUL_ADD_STORE(r.right, data[kScaleX], data[kTranslateX]);
+        MUL_ADD_STORE(r.top, data[kScaleY], data[kTranslateY]);
+        MUL_ADD_STORE(r.bottom, data[kScaleY], data[kTranslateY]);
+        return;
+    }
+
+    float vertices[] = {
+        r.left, r.top,
+        r.right, r.top,
+        r.right, r.bottom,
+        r.left, r.bottom
+    };
+
+    float x, y, z;
+
+    for (int i = 0; i < 8; i+= 2) {
+        float px = vertices[i];
+        float py = vertices[i + 1];
+
+        x = px * data[kScaleX] + py * data[kSkewX] + data[kTranslateX];
+        y = px * data[kSkewY] + py * data[kScaleY] + data[kTranslateY];
+        z = px * data[kPerspective0] + py * data[kPerspective1] + data[kPerspective2];
+        if (z) z = 1.0f / z;
+
+        vertices[i] = x * z;
+        vertices[i + 1] = y * z;
+    }
+
+    r.left = r.right = vertices[0];
+    r.top = r.bottom = vertices[1];
+
+    for (int i = 2; i < 8; i += 2) {
+        x = vertices[i];
+        y = vertices[i + 1];
+
+        if (x < r.left) r.left = x;
+        else if (x > r.right) r.right = x;
+        if (y < r.top) r.top = y;
+        else if (y > r.bottom) r.bottom = y;
+    }
+}
+
+void Matrix4::dump() const {
+    LOGD("Matrix4[simple=%d", mSimpleMatrix);
+    LOGD("  %f %f %f %f", data[kScaleX], data[kSkewX], data[8], data[kTranslateX]);
+    LOGD("  %f %f %f %f", data[kSkewY], data[kScaleY], data[9], data[kTranslateY]);
+    LOGD("  %f %f %f %f", data[2], data[6], data[kScaleZ], data[kTranslateZ]);
+    LOGD("  %f %f %f %f", data[kPerspective0], data[kPerspective1], data[11], data[kPerspective2]);
+    LOGD("]");
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
new file mode 100644
index 0000000..fe81159
--- /dev/null
+++ b/libs/hwui/Matrix.h
@@ -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.
+ */
+
+#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];
+
+    enum Entry {
+        kScaleX = 0,
+        kSkewY = 1,
+        kPerspective0 = 3,
+        kSkewX = 4,
+        kScaleY = 5,
+        kPerspective1 = 7,
+        kScaleZ = 10,
+        kTranslateX = 12,
+        kTranslateY = 13,
+        kTranslateZ = 14,
+        kPerspective2 = 15
+    };
+
+    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 multiply(float v);
+
+    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);
+    }
+
+    bool changesBounds();
+
+    void copyTo(float* v) const;
+    void copyTo(SkMatrix& v) const;
+
+    void mapRect(Rect& r) const;
+    void mapPoint(float& x, float& y) const;
+
+    float getTranslateX();
+    float getTranslateY();
+
+    void dump() const;
+
+private:
+    bool mSimpleMatrix;
+
+    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/OpenGLDebugRenderer.cpp b/libs/hwui/OpenGLDebugRenderer.cpp
new file mode 100644
index 0000000..4e5123e
--- /dev/null
+++ b/libs/hwui/OpenGLDebugRenderer.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/StopWatch.h>
+
+#include "OpenGLDebugRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+void OpenGLDebugRenderer::prepare() {
+    mPrimitivesCount = 0;
+    LOGD("========= Frame start =========");
+    OpenGLRenderer::prepare();
+}
+
+void OpenGLDebugRenderer::finish() {
+    LOGD("========= Frame end =========");
+    LOGD("Primitives draw count = %d", mPrimitivesCount);
+    OpenGLRenderer::finish();
+}
+
+void OpenGLDebugRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
+    mPrimitivesCount++;
+    StopWatch w("composeLayer");
+    return OpenGLRenderer::composeLayer(current, previous);
+}
+
+int OpenGLDebugRenderer::saveLayer(float left, float top, float right, float bottom,
+        const SkPaint* p, int flags) {
+    mPrimitivesCount++;
+    StopWatch w("saveLayer");
+    return OpenGLRenderer::saveLayer(left, top, right, bottom, p, flags);
+}
+
+void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, float left, float top,
+        const SkPaint* paint) {
+    mPrimitivesCount++;
+    StopWatch w("drawBitmap");
+    OpenGLRenderer::drawBitmap(bitmap, left, top, paint);
+}
+
+void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix,
+        const SkPaint* paint) {
+    mPrimitivesCount++;
+    StopWatch w("drawBitmapMatrix");
+    OpenGLRenderer::drawBitmap(bitmap, matrix, paint);
+}
+
+void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
+        float srcRight, float srcBottom, float dstLeft, float dstTop,
+        float dstRight, float dstBottom, const SkPaint* paint) {
+    mPrimitivesCount++;
+    StopWatch w("drawBitmapRect");
+    OpenGLRenderer::drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom,
+            dstLeft, dstTop, dstRight, dstBottom, paint);
+}
+
+void OpenGLDebugRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
+        uint32_t width, uint32_t height, float left, float top, float right, float bottom,
+        const SkPaint* paint) {
+    mPrimitivesCount++;
+    StopWatch w("drawPatch");
+    OpenGLRenderer::drawPatch(bitmap, xDivs, yDivs, width, height,
+            left, top, right, bottom, paint);
+}
+
+void OpenGLDebugRenderer::drawColor(int color, SkXfermode::Mode mode) {
+    mPrimitivesCount++;
+    StopWatch w("drawColor");
+    OpenGLRenderer::drawColor(color, mode);
+}
+
+void OpenGLDebugRenderer::drawRect(float left, float top, float right, float bottom,
+        const SkPaint* paint) {
+    mPrimitivesCount++;
+    StopWatch w("drawRect");
+    OpenGLRenderer::drawRect(left, top, right, bottom, paint);
+}
+
+void OpenGLDebugRenderer::drawPath(SkPath* path, SkPaint* paint) {
+    mPrimitivesCount++;
+    StopWatch w("drawPath");
+    OpenGLRenderer::drawPath(path, paint);
+}
+
+void OpenGLDebugRenderer::drawLines(float* points, int count, const SkPaint* paint) {
+    mPrimitivesCount++;
+    StopWatch w("drawLines");
+    OpenGLRenderer::drawLines(points, count, paint);
+}
+
+void OpenGLDebugRenderer::drawText(const char* text, int bytesCount, int count, float x, float y,
+        SkPaint* paint) {
+    mPrimitivesCount++;
+    StopWatch w("drawText");
+    OpenGLRenderer::drawText(text, bytesCount, count, x, y, paint);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/OpenGLDebugRenderer.h b/libs/hwui/OpenGLDebugRenderer.h
new file mode 100644
index 0000000..ce15512
--- /dev/null
+++ b/libs/hwui/OpenGLDebugRenderer.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 ANDROID_UI_OPENGL_DEBUG_RENDERER_H
+#define ANDROID_UI_OPENGL_DEBUG_RENDERER_H
+
+#include "OpenGLRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Renderer
+///////////////////////////////////////////////////////////////////////////////
+
+class OpenGLDebugRenderer: public OpenGLRenderer {
+public:
+    OpenGLDebugRenderer(): mPrimitivesCount(0) {
+    }
+
+    ~OpenGLDebugRenderer() {
+    }
+
+    void prepare();
+    void finish();
+
+    int saveLayer(float left, float top, float right, float bottom,
+            const SkPaint* p, int flags);
+
+    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, const int32_t* xDivs, const int32_t* yDivs,
+            uint32_t width, uint32_t height, 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 drawPath(SkPath* path, SkPaint* paint);
+    void drawLines(float* points, int count, const SkPaint* paint);
+    void drawText(const char* text, int bytesCount, int count, float x, float y,
+            SkPaint* paint);
+
+protected:
+    void composeLayer(sp<Snapshot> current, sp<Snapshot> previous);
+
+private:
+    uint32_t mPrimitivesCount;
+
+}; // class OpenGLDebugRenderer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_OPENGL_DEBUG_RENDERER_H
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
new file mode 100644
index 0000000..ee5fe22
--- /dev/null
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -0,0 +1,1323 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <utils/Log.h>
+#include <utils/StopWatch.h>
+
+#include "OpenGLRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+#define REQUIRED_TEXTURE_UNITS_COUNT 3
+
+// Generates simple and textured vertices
+#define FV(x, y, u, v) { { x, y }, { u, v } }
+
+#define RAD_TO_DEG (180.0f / 3.14159265f)
+#define MIN_ANGLE 0.001f
+
+///////////////////////////////////////////////////////////////////////////////
+// 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 }
+};
+
+// This array contains the swapped version of each SkXfermode. For instance
+// this array's SrcOver blending mode is actually DstOver. You can refer to
+// createLayer() for more information on the purpose of this array.
+static const Blender gBlendsSwap[] = {
+        { SkXfermode::kClear_Mode,   GL_ZERO,                 GL_ZERO },
+        { SkXfermode::kSrc_Mode,     GL_ZERO,                 GL_ONE },
+        { SkXfermode::kDst_Mode,     GL_ONE,                  GL_ZERO },
+        { SkXfermode::kSrcOver_Mode, GL_ONE_MINUS_DST_ALPHA,  GL_ONE },
+        { SkXfermode::kDstOver_Mode, GL_ONE,                  GL_ONE_MINUS_SRC_ALPHA },
+        { SkXfermode::kSrcIn_Mode,   GL_ZERO,                 GL_SRC_ALPHA },
+        { SkXfermode::kDstIn_Mode,   GL_DST_ALPHA,            GL_ZERO },
+        { SkXfermode::kSrcOut_Mode,  GL_ZERO,                 GL_ONE_MINUS_SRC_ALPHA },
+        { SkXfermode::kDstOut_Mode,  GL_ONE_MINUS_DST_ALPHA,  GL_ZERO },
+        { SkXfermode::kSrcATop_Mode, GL_ONE_MINUS_DST_ALPHA,  GL_SRC_ALPHA },
+        { SkXfermode::kDstATop_Mode, GL_DST_ALPHA,            GL_ONE_MINUS_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(): mCaches(Caches::getInstance()) {
+    mShader = NULL;
+    mColorFilter = NULL;
+    mHasShadow = false;
+
+    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);
+    }
+
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
+}
+
+OpenGLRenderer::~OpenGLRenderer() {
+    // The context has already been destroyed at this point, do not call
+    // GL APIs. All GL state should be kept in Caches.h
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// 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;
+    mFirstSnapshot->viewport.set(0, 0, width, height);
+}
+
+void OpenGLRenderer::prepare() {
+    mSnapshot = new Snapshot(mFirstSnapshot,
+            SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+    mSaveCount = 1;
+
+    glViewport(0, 0, mWidth, mHeight);
+
+    glDisable(GL_DITHER);
+    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);
+}
+
+void OpenGLRenderer::finish() {
+#if DEBUG_OPENGL
+    GLenum status = GL_NO_ERROR;
+    while ((status = glGetError()) != GL_NO_ERROR) {
+        LOGD("GL error from OpenGLRenderer: 0x%x", status);
+    }
+#endif
+}
+
+void OpenGLRenderer::acquireContext() {
+    if (mCaches.currentProgram) {
+        if (mCaches.currentProgram->isInUse()) {
+            mCaches.currentProgram->remove();
+            mCaches.currentProgram = NULL;
+        }
+    }
+}
+
+void OpenGLRenderer::releaseContext() {
+    glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
+
+    glEnable(GL_SCISSOR_TEST);
+    setScissorFromClip();
+
+    glDisable(GL_DITHER);
+
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+    if (mCaches.blend) {
+        glEnable(GL_BLEND);
+        glBlendFunc(mCaches.lastSrcMode, mCaches.lastDstMode);
+        glBlendEquation(GL_FUNC_ADD);
+    } else {
+        glDisable(GL_BLEND);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// State management
+///////////////////////////////////////////////////////////////////////////////
+
+int OpenGLRenderer::getSaveCount() const {
+    return mSaveCount;
+}
+
+int OpenGLRenderer::save(int flags) {
+    return saveSnapshot(flags);
+}
+
+void OpenGLRenderer::restore() {
+    if (mSaveCount > 1) {
+        restoreSnapshot();
+    }
+}
+
+void OpenGLRenderer::restoreToCount(int saveCount) {
+    if (saveCount < 1) saveCount = 1;
+
+    while (mSaveCount > saveCount) {
+        restoreSnapshot();
+    }
+}
+
+int OpenGLRenderer::saveSnapshot(int flags) {
+    mSnapshot = new Snapshot(mSnapshot, flags);
+    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) {
+        Rect& r = previous->viewport;
+        glViewport(r.left, r.top, r.right, r.bottom);
+        mOrthoMatrix.load(current->orthoMatrix);
+    }
+
+    mSaveCount--;
+    mSnapshot = previous;
+
+    if (restoreLayer) {
+        composeLayer(current, previous);
+    }
+
+    if (restoreClip) {
+        setScissorFromClip();
+    }
+
+    return restoreClip;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Layers
+///////////////////////////////////////////////////////////////////////////////
+
+int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
+        const SkPaint* p, int flags) {
+    const GLuint previousFbo = mSnapshot->fbo;
+    const int count = saveSnapshot(flags);
+
+    int alpha = 255;
+    SkXfermode::Mode mode;
+
+    if (p) {
+        alpha = p->getAlpha();
+        if (!mExtensions.hasFramebufferFetch()) {
+            const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
+            if (!isMode) {
+                // Assume SRC_OVER
+                mode = SkXfermode::kSrcOver_Mode;
+            }
+        } else {
+            mode = getXfermode(p->getXfermode());
+        }
+    } else {
+        mode = SkXfermode::kSrcOver_Mode;
+    }
+
+    createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags, previousFbo);
+
+    return count;
+}
+
+int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bottom,
+        int alpha, int flags) {
+    if (alpha == 0xff) {
+        return saveLayer(left, top, right, bottom, NULL, flags);
+    } else {
+        SkPaint paint;
+        paint.setAlpha(alpha);
+        return saveLayer(left, top, right, bottom, &paint, flags);
+    }
+}
+
+/**
+ * Layers are viewed by Skia are slightly different than layers in image editing
+ * programs (for instance.) When a layer is created, previously created layers
+ * and the frame buffer still receive every drawing command. For instance, if a
+ * layer is created and a shape intersecting the bounds of the layers and the
+ * framebuffer is draw, the shape will be drawn on both (unless the layer was
+ * created with the SkCanvas::kClipToLayer_SaveFlag flag.)
+ *
+ * A way to implement layers is to create an FBO for each layer, backed by an RGBA
+ * texture. Unfortunately, this is inefficient as it requires every primitive to
+ * be drawn n + 1 times, where n is the number of active layers. In practice this
+ * means, for every primitive:
+ *   - Switch active frame buffer
+ *   - Change viewport, clip and projection matrix
+ *   - Issue the drawing
+ *
+ * Switching rendering target n + 1 times per drawn primitive is extremely costly.
+ * To avoid this, layers are implemented in a different way here.
+ *
+ * This implementation relies on the frame buffer being at least RGBA 8888. When
+ * a layer is created, only a texture is created, not an FBO. The content of the
+ * frame buffer contained within the layer's bounds is copied into this texture
+ * using glCopyTexImage2D(). The layer's region is then cleared(1) in the frame
+ * buffer and drawing continues as normal. This technique therefore treats the
+ * frame buffer as a scratch buffer for the layers.
+ *
+ * To compose the layers back onto the frame buffer, each layer texture
+ * (containing the original frame buffer data) is drawn as a simple quad over
+ * the frame buffer. The trick is that the quad is set as the composition
+ * destination in the blending equation, and the frame buffer becomes the source
+ * of the composition.
+ *
+ * Drawing layers with an alpha value requires an extra step before composition.
+ * An empty quad is drawn over the layer's region in the frame buffer. This quad
+ * is drawn with the rgba color (0,0,0,alpha). The alpha value offered by the
+ * quad is used to multiply the colors in the frame buffer. This is achieved by
+ * changing the GL blend functions for the GL_FUNC_ADD blend equation to
+ * GL_ZERO, GL_SRC_ALPHA.
+ *
+ * Because glCopyTexImage2D() can be slow, an alternative implementation might
+ * be use to draw a single clipped layer. The implementation described above
+ * is correct in every case.
+ *
+ * (1) The frame buffer is actually not cleared right away. To allow the GPU
+ *     to potentially optimize series of calls to glCopyTexImage2D, the frame
+ *     buffer is left untouched until the first drawing operation. Only when
+ *     something actually gets drawn are the layers regions cleared.
+ */
+bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
+        float right, float bottom, int alpha, SkXfermode::Mode mode,
+        int flags, GLuint previousFbo) {
+    LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
+    LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
+
+    const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
+
+    // Window coordinates of the layer
+    Rect bounds(left, top, right, bottom);
+    if (!fboLayer) {
+        mSnapshot->transform->mapRect(bounds);
+        // Layers only make sense if they are in the framebuffer's bounds
+        bounds.intersect(*mSnapshot->clipRect);
+        bounds.snapToPixelBoundaries();
+    }
+
+    if (bounds.isEmpty() || bounds.getWidth() > mMaxTextureSize ||
+            bounds.getHeight() > mMaxTextureSize) {
+        return false;
+    }
+
+    glActiveTexture(GL_TEXTURE0);
+
+    LayerSize size(bounds.getWidth(), bounds.getHeight());
+    Layer* layer = mCaches.layerCache.get(size);
+    if (!layer) {
+        return false;
+    }
+
+    layer->mode = mode;
+    layer->alpha = alpha;
+    layer->layer.set(bounds);
+
+    // Save the layer in the snapshot
+    snapshot->flags |= Snapshot::kFlagIsLayer;
+    snapshot->layer = layer;
+
+    if (fboLayer) {
+        layer->fbo = mCaches.fboCache.get();
+
+        snapshot->flags |= Snapshot::kFlagIsFboLayer;
+        snapshot->fbo = layer->fbo;
+        snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
+        snapshot->resetClip(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+        snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+        snapshot->height = bounds.getHeight();
+        snapshot->flags |= Snapshot::kFlagDirtyOrtho;
+        snapshot->orthoMatrix.load(mOrthoMatrix);
+
+        setScissorFromClip();
+
+        // Bind texture to FBO
+        glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+        // Initialize the texture if needed
+        if (layer->empty) {
+            layer->empty = false;
+            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0,
+                    GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+        }
+
+        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);
+            glDeleteTextures(1, &layer->texture);
+            mCaches.fboCache.put(layer->fbo);
+
+            delete layer;
+
+            return false;
+        }
+
+        // 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);
+
+        // Change the ortho projection
+        glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
+        mOrthoMatrix.loadOrtho(0.0f, bounds.getWidth(), bounds.getHeight(), 0.0f, -1.0f, 1.0f);
+    } else {
+        // Copy the framebuffer into the layer
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+        // TODO: Workaround for b/3054204
+        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+                bounds.getWidth(), bounds.getHeight(), 0);
+
+        // TODO: Waiting for b/3054204 to be fixed
+        // if (layer->empty) {
+        //     glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+        //             bounds.getWidth(), bounds.getHeight(), 0);
+        //     layer->empty = false;
+        // } else {
+        //     glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
+        //             bounds.getWidth(), bounds.getHeight());
+        //  }
+
+        // Enqueue the buffer coordinates to clear the corresponding region later
+        mLayers.push(new Rect(bounds));
+    }
+
+    return true;
+}
+
+/**
+ * Read the documentation of createLayer() before doing anything in this method.
+ */
+void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
+    if (!current->layer) {
+        LOGE("Attempting to compose a layer that does not exist");
+        return;
+    }
+
+    const bool fboLayer = current->flags & SkCanvas::kClipToLayer_SaveFlag;
+
+    if (fboLayer) {
+        // Unbind current FBO and restore previous one
+        glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+    }
+
+    // Restore the clip from the previous snapshot
+    const Rect& clip = *previous->clipRect;
+    glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
+
+    Layer* layer = current->layer;
+    const Rect& rect = layer->layer;
+
+    if (!fboLayer && layer->alpha < 255) {
+        drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
+                layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
+    }
+
+    // Layers are already drawn with a top-left origin, don't flip the texture
+    resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
+
+    if (fboLayer) {
+        drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
+                layer->texture, layer->alpha / 255.0f, layer->mode, layer->blend);
+    } else {
+        drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
+                1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
+                &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+    }
+
+    resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+
+    if (fboLayer) {
+        // Detach the texture from the FBO
+        glBindFramebuffer(GL_FRAMEBUFFER, current->fbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+        glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+
+        // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed
+        mCaches.fboCache.put(current->fbo);
+    }
+
+    LayerSize size(rect.getWidth(), rect.getHeight());
+    // Failing to add the layer to the cache should happen only if the layer is too large
+    if (!mCaches.layerCache.put(size, layer)) {
+        LAYER_LOGD("Deleting layer");
+        glDeleteTextures(1, &layer->texture);
+        delete layer;
+    }
+}
+
+void OpenGLRenderer::clearLayerRegions() {
+    if (mLayers.size() == 0) return;
+
+    for (uint32_t i = 0; i < mLayers.size(); i++) {
+        Rect* bounds = mLayers.itemAt(i);
+
+        // Clear the framebuffer where the layer will draw
+        glScissor(bounds->left, mHeight - bounds->bottom,
+                bounds->getWidth(), bounds->getHeight());
+        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+        glClear(GL_COLOR_BUFFER_BIT);
+
+        delete bounds;
+    }
+    mLayers.clear();
+
+    // Restore the clip
+    setScissorFromClip();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// 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;
+    }
+
+    glActiveTexture(GL_TEXTURE0);
+    const Texture* texture = mCaches.textureCache.get(bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    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;
+    }
+
+    glActiveTexture(GL_TEXTURE0);
+    const Texture* texture = mCaches.textureCache.get(bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    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;
+    }
+
+    glActiveTexture(GL_TEXTURE0);
+    const Texture* texture = mCaches.textureCache.get(bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    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, const int32_t* xDivs, const int32_t* yDivs,
+        uint32_t width, uint32_t height, float left, float top, float right, float bottom,
+        const SkPaint* paint) {
+    if (quickReject(left, top, right, bottom)) {
+        return;
+    }
+
+    glActiveTexture(GL_TEXTURE0);
+    const Texture* texture = mCaches.textureCache.get(bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    Patch* mesh = mCaches.patchCache.get(width, height);
+    mesh->updateVertices(bitmap->width(), bitmap->height(),left, top, right, bottom,
+            xDivs, yDivs, width, height);
+
+    // 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], GL_TRIANGLES, mesh->verticesCount);
+}
+
+void OpenGLRenderer::drawLines(float* points, int count, const SkPaint* paint) {
+    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;
+
+    const bool isAA = paint->isAntiAlias();
+    if (isAA) {
+        GLuint textureUnit = 0;
+        setupTextureAlpha8(mCaches.line.getTexture(), 0, 0, textureUnit, 0.0f, 0.0f, r, g, b, a,
+                mode, false, true, mCaches.line.getVertices(), mCaches.line.getTexCoords());
+    } else {
+        setupColorRect(0.0f, 0.0f, 1.0f, 1.0f, r, g, b, a, mode, false);
+    }
+
+    const float strokeWidth = paint->getStrokeWidth();
+    const GLsizei elementsCount = isAA ? mCaches.line.getElementsCount() : gMeshCount;
+    const GLenum drawMode = isAA ? GL_TRIANGLES : GL_TRIANGLE_STRIP;
+
+    for (int i = 0; i < count; i += 4) {
+        float tx = 0.0f;
+        float ty = 0.0f;
+
+        if (isAA) {
+            mCaches.line.update(points[i], points[i + 1], points[i + 2], points[i + 3],
+                    strokeWidth, tx, ty);
+        } else {
+            ty = strokeWidth <= 1.0f ? 0.0f : -strokeWidth * 0.5f;
+        }
+
+        const float dx = points[i + 2] - points[i];
+        const float dy = points[i + 3] - points[i + 1];
+        const float mag = sqrtf(dx * dx + dy * dy);
+        const float angle = acos(dx / mag);
+
+        mModelView.loadTranslate(points[i], points[i + 1], 0.0f);
+        if (angle > MIN_ANGLE || angle < -MIN_ANGLE) {
+            mModelView.rotate(angle * RAD_TO_DEG, 0.0f, 0.0f, 1.0f);
+        }
+        mModelView.translate(tx, ty, 0.0f);
+        if (!isAA) {
+            float length = mCaches.line.getLength(points[i], points[i + 1],
+                    points[i + 2], points[i + 3]);
+            mModelView.scale(length, strokeWidth, 1.0f);
+        }
+        mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+
+        if (mShader) {
+            mShader->updateTransforms(mCaches.currentProgram, mModelView, *mSnapshot);
+        }
+
+        glDrawArrays(drawMode, 0, elementsCount);
+    }
+
+    if (isAA) {
+        glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
+    }
+}
+
+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;
+    if (!mExtensions.hasFramebufferFetch()) {
+        const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
+        if (!isMode) {
+            // Assume SRC_OVER
+            mode = SkXfermode::kSrcOver_Mode;
+        }
+    } else {
+        mode = getXfermode(p->getXfermode());
+    }
+
+    // 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;
+    }
+
+    paint->setAntiAlias(true);
+
+    float length = -1.0f;
+    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;
+
+    FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(paint);
+    fontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()),
+            paint->getTextSize());
+
+    if (mHasShadow) {
+        glActiveTexture(gTextureUnits[0]);
+        mCaches.dropShadowCache.setFontRenderer(fontRenderer);
+        const ShadowTexture* shadow = mCaches.dropShadowCache.get(paint, text, bytesCount,
+                count, mShadowRadius);
+        const AutoTexture autoCleanup(shadow);
+
+        setupShadow(shadow, x, y, mode, a);
+
+        // Draw the mesh
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+        glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
+    }
+
+    GLuint textureUnit = 0;
+    glActiveTexture(gTextureUnits[textureUnit]);
+
+    // Assume that the modelView matrix does not force scales, rotates, etc.
+    const bool linearFilter = mSnapshot->transform->changesBounds();
+    setupTextureAlpha8(fontRenderer.getTexture(linearFilter), 0, 0, textureUnit,
+            x, y, r, g, b, a, mode, false, true);
+
+    const Rect& clip = mSnapshot->getLocalClip();
+    clearLayerRegions();
+    fontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y);
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+    glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
+
+    drawTextDecorations(text, bytesCount, length, x, y, paint);
+}
+
+void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
+    GLuint textureUnit = 0;
+    glActiveTexture(gTextureUnits[textureUnit]);
+
+    const PathTexture* texture = mCaches.pathCache.get(path, paint);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    const float x = texture->left - texture->offset;
+    const float y = texture->top - texture->offset;
+
+    if (quickReject(x, y, x + texture->width, y + texture->height)) {
+        return;
+    }
+
+    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;
+
+    setupTextureAlpha8(texture, textureUnit, x, y, r, g, b, a, mode, true, true);
+
+    clearLayerRegions();
+
+    // Draw the mesh
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+    glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Shaders
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::resetShader() {
+    mShader = NULL;
+}
+
+void OpenGLRenderer::setupShader(SkiaShader* shader) {
+    mShader = shader;
+    if (mShader) {
+        mShader->set(&mCaches.textureCache, &mCaches.gradientCache);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Color filters
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::resetColorFilter() {
+    mColorFilter = NULL;
+}
+
+void OpenGLRenderer::setupColorFilter(SkiaColorFilter* filter) {
+    mColorFilter = filter;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Drop shadow
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::resetShadow() {
+    mHasShadow = false;
+}
+
+void OpenGLRenderer::setupShadow(float radius, float dx, float dy, int color) {
+    mHasShadow = true;
+    mShadowRadius = radius;
+    mShadowDx = dx;
+    mShadowDy = dy;
+    mShadowColor = color;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Drawing implementation
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::setupShadow(const ShadowTexture* texture, float x, float y,
+        SkXfermode::Mode mode, float alpha) {
+    const float sx = x - texture->left + mShadowDx;
+    const float sy = y - texture->top + mShadowDy;
+
+    const int shadowAlpha = ((mShadowColor >> 24) & 0xFF);
+    const GLfloat a = shadowAlpha < 255 ? shadowAlpha / 255.0f : alpha;
+    const GLfloat r = a * ((mShadowColor >> 16) & 0xFF) / 255.0f;
+    const GLfloat g = a * ((mShadowColor >>  8) & 0xFF) / 255.0f;
+    const GLfloat b = a * ((mShadowColor      ) & 0xFF) / 255.0f;
+
+    GLuint textureUnit = 0;
+    setupTextureAlpha8(texture, textureUnit, sx, sy, r, g, b, a, mode, true, false);
+}
+
+void OpenGLRenderer::setupTextureAlpha8(const Texture* texture, GLuint& textureUnit,
+        float x, float y, float r, float g, float b, float a, SkXfermode::Mode mode,
+        bool transforms, bool applyFilters) {
+    setupTextureAlpha8(texture->id, texture->width, texture->height, textureUnit,
+            x, y, r, g, b, a, mode, transforms, applyFilters,
+            &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0]);
+}
+
+void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t height,
+        GLuint& textureUnit, float x, float y, float r, float g, float b, float a,
+        SkXfermode::Mode mode, bool transforms, bool applyFilters) {
+    setupTextureAlpha8(texture, width, height, textureUnit,
+            x, y, r, g, b, a, mode, transforms, applyFilters,
+            &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0]);
+}
+
+void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t height,
+        GLuint& textureUnit, float x, float y, float r, float g, float b, float a,
+        SkXfermode::Mode mode, bool transforms, bool applyFilters,
+        GLvoid* vertices, GLvoid* texCoords) {
+     // Describe the required shaders
+     ProgramDescription description;
+     description.hasTexture = true;
+     description.hasAlpha8Texture = true;
+
+     if (applyFilters) {
+         if (mShader) {
+             mShader->describe(description, mExtensions);
+         }
+         if (mColorFilter) {
+             mColorFilter->describe(description, mExtensions);
+         }
+     }
+
+     // Setup the blending mode
+     chooseBlending(true, mode, description);
+
+     // Build and use the appropriate shader
+     useProgram(mCaches.programCache.get(description));
+
+     bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit);
+     glUniform1i(mCaches.currentProgram->getUniform("sampler"), textureUnit);
+
+     int texCoordsSlot = mCaches.currentProgram->getAttrib("texCoords");
+     glEnableVertexAttribArray(texCoordsSlot);
+
+     // Setup attributes
+     glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
+             gMeshStride, vertices);
+     glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE,
+             gMeshStride, texCoords);
+
+     // Setup uniforms
+     if (transforms) {
+         mModelView.loadTranslate(x, y, 0.0f);
+         mModelView.scale(width, height, 1.0f);
+     } else {
+         mModelView.loadIdentity();
+     }
+     mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+     glUniform4f(mCaches.currentProgram->color, r, g, b, a);
+
+     textureUnit++;
+     if (applyFilters) {
+         // Setup attributes and uniforms required by the shaders
+         if (mShader) {
+             mShader->setupProgram(mCaches.currentProgram, mModelView, *mSnapshot, &textureUnit);
+         }
+         if (mColorFilter) {
+             mColorFilter->setupProgram(mCaches.currentProgram);
+         }
+     }
+}
+
+// Same values used by Skia
+#define kStdStrikeThru_Offset   (-6.0f / 21.0f)
+#define kStdUnderline_Offset    (1.0f / 9.0f)
+#define kStdUnderline_Thickness (1.0f / 18.0f)
+
+void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float length,
+        float x, float y, SkPaint* paint) {
+    // Handle underline and strike-through
+    uint32_t flags = paint->getFlags();
+    if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+        float underlineWidth = length;
+        // If length is > 0.0f, we already measured the text for the text alignment
+        if (length <= 0.0f) {
+            underlineWidth = paint->measureText(text, bytesCount);
+        }
+
+        float offsetX = 0;
+        switch (paint->getTextAlign()) {
+            case SkPaint::kCenter_Align:
+                offsetX = underlineWidth * 0.5f;
+                break;
+            case SkPaint::kRight_Align:
+                offsetX = underlineWidth;
+                break;
+            default:
+                break;
+        }
+
+        if (underlineWidth > 0.0f) {
+            const float textSize = paint->getTextSize();
+            const float strokeWidth = textSize * kStdUnderline_Thickness;
+
+            const float left = x - offsetX;
+            float top = 0.0f;
+
+            const int pointsCount = 4 * (flags & SkPaint::kStrikeThruText_Flag ? 2 : 1);
+            float points[pointsCount];
+            int currentPoint = 0;
+
+            if (flags & SkPaint::kUnderlineText_Flag) {
+                top = y + textSize * kStdUnderline_Offset;
+                points[currentPoint++] = left;
+                points[currentPoint++] = top;
+                points[currentPoint++] = left + underlineWidth;
+                points[currentPoint++] = top;
+            }
+
+            if (flags & SkPaint::kStrikeThruText_Flag) {
+                top = y + textSize * kStdStrikeThru_Offset;
+                points[currentPoint++] = left;
+                points[currentPoint++] = top;
+                points[currentPoint++] = left + underlineWidth;
+                points[currentPoint++] = top;
+            }
+
+            SkPaint linesPaint(*paint);
+            linesPaint.setStrokeWidth(strokeWidth);
+
+            drawLines(&points[0], pointsCount, &linesPaint);
+        }
+    }
+}
+
+void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom,
+        int color, SkXfermode::Mode mode, bool ignoreTransform) {
+    clearLayerRegions();
+
+    // 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;
+
+    setupColorRect(left, top, right, bottom, r, g, b, a, mode, ignoreTransform);
+
+    // Draw the mesh
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+}
+
+void OpenGLRenderer::setupColorRect(float left, float top, float right, float bottom,
+        float r, float g, float b, float a, SkXfermode::Mode mode, bool ignoreTransform) {
+    GLuint textureUnit = 0;
+
+    // Describe the required shaders
+    ProgramDescription description;
+    if (mShader) {
+        mShader->describe(description, mExtensions);
+    }
+    if (mColorFilter) {
+        mColorFilter->describe(description, mExtensions);
+    }
+
+    // Setup the blending mode
+    chooseBlending(a < 1.0f || (mShader && mShader->blend()), mode, description);
+
+    // Build and use the appropriate shader
+    useProgram(mCaches.programCache.get(description));
+
+    // Setup attributes
+    glVertexAttribPointer(mCaches.currentProgram->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) {
+        mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+    } else {
+        mat4 identity;
+        mCaches.currentProgram->set(mOrthoMatrix, mModelView, identity);
+    }
+    glUniform4f(mCaches.currentProgram->color, r, g, b, a);
+
+    // Setup attributes and uniforms required by the shaders
+    if (mShader) {
+        mShader->setupProgram(mCaches.currentProgram, mModelView, *mSnapshot, &textureUnit);
+    }
+    if (mColorFilter) {
+        mColorFilter->setupProgram(mCaches.currentProgram);
+    }
+}
+
+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],
+            GL_TRIANGLE_STRIP, gMeshCount);
+}
+
+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],
+            GL_TRIANGLE_STRIP, gMeshCount);
+}
+
+void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom,
+        GLuint texture, float alpha, SkXfermode::Mode mode, bool blend,
+        GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
+        bool swapSrcDst, bool ignoreTransform) {
+    clearLayerRegions();
+
+    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);
+
+    chooseBlending(blend || alpha < 1.0f, mode, description, swapSrcDst);
+
+    useProgram(mCaches.programCache.get(description));
+    if (!ignoreTransform) {
+        mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+    } else {
+        mat4 m;
+        mCaches.currentProgram->set(mOrthoMatrix, mModelView, m);
+    }
+
+    // Texture
+    bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0);
+    glUniform1i(mCaches.currentProgram->getUniform("sampler"), 0);
+
+    // Always premultiplied
+    glUniform4f(mCaches.currentProgram->color, alpha, alpha, alpha, alpha);
+
+    // Mesh
+    int texCoordsSlot = mCaches.currentProgram->getAttrib("texCoords");
+    glEnableVertexAttribArray(texCoordsSlot);
+    glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
+            gMeshStride, vertices);
+    glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE, gMeshStride, texCoords);
+
+    // Color filter
+    if (mColorFilter) {
+        mColorFilter->setupProgram(mCaches.currentProgram);
+    }
+
+    glDrawArrays(drawMode, 0, elementsCount);
+    glDisableVertexAttribArray(texCoordsSlot);
+}
+
+void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
+        ProgramDescription& description, bool swapSrcDst) {
+    blend = blend || mode != SkXfermode::kSrcOver_Mode;
+    if (blend) {
+        if (mode < SkXfermode::kPlus_Mode) {
+            if (!mCaches.blend) {
+                glEnable(GL_BLEND);
+            }
+
+            GLenum sourceMode = swapSrcDst ? gBlendsSwap[mode].src : gBlends[mode].src;
+            GLenum destMode = swapSrcDst ? gBlendsSwap[mode].dst : gBlends[mode].dst;
+
+            if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) {
+                glBlendFunc(sourceMode, destMode);
+                mCaches.lastSrcMode = sourceMode;
+                mCaches.lastDstMode = destMode;
+            }
+        } else {
+            // These blend modes are not supported by OpenGL directly and have
+            // to be implemented using shaders. Since the shader will perform
+            // the blending, turn blending off here
+            if (mExtensions.hasFramebufferFetch()) {
+                description.framebufferMode = mode;
+                description.swapSrcDst = swapSrcDst;
+            }
+
+            if (mCaches.blend) {
+                glDisable(GL_BLEND);
+            }
+            blend = false;
+        }
+    } else if (mCaches.blend) {
+        glDisable(GL_BLEND);
+    }
+    mCaches.blend = blend;
+}
+
+bool OpenGLRenderer::useProgram(Program* program) {
+    if (!program->isInUse()) {
+        if (mCaches.currentProgram != NULL) mCaches.currentProgram->remove();
+        program->use();
+        mCaches.currentProgram = 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) {
+        if (!mExtensions.hasFramebufferFetch()) {
+            const bool isMode = SkXfermode::IsMode(paint->getXfermode(), mode);
+            if (!isMode) {
+                // Assume SRC_OVER
+                *mode = SkXfermode::kSrcOver_Mode;
+            }
+        } else {
+            *mode = getXfermode(paint->getXfermode());
+        }
+
+        // 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;
+    }
+}
+
+SkXfermode::Mode OpenGLRenderer::getXfermode(SkXfermode* mode) {
+    if (mode == NULL) {
+        return SkXfermode::kSrcOver_Mode;
+    }
+    return mode->fMode;
+}
+
+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..e3d4653
--- /dev/null
+++ b/libs/hwui/OpenGLRenderer.h
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/Vector.h>
+
+#include "Extensions.h"
+#include "Matrix.h"
+#include "Program.h"
+#include "Rect.h"
+#include "Snapshot.h"
+#include "Vertex.h"
+#include "SkiaShader.h"
+#include "SkiaColorFilter.h"
+#include "Caches.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_OPENGL 1
+
+///////////////////////////////////////////////////////////////////////////////
+// Renderer
+///////////////////////////////////////////////////////////////////////////////
+
+class DisplayListRenderer;
+
+/**
+ * OpenGL renderer used to draw accelerated 2D graphics. The API is a
+ * simplified version of Skia's Canvas API.
+ */
+class OpenGLRenderer {
+public:
+    OpenGLRenderer();
+    virtual ~OpenGLRenderer();
+
+    virtual void setViewport(int width, int height);
+
+    virtual void prepare();
+    virtual void finish();
+
+    virtual void acquireContext();
+    virtual void releaseContext();
+
+    int getSaveCount() const;
+    virtual int save(int flags);
+    virtual void restore();
+    virtual void restoreToCount(int saveCount);
+
+    virtual int saveLayer(float left, float top, float right, float bottom,
+            const SkPaint* p, int flags);
+    virtual int saveLayerAlpha(float left, float top, float right, float bottom,
+            int alpha, int flags);
+
+    virtual void translate(float dx, float dy);
+    virtual void rotate(float degrees);
+    virtual void scale(float sx, float sy);
+
+    void getMatrix(SkMatrix* matrix);
+    virtual void setMatrix(SkMatrix* matrix);
+    virtual void concatMatrix(SkMatrix* matrix);
+
+    const Rect& getClipBounds();
+    bool quickReject(float left, float top, float right, float bottom);
+    virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
+
+    virtual void drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint);
+    virtual void drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint);
+    virtual void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
+            float srcRight, float srcBottom, float dstLeft, float dstTop,
+            float dstRight, float dstBottom, const SkPaint* paint);
+    virtual void drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
+            uint32_t width, uint32_t height, float left, float top, float right, float bottom,
+            const SkPaint* paint);
+    virtual void drawColor(int color, SkXfermode::Mode mode);
+    virtual void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
+    virtual void drawPath(SkPath* path, SkPaint* paint);
+    virtual void drawLines(float* points, int count, const SkPaint* paint);
+    virtual void drawText(const char* text, int bytesCount, int count, float x, float y,
+            SkPaint* paint);
+
+    virtual void resetShader();
+    virtual void setupShader(SkiaShader* shader);
+
+    virtual void resetColorFilter();
+    virtual void setupColorFilter(SkiaColorFilter* filter);
+
+    virtual void resetShadow();
+    virtual void setupShadow(float radius, float dx, float dy, int color);
+
+protected:
+    /**
+     * 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
+     */
+    virtual void composeLayer(sp<Snapshot> current, sp<Snapshot> previous);
+
+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.
+     *
+     * @param flags The save flags; see SkCanvas for more information
+     *
+     * @return The new save count. This value can be passed to #restoreToCount()
+     */
+    int saveSnapshot(int flags);
+
+    /**
+     * Restores the current snapshot; mSnapshot becomes mSnapshot->previous.
+     *
+     * @return True if the clip was modified.
+     */
+    bool restoreSnapshot();
+
+    /**
+     * Sets the clipping rectangle using glScissor. The clip is defined by
+     * the current snapshot's clipRect member.
+     */
+    void setScissorFromClip();
+
+    /**
+     * 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
+     * @param previousFbo The name of the current framebuffer
+     *
+     * @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, GLuint previousFbo);
+
+    /**
+     * Clears all the regions corresponding to the current list of layers.
+     * This method MUST be invoked before any drawing operation.
+     */
+    void clearLayerRegions();
+
+    /**
+     * 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
+     * @paran ignoreBlending True if the blending is set by the caller
+     */
+    void drawColorRect(float left, float top, float right, float bottom,
+            int color, SkXfermode::Mode mode, bool ignoreTransform = false);
+
+    /**
+     * Setups shaders to draw a colored rect.
+     */
+    void setupColorRect(float left, float top, float right, float bottom,
+            float r, float g, float b, float a, SkXfermode::Mode mode, bool ignoreTransform);
+
+    /**
+     * 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
+     * @param swapSrcDst Whether or not the src and dst blending operations should be swapped
+     * @param ignoreTransform True if the current transform should be ignored
+     */
+    void drawTextureMesh(float left, float top, float right, float bottom, GLuint texture,
+            float alpha, SkXfermode::Mode mode, bool blend,
+            GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
+            bool swapSrcDst = false, bool ignoreTransform = false);
+
+    /**
+     * Prepares the renderer to draw the specified shadow.
+     *
+     * @param texture The shadow texture
+     * @param x The x coordinate of the shadow
+     * @param y The y coordinate of the shadow
+     * @param mode The blending mode
+     * @param alpha The alpha value
+     */
+    void setupShadow(const ShadowTexture* texture, float x, float y, SkXfermode::Mode mode,
+            float alpha);
+
+    /**
+     * Prepares the renderer to draw the specified Alpha8 texture as a rectangle.
+     *
+     * @param texture The texture to render with
+     * @param textureUnit The texture unit to use, may be modified
+     * @param x The x coordinate of the rectangle to draw
+     * @param y The y coordinate of the rectangle to draw
+     * @param r The red component of the color
+     * @param g The green component of the color
+     * @param b The blue component of the color
+     * @param a The alpha component of the color
+     * @param mode The blending mode
+     * @param transforms True if the matrix passed to the shader should be multiplied
+     *        by the model-view matrix
+     * @param applyFilters Whether or not to take color filters and
+     *        shaders into account
+     */
+    void setupTextureAlpha8(const Texture* texture, GLuint& textureUnit, float x, float y,
+            float r, float g, float b, float a, SkXfermode::Mode mode, bool transforms,
+            bool applyFilters);
+
+    /**
+     * Prepares the renderer to draw the specified Alpha8 texture as a rectangle.
+     *
+     * @param texture The texture to render with
+     * @param width The width of the texture
+     * @param height The height of the texture
+     * @param textureUnit The texture unit to use, may be modified
+     * @param x The x coordinate of the rectangle to draw
+     * @param y The y coordinate of the rectangle to draw
+     * @param r The red component of the color
+     * @param g The green component of the color
+     * @param b The blue component of the color
+     * @param a The alpha component of the color
+     * @param mode The blending mode
+     * @param transforms True if the matrix passed to the shader should be multiplied
+     *        by the model-view matrix
+     * @param applyFilters Whether or not to take color filters and
+     *        shaders into account
+     */
+    void setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t height,
+            GLuint& textureUnit, float x, float y, float r, float g, float b, float a,
+            SkXfermode::Mode mode, bool transforms, bool applyFilters);
+
+    /**
+     * Same as above setupTextureAlpha8() but specifies the mesh's vertices
+     * and texCoords pointers.
+     */
+    void setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t height,
+            GLuint& textureUnit, float x, float y, float r, float g, float b, float a,
+            SkXfermode::Mode mode, bool transforms, bool applyFilters,
+            GLvoid* vertices, GLvoid* texCoords);
+
+    /**
+     * Draws text underline and strike-through if needed.
+     *
+     * @param text The text to decor
+     * @param bytesCount The number of bytes in the text
+     * @param length The length in pixels of the text, can be <= 0.0f to force a measurement
+     * @param x The x coordinate where the text will be drawn
+     * @param y The y coordinate where the text will be drawn
+     * @param paint The paint to draw the text with
+     */
+    void drawTextDecorations(const char* text, int bytesCount, float length,
+            float x, float y, SkPaint* paint);
+
+    /**
+     * 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, ProgramDescription& description,
+            bool swapSrcDst = false);
+
+    /**
+     * Safely retrieves the mode from the specified xfermode. If the specified
+     * xfermode is null, the mode is assumed to be SkXfermode::kSrcOver_Mode.
+     */
+    inline SkXfermode::Mode getXfermode(SkXfermode* mode);
+
+    /**
+     * 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
+    SkiaShader* mShader;
+
+    // Color filters
+    SkiaColorFilter* mColorFilter;
+
+    // Used to draw textured quads
+    TextureVertex mMeshVertices[4];
+
+    // GL extensions
+    Extensions mExtensions;
+
+    // Drop shadow
+    bool mHasShadow;
+    float mShadowRadius;
+    float mShadowDx;
+    float mShadowDy;
+    int mShadowColor;
+
+    // Various caches
+    Caches& mCaches;
+
+    // List of rectangles to clear due to calls to saveLayer()
+    Vector<Rect*> mLayers;
+
+    // Misc
+    GLint mMaxTextureSize;
+
+    friend class DisplayListRenderer;
+
+}; // 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..ca0a0b1
--- /dev/null
+++ b/libs/hwui/Patch.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 "OpenGLRenderer"
+
+#include <cmath>
+
+#include "Patch.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+Patch::Patch(const uint32_t xCount, const uint32_t yCount) {
+    // 2 triangles per patch, 3 vertices per triangle
+    verticesCount = (xCount + 1) * (yCount + 1) * 2 * 3;
+    vertices = new TextureVertex[verticesCount];
+}
+
+Patch::~Patch() {
+    delete[] vertices;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Vertices management
+///////////////////////////////////////////////////////////////////////////////
+
+void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight,
+        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 stretchX = 0.0f;
+    float stretchY = 0.0;
+
+    const float meshWidth = right - left;
+
+    if (xStretchCount > 0) {
+        uint32_t stretchSize = 0;
+        for (uint32_t i = 1; i < width; i += 2) {
+            stretchSize += xDivs[i] - xDivs[i - 1];
+        }
+        const float xStretchTex = stretchSize;
+        const float fixed = bitmapWidth - stretchSize;
+        const float xStretch = right - left - fixed;
+        stretchX = xStretch / xStretchTex;
+    }
+
+    if (yStretchCount > 0) {
+        uint32_t stretchSize = 0;
+        for (uint32_t i = 1; i < height; i += 2) {
+            stretchSize += yDivs[i] - yDivs[i - 1];
+        }
+        const float yStretchTex = stretchSize;
+        const float fixed = bitmapHeight - stretchSize;
+        const float yStretch = bottom - top - fixed;
+        stretchY = yStretch / yStretchTex;
+    }
+
+    TextureVertex* vertex = vertices;
+
+    float previousStepY = 0.0f;
+
+    float y1 = 0.0f;
+    float v1 = 0.0f;
+
+    for (uint32_t i = 0; i < height; i++) {
+        float stepY = yDivs[i];
+
+        float y2 = 0.0f;
+        if (i & 1) {
+            const float segment = stepY - previousStepY;
+            y2 = y1 + segment * stretchY;
+        } else {
+            y2 = y1 + stepY - previousStepY;
+        }
+        float v2 = fmax(0.0f, stepY - 0.5f) / bitmapHeight;
+
+        generateRow(vertex, y1, y2, v1, v2, xDivs, width, stretchX,
+                right - left, bitmapWidth);
+
+        y1 = y2;
+        v1 = (stepY + 0.5f) / bitmapHeight;
+
+        previousStepY = stepY;
+    }
+
+    generateRow(vertex, y1, bottom - top, v1, 1.0f, xDivs, width, stretchX,
+            right - left, bitmapWidth);
+}
+
+inline void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, float v2,
+        const int32_t xDivs[], uint32_t xCount, float stretchX, float width, float bitmapWidth) {
+    float previousStepX = 0.0f;
+
+    float x1 = 0.0f;
+    float u1 = 0.0f;
+
+    // Generate the row quad by quad
+    for (uint32_t i = 0; i < xCount; i++) {
+        float stepX = xDivs[i];
+
+        float x2 = 0.0f;
+        if (i & 1) {
+            const float segment = stepX - previousStepX;
+            x2 = x1 + segment * stretchX;
+        } else {
+            x2 = x1 + stepX - previousStepX;
+        }
+        float u2 = fmax(0.0f, stepX - 0.5f) / bitmapWidth;
+
+        generateQuad(vertex, x1, y1, x2, y2, u1, v1, u2, v2);
+
+        x1 = x2;
+        u1 = (stepX + 0.5f) / bitmapWidth;
+
+        previousStepX = stepX;
+    }
+
+    generateQuad(vertex, x1, y1, width, y2, u1, v1, 1.0f, v2);
+}
+
+inline void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, float y2,
+            float u1, float v1, float u2, float v2) {
+    // Left triangle
+    TextureVertex::set(vertex++, x1, y1, u1, v1);
+    TextureVertex::set(vertex++, x2, y1, u2, v1);
+    TextureVertex::set(vertex++, x1, y2, u1, v2);
+
+    // Right triangle
+    TextureVertex::set(vertex++, x1, y2, u1, v2);
+    TextureVertex::set(vertex++, x2, y1, u2, v1);
+    TextureVertex::set(vertex++, x2, y2, u2, v2);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
new file mode 100644
index 0000000..1d08c64
--- /dev/null
+++ b/libs/hwui/Patch.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 ANDROID_UI_PATCH_H
+#define ANDROID_UI_PATCH_H
+
+#include <sys/types.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 float bitmapWidth, const float bitmapHeight,
+            float left, float top, float right, float bottom,
+            const int32_t* xDivs, const int32_t* yDivs,
+            const uint32_t width, const uint32_t height);
+
+    TextureVertex* vertices;
+    uint32_t verticesCount;
+
+private:
+    static inline void generateRow(TextureVertex*& vertex, float y1, float y2,
+            float v1, float v2, const int32_t xDivs[], uint32_t xCount,
+            float stretchX, float width, float bitmapWidth);
+    static inline void generateQuad(TextureVertex*& vertex,
+            float x1, float y1, float x2, float y2,
+            float u1, float v1, float u2, float v2);
+}; // 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..f2cf548
--- /dev/null
+++ b/libs/hwui/PatchCache.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+PatchCache::PatchCache(): mCache(DEFAULT_PATCH_CACHE_SIZE) {
+}
+
+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(uint32_t width, uint32_t height) {
+    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..b077469
--- /dev/null
+++ b/libs/hwui/PatchCache.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef 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();
+    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(uint32_t width, uint32_t height);
+    void clear();
+
+private:
+    GenerationCache<PatchDescription, Patch*> mCache;
+}; // class PatchCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PATCH_CACHE_H
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
new file mode 100644
index 0000000..377727b
--- /dev/null
+++ b/libs/hwui/PathCache.cpp
@@ -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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <GLES2/gl2.h>
+
+#include <SkCanvas.h>
+#include <SkRect.h>
+
+#include <utils/threads.h>
+
+#include "PathCache.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+PathCache::PathCache():
+        mCache(GenerationCache<PathCacheEntry, PathTexture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(MB(DEFAULT_PATH_CACHE_SIZE)) {
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_PATH_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting path cache size to %sMB", property);
+        setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default path cache size of %.2fMB", DEFAULT_PATH_CACHE_SIZE);
+    }
+    init();
+}
+
+PathCache::PathCache(uint32_t maxByteSize):
+        mCache(GenerationCache<PathCacheEntry, PathTexture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(maxByteSize) {
+    init();
+}
+
+PathCache::~PathCache() {
+    Mutex::Autolock _l(mLock);
+    mCache.clear();
+}
+
+void PathCache::init() {
+    mCache.setOnEntryRemovedListener(this);
+
+    GLint maxTextureSize;
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+    mMaxTextureSize = maxTextureSize;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t PathCache::getSize() {
+    Mutex::Autolock _l(mLock);
+    return mSize;
+}
+
+uint32_t PathCache::getMaxSize() {
+    Mutex::Autolock _l(mLock);
+    return mMaxSize;
+}
+
+void PathCache::setMaxSize(uint32_t maxSize) {
+    Mutex::Autolock _l(mLock);
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        mCache.removeOldest();
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::operator()(PathCacheEntry& path, PathTexture*& texture) {
+    const uint32_t size = texture->width * texture->height;
+    mSize -= size;
+
+    if (texture) {
+        glDeleteTextures(1, &texture->id);
+        delete texture;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::remove(SkPath* path) {
+    Mutex::Autolock _l(mLock);
+    // TODO: Linear search...
+    for (uint32_t i = 0; i < mCache.size(); i++) {
+        if (mCache.getKeyAt(i).path == path) {
+            mCache.removeAt(i);
+        }
+    }
+}
+
+PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
+    PathCacheEntry entry(path, paint);
+
+    mLock.lock();
+    PathTexture* texture = mCache.get(entry);
+    mLock.unlock();
+
+    if (!texture) {
+        texture = addTexture(entry, path, paint);
+    } else if (path->getGenerationID() != texture->generation) {
+        mLock.lock();
+        mCache.remove(entry);
+        mLock.unlock();
+        texture = addTexture(entry, path, paint);
+    }
+
+    return texture;
+}
+
+PathTexture* PathCache::addTexture(const PathCacheEntry& entry,
+        const SkPath *path, const SkPaint* paint) {
+    const SkRect& bounds = path->getBounds();
+
+    const float pathWidth = fmax(bounds.width(), 1.0f);
+    const float pathHeight = fmax(bounds.height(), 1.0f);
+
+    if (pathWidth > mMaxTextureSize || pathHeight > mMaxTextureSize) {
+        LOGW("Path too large to be rendered into a texture");
+        return NULL;
+    }
+
+    const float offset = entry.strokeWidth * 1.5f;
+    const uint32_t width = uint32_t(pathWidth + offset * 2.0 + 0.5);
+    const uint32_t height = uint32_t(pathHeight + offset * 2.0 + 0.5);
+
+    const uint32_t size = width * height;
+    // Don't even try to cache a bitmap that's bigger than the cache
+    if (size < mMaxSize) {
+        mLock.lock();
+        while (mSize + size > mMaxSize) {
+            mCache.removeOldest();
+        }
+        mLock.unlock();
+    }
+
+    PathTexture* texture = new PathTexture;
+    texture->left = bounds.fLeft;
+    texture->top = bounds.fTop;
+    texture->offset = offset;
+    texture->width = width;
+    texture->height = height;
+    texture->generation = path->getGenerationID();
+
+    SkBitmap bitmap;
+    bitmap.setConfig(SkBitmap::kA8_Config, width, height);
+    bitmap.allocPixels();
+    bitmap.eraseColor(0);
+
+    SkCanvas canvas(bitmap);
+    canvas.translate(-bounds.fLeft + offset, -bounds.fTop + offset);
+    canvas.drawPath(*path, *paint);
+
+    generateTexture(bitmap, texture);
+
+    if (size < mMaxSize) {
+        mLock.lock();
+        mSize += size;
+        mCache.put(entry, texture);
+        mLock.unlock();
+    } else {
+        texture->cleanup = true;
+    }
+
+    return texture;
+}
+
+void PathCache::clear() {
+    Mutex::Autolock _l(mLock);
+    mCache.clear();
+}
+
+void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) {
+    SkAutoLockPixels alp(bitmap);
+    if (!bitmap.readyToDraw()) {
+        LOGE("Cannot generate texture from bitmap");
+        return;
+    }
+
+    glGenTextures(1, &texture->id);
+
+    glBindTexture(GL_TEXTURE_2D, texture->id);
+    // Textures are Alpha8
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+    texture->blend = true;
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
+            GL_ALPHA, 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/PathCache.h b/libs/hwui/PathCache.h
new file mode 100644
index 0000000..bde0e7d
--- /dev/null
+++ b/libs/hwui/PathCache.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_PATH_CACHE_H
+#define ANDROID_UI_PATH_CACHE_H
+
+#include <SkBitmap.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+
+#include "Texture.h"
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Describe a path in the path cache.
+ */
+struct PathCacheEntry {
+    PathCacheEntry() {
+        path = NULL;
+        join = SkPaint::kDefault_Join;
+        cap = SkPaint::kDefault_Cap;
+        style = SkPaint::kFill_Style;
+        miter = 4.0f;
+        strokeWidth = 1.0f;
+    }
+
+    PathCacheEntry(const PathCacheEntry& entry):
+        path(entry.path), join(entry.join), cap(entry.cap),
+        style(entry.style), miter(entry.miter),
+        strokeWidth(entry.strokeWidth) {
+    }
+
+    PathCacheEntry(SkPath* path, SkPaint* paint) {
+        this->path = path;
+        join = paint->getStrokeJoin();
+        cap = paint->getStrokeCap();
+        miter = paint->getStrokeMiter();
+        strokeWidth = paint->getStrokeWidth();
+        style = paint->getStyle();
+    }
+
+    SkPath* path;
+    SkPaint::Join join;
+    SkPaint::Cap cap;
+    SkPaint::Style style;
+    float miter;
+    float strokeWidth;
+
+    bool operator<(const PathCacheEntry& rhs) const {
+        return memcmp(this, &rhs, sizeof(PathCacheEntry)) < 0;
+    }
+}; // struct PathCacheEntry
+
+/**
+ * Alpha texture used to represent a path.
+ */
+struct PathTexture: public Texture {
+    PathTexture(): Texture() {
+    }
+
+    /**
+     * Left coordinate of the path bounds.
+     */
+    float left;
+    /**
+     * Top coordinate of the path bounds.
+     */
+    float top;
+    /**
+     * Offset to draw the path at the correct origin.
+     */
+    float offset;
+}; // struct PathTexture
+
+/**
+ * A simple LRU path 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 PathCache: public OnEntryRemoved<PathCacheEntry, PathTexture*> {
+public:
+    PathCache();
+    PathCache(uint32_t maxByteSize);
+    ~PathCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(PathCacheEntry& path, PathTexture*& texture);
+
+    /**
+     * Returns the texture associated with the specified path. If the texture
+     * cannot be found in the cache, a new texture is generated.
+     */
+    PathTexture* get(SkPath* path, SkPaint* paint);
+    /**
+     * Clears the cache. This causes all textures to be deleted.
+     */
+    void clear();
+    /**
+     * Removes an entry.
+     */
+    void remove(SkPath* path);
+
+    /**
+     * 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.
+     */
+    void generateTexture(SkBitmap& bitmap, Texture* texture);
+
+    PathTexture* addTexture(const PathCacheEntry& entry, const SkPath *path, const SkPaint* paint);
+
+    void init();
+
+    GenerationCache<PathCacheEntry, PathTexture*> mCache;
+
+    uint32_t mSize;
+    uint32_t mMaxSize;
+    GLuint mMaxTextureSize;
+
+    /**
+     * Used to access mCache and mSize. All methods are accessed from a single
+     * thread except for remove().
+     */
+    mutable Mutex mLock;
+}; // class PathCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PATH_CACHE_H
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
new file mode 100644
index 0000000..2e1b9a0
--- /dev/null
+++ b/libs/hwui/Program.cpp
@@ -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.
+ */
+
+#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) {
+            GLchar log[infoLen];
+            glGetProgramInfoLog(id, infoLen, 0, &log[0]);
+            LOGE("Error while linking shaders: %s", log);
+        }
+        glDeleteShader(vertexShader);
+        glDeleteShader(fragmentShader);
+        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..d1a1b45
--- /dev/null
+++ b/libs/hwui/ProgramCache.cpp
@@ -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.
+ */
+
+#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[3] = {
+        // Linear
+        "uniform mat4 screenSpace;\n",
+        // Circular
+        "uniform vec2 gradientStart;\n"
+        "uniform mat4 gradientMatrix;\n"
+        "uniform mat4 screenSpace;\n",
+        // Sweep
+        "uniform vec2 gradientStart;\n"
+        "uniform mat4 gradientMatrix;\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[3] = {
+        // Linear
+        "varying float index;\n",
+        // Circular
+        "varying vec2 circular;\n",
+        // Sweep
+        "varying vec2 sweep;\n"
+};
+const char* gVS_Main =
+        "\nvoid main(void) {\n";
+const char* gVS_Main_OutTexCoords =
+        "    outTexCoords = texCoords;\n";
+const char* gVS_Main_OutGradient[3] = {
+        // Linear
+        "    index = (screenSpace * position).x;\n",
+        // Circular
+        "    vec4 location = screenSpace * position;\n"
+        "    circular = (gradientMatrix * vec4(location.xy - gradientStart, 0.0, 0.0)).xy;\n",
+        // Sweep
+        "    vec4 location = screenSpace * position;\n"
+        "    sweep = (gradientMatrix * vec4(location.xy - gradientStart, 0.0, 0.0)).xy;\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_Extension_FramebufferFetch =
+        "#extension GL_NV_shader_framebuffer_fetch : enable\n\n";
+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[3] = {
+        // Linear
+        "uniform sampler2D gradientSampler;\n",
+        // Circular
+        "uniform float gradientRadius;\n"
+        "uniform sampler2D gradientSampler;\n",
+        // Sweep
+        "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"
+        "    lowp 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[3] = {
+        // Linear
+        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index, 0.5));\n",
+        // Circular
+        "    float index = length(circular) * gradientRadius;\n"
+        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index, 0.5));\n",
+        // Sweep
+        "    float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n"
+        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(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_FragColor_Blend =
+        "    gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n";
+const char* gFS_Main_FragColor_Blend_Swap =
+        "    gl_FragColor = blendFramebuffer(gl_LastFragColor, 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* gBlendOps[18] = {
+        // 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, "
+                "src.a + dst.a - 2.0 * src.a * dst.a);\n",
+        // Add
+        "return min(src + dst, 1.0);\n",
+        // Multiply
+        "return src * dst;\n",
+        // Screen
+        "return src + dst - src * dst;\n",
+        // Overlay
+        "return clamp(vec4(mix("
+                "2.0 * src.rgb * dst.rgb + src.rgb * (1.0 - dst.a) + dst.rgb * (1.0 - src.a), "
+                "src.a * dst.a - 2.0 * (dst.a - dst.rgb) * (src.a - src.rgb) + src.rgb * (1.0 - dst.a) + dst.rgb * (1.0 - src.a), "
+                "step(dst.a, 2.0 * dst.rgb)), "
+                "src.a + dst.a - src.a * dst.a), 0.0, 1.0);\n",
+        // Darken
+        "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb + "
+                "min(src.rgb * dst.a, dst.rgb * src.a), src.a + dst.a - src.a * dst.a);\n",
+        // Lighten
+        "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb + "
+                "max(src.rgb * dst.a, dst.rgb * src.a), src.a + dst.a - 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) {
+        description.log("Could not find program");
+        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[description.gradientType]);
+    }
+    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[description.gradientType]);
+    }
+    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_OutGradient[description.gradientType]);
+        }
+        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;
+
+    bool blendFramebuffer = description.framebufferMode >= SkXfermode::kPlus_Mode;
+    if (blendFramebuffer) {
+        shader.append(gFS_Header_Extension_FramebufferFetch);
+    }
+
+    shader.append(gFS_Header);
+
+    // Varyings
+    if (description.hasTexture) {
+        shader.append(gVS_Header_Varyings_HasTexture);
+    }
+    if (description.hasGradient) {
+        shader.append(gVS_Header_Varyings_HasGradient[description.gradientType]);
+    }
+    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[description.gradientType]);
+    }
+    if (description.hasBitmap) {
+        shader.append(gFS_Uniforms_BitmapSampler);
+    }
+    shader.append(gFS_Uniforms_ColorOp[description.colorOp]);
+
+    // Generate required functions
+    if (description.hasGradient && description.hasBitmap) {
+        generateBlend(shader, "blendShaders", description.shadersMode);
+    }
+    if (description.colorOp == ProgramDescription::kColorBlend) {
+        generateBlend(shader, "blendColors", description.colorMode);
+    }
+    if (blendFramebuffer) {
+        generateBlend(shader, "blendFramebuffer", description.framebufferMode);
+    }
+    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[description.gradientType]);
+        }
+        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
+        if (!blendFramebuffer) {
+            shader.append(gFS_Main_FragColor);
+        } else {
+            shader.append(!description.swapSrcDst ?
+                    gFS_Main_FragColor_Blend : gFS_Main_FragColor_Blend_Swap);
+        }
+    }
+    // End the shader
+    shader.append(gFS_Footer);
+
+    if (DEBUG_PROGRAM_CACHE) {
+        PROGRAM_LOGD("*** Generated fragment shader:\n\n");
+        printLongString(shader);
+    }
+
+    return shader;
+}
+
+void ProgramCache::generateBlend(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(gBlendOps[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_CLAMP_TO_EDGE:
+            shader.append("texCoords.x");
+            break;
+        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_CLAMP_TO_EDGE:
+            shader.append("texCoords.y");
+            break;
+        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..4fa8011
--- /dev/null
+++ b/libs/hwui/ProgramCache.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_SWAP_SRC_DST 0x2000
+
+#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
+#define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800
+
+// Encode the xfermodes on 6 bits
+#define PROGRAM_MAX_XFERMODE 0x1f
+#define PROGRAM_XFERMODE_SHADER_SHIFT 26
+#define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20
+#define PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT 14
+
+#define PROGRAM_BITMAP_WRAPS_SHIFT 9
+#define PROGRAM_BITMAP_WRAPT_SHIFT 11
+
+#define PROGRAM_GRADIENT_TYPE_SHIFT 33
+
+///////////////////////////////////////////////////////////////////////////////
+// Types
+///////////////////////////////////////////////////////////////////////////////
+
+typedef uint64_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
+    };
+
+    enum Gradient {
+        kGradientLinear,
+        kGradientCircular,
+        kGradientSweep
+    };
+
+    ProgramDescription():
+        hasTexture(false), hasAlpha8Texture(false),
+        hasBitmap(false), isBitmapNpot(false), hasGradient(false),
+        gradientType(kGradientLinear),
+        shadersMode(SkXfermode::kClear_Mode), isBitmapFirst(false),
+        bitmapWrapS(GL_CLAMP_TO_EDGE), bitmapWrapT(GL_CLAMP_TO_EDGE),
+        colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode),
+        framebufferMode(SkXfermode::kClear_Mode), swapSrcDst(false) {
+    }
+
+    // Texturing
+    bool hasTexture;
+    bool hasAlpha8Texture;
+
+    // Shaders
+    bool hasBitmap;
+    bool isBitmapNpot;
+
+    bool hasGradient;
+    Gradient gradientType;
+
+    SkXfermode::Mode shadersMode;
+
+    bool isBitmapFirst;
+    GLenum bitmapWrapS;
+    GLenum bitmapWrapT;
+
+    // Color operations
+    int colorOp;
+    SkXfermode::Mode colorMode;
+
+    // Framebuffer blending (requires Extensions.hasFramebufferFetch())
+    // Ignored for all values < SkXfermode::kPlus_Mode
+    SkXfermode::Mode framebufferMode;
+    bool swapSrcDst;
+
+    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;
+        key |= programid(gradientType) << PROGRAM_GRADIENT_TYPE_SHIFT;
+        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;
+        }
+        key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT;
+        if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST;
+        return key;
+    }
+
+    void log(const char* message) const {
+        programid k = key();
+        PROGRAM_LOGD("%s (key = 0x%.8x%.8x)", message, uint32_t(k >> 32),
+                uint32_t(k & 0xffffffff));
+    }
+}; // 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 generateBlend(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..3012824
--- /dev/null
+++ b/libs/hwui/Properties.h
@@ -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.
+ */
+
+#ifndef ANDROID_UI_PROPERTIES_H
+#define ANDROID_UI_PROPERTIES_H
+
+#include <cutils/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"
+#define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
+#define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size"
+#define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_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"
+
+// Gamma (>= 1.0, <= 10.0)
+#define PROPERTY_TEXT_GAMMA "ro.text_gamma"
+#define PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD "ro.text_gamma.black_threshold"
+#define PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD "ro.text_gamma.white_threshold"
+
+// Converts a number of mega-bytes into bytes
+#define MB(s) s * 1024 * 1024
+
+#define DEFAULT_TEXTURE_CACHE_SIZE 18.0f
+#define DEFAULT_LAYER_CACHE_SIZE 8.0f
+#define DEFAULT_PATH_CACHE_SIZE 5.0f
+#define DEFAULT_PATCH_CACHE_SIZE 100
+#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
+#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
+#define DEFAULT_FBO_CACHE_SIZE 25
+
+#define DEFAULT_TEXT_GAMMA 1.4f
+#define DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD 64
+#define DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD 192
+
+#endif // ANDROID_UI_PROPERTIES_H
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
new file mode 100644
index 0000000..f03a602
--- /dev/null
+++ b/libs/hwui/Rect.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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);
+    }
+
+    inline float getWidth() const {
+        return right - left;
+    }
+
+    inline 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 snapToPixelBoundaries() {
+        left = floor(left);
+        top = floor(top);
+        right = ceil(right);
+        bottom = ceil(bottom);
+    }
+
+    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_UI_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..83de2b2
--- /dev/null
+++ b/libs/hwui/SkiaShader.cpp
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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), mTexture(NULL) {
+}
+
+void SkiaBitmapShader::describe(ProgramDescription& description, const Extensions& extensions) {
+    const Texture* texture = mTextureCache->get(mBitmap);
+    if (!texture) return;
+    mTexture = texture;
+
+    const float width = texture->width;
+    const float height = texture->height;
+
+    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)) &&
+            (mTileX != SkShader::kClamp_TileMode || mTileY != SkShader::kClamp_TileMode)) {
+        description.isBitmapNpot = true;
+        description.bitmapWrapS = gTileModes[mTileX];
+        description.bitmapWrapT = gTileModes[mTileY];
+        mWrapS = GL_CLAMP_TO_EDGE;
+        mWrapT = GL_CLAMP_TO_EDGE;
+    } else {
+        mWrapS = gTileModes[mTileX];
+        mWrapT = gTileModes[mTileY];
+    }
+}
+
+void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView,
+        const Snapshot& snapshot, GLuint* textureUnit) {
+    GLuint textureSlot = (*textureUnit)++;
+    glActiveTexture(gTextureUnitsMap[textureSlot]);
+
+    const Texture* texture = mTexture;
+    mTexture = NULL;
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    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, mWrapS, mWrapT, 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);
+}
+
+void SkiaBitmapShader::updateTransforms(Program* program, const mat4& modelView,
+        const Snapshot& snapshot) {
+    mat4 textureTransform;
+    if (mMatrix) {
+        SkMatrix inverse;
+        mMatrix->invert(&inverse);
+        textureTransform.load(inverse);
+        textureTransform.multiply(modelView);
+    } else {
+        textureTransform.load(modelView);
+    }
+
+    glUniformMatrix4fv(program->getUniform("textureTransform"), 1,
+            GL_FALSE, &textureTransform.data[0]);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Linear gradient shader
+///////////////////////////////////////////////////////////////////////////////
+
+static void toUnitMatrix(const SkPoint pts[2], SkMatrix* matrix) {
+    SkVector vec = pts[1] - pts[0];
+    const float mag = vec.length();
+    const float inv = mag ? 1.0f / mag : 0;
+
+    vec.scale(inv);
+    matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
+    matrix->postTranslate(-pts[0].fX, -pts[0].fY);
+    matrix->postScale(inv, inv);
+}
+
+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) {
+    SkPoint points[2];
+    points[0].set(bounds[0], bounds[1]);
+    points[1].set(bounds[2], bounds[3]);
+
+    SkMatrix unitMatrix;
+    toUnitMatrix(points, &unitMatrix);
+    mUnitMatrix.load(unitMatrix);
+
+    updateLocalMatrix(matrix);
+}
+
+SkiaLinearGradientShader::~SkiaLinearGradientShader() {
+    delete[] mBounds;
+    delete[] mColors;
+    delete[] mPositions;
+}
+
+void SkiaLinearGradientShader::describe(ProgramDescription& description,
+        const Extensions& extensions) {
+    description.hasGradient = true;
+    description.gradientType = ProgramDescription::kGradientLinear;
+}
+
+void SkiaLinearGradientShader::computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView) {
+    screenSpace.loadMultiply(mUnitMatrix, mShaderMatrix);
+    screenSpace.multiply(modelView);
+}
+
+void SkiaLinearGradientShader::updateLocalMatrix(const SkMatrix* matrix) {
+    if (matrix) {
+        mat4 localMatrix(*matrix);
+        mShaderMatrix.loadInverse(localMatrix);
+    }
+}
+
+void SkiaLinearGradientShader::setMatrix(SkMatrix* matrix) {
+    SkiaShader::setMatrix(matrix);
+    updateLocalMatrix(matrix);
+}
+
+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, mColors, mPositions, mCount, mTileX);
+    }
+
+    mat4 screenSpace;
+    computeScreenSpaceMatrix(screenSpace, modelView);
+
+    // Uniforms
+    bindTexture(texture->id, gTileModes[mTileX], gTileModes[mTileY], textureSlot);
+    glUniform1i(program->getUniform("gradientSampler"), textureSlot);
+    glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
+}
+
+void SkiaLinearGradientShader::updateTransforms(Program* program, const mat4& modelView,
+        const Snapshot& snapshot) {
+    mat4 screenSpace;
+    computeScreenSpaceMatrix(screenSpace, modelView);
+    glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Circular gradient shader
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaCircularGradientShader::SkiaCircularGradientShader(float x, float y, float radius,
+        uint32_t* colors, float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
+        SkMatrix* matrix, bool blend):
+        SkiaSweepGradientShader(kCircularGradient, x, y, colors, positions, count, key,
+                tileMode, matrix, blend),
+        mRadius(radius) {
+}
+
+void SkiaCircularGradientShader::describe(ProgramDescription& description,
+        const Extensions& extensions) {
+    description.hasGradient = true;
+    description.gradientType = ProgramDescription::kGradientCircular;
+}
+
+void SkiaCircularGradientShader::setupProgram(Program* program, const mat4& modelView,
+        const Snapshot& snapshot, GLuint* textureUnit) {
+    SkiaSweepGradientShader::setupProgram(program, modelView, snapshot, textureUnit);
+    glUniform1f(program->getUniform("gradientRadius"), 1.0f / mRadius);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Sweep gradient shader
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaSweepGradientShader::SkiaSweepGradientShader(float x, float y, uint32_t* colors,
+        float* positions, int count, SkShader* key, SkMatrix* matrix, bool blend):
+        SkiaShader(kSweepGradient, key, SkShader::kClamp_TileMode,
+                SkShader::kClamp_TileMode, matrix, blend),
+        mX(x), mY(y), mColors(colors), mPositions(positions), mCount(count) {
+}
+
+SkiaSweepGradientShader::SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors,
+        float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
+        SkMatrix* matrix, bool blend):
+        SkiaShader(type, key, tileMode, tileMode, matrix, blend),
+        mX(x), mY(y), mColors(colors), mPositions(positions), mCount(count) {
+}
+
+SkiaSweepGradientShader::~SkiaSweepGradientShader() {
+    delete[] mColors;
+    delete[] mPositions;
+}
+
+void SkiaSweepGradientShader::describe(ProgramDescription& description,
+        const Extensions& extensions) {
+    description.hasGradient = true;
+    description.gradientType = ProgramDescription::kGradientSweep;
+}
+
+void SkiaSweepGradientShader::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, mColors, mPositions, mCount);
+    }
+
+    float left = mX;
+    float top = mY;
+
+    mat4 shaderMatrix;
+    if (mMatrix) {
+        shaderMatrix.load(*mMatrix);
+        shaderMatrix.mapPoint(left, top);
+    }
+
+    mat4 copy(shaderMatrix);
+    shaderMatrix.loadInverse(copy);
+
+    snapshot.transform->mapPoint(left, top);
+
+    mat4 screenSpace(*snapshot.transform);
+    screenSpace.multiply(modelView);
+
+    // Uniforms
+    bindTexture(texture->id, gTileModes[mTileX], gTileModes[mTileY], textureSlot);
+    glUniform1i(program->getUniform("gradientSampler"), textureSlot);
+    glUniformMatrix4fv(program->getUniform("gradientMatrix"), 1, GL_FALSE, &shaderMatrix.data[0]);
+    glUniform2f(program->getUniform("gradientStart"), left, top);
+    glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
+}
+
+void SkiaSweepGradientShader::updateTransforms(Program* program, const mat4& modelView,
+        const Snapshot& snapshot) {
+    mat4 screenSpace(*snapshot.transform);
+    screenSpace.multiply(modelView);
+    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..2c1eb35
--- /dev/null
+++ b/libs/hwui/SkiaShader.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+    }
+
+    virtual void updateTransforms(Program* program, const mat4& modelView,
+            const Snapshot& snapshot) {
+    }
+
+    virtual 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);
+    void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);
+
+private:
+    /**
+     * This method does not work for n == 0.
+     */
+    inline bool isPowerOfTwo(unsigned int n) {
+        return !(n & (n - 1));
+    }
+
+    SkBitmap* mBitmap;
+    const Texture* mTexture;
+    GLenum mWrapS;
+    GLenum mWrapT;
+}; // 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);
+    void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);
+
+    void setMatrix(SkMatrix* matrix);
+
+private:
+    void updateLocalMatrix(const SkMatrix* matrix);
+    void computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView);
+
+    mat4 mUnitMatrix;
+    mat4 mShaderMatrix;
+
+    float* mBounds;
+    uint32_t* mColors;
+    float* mPositions;
+    int mCount;
+}; // struct SkiaLinearGradientShader
+
+/**
+ * A shader that draws a sweep gradient.
+ */
+struct SkiaSweepGradientShader: public SkiaShader {
+    SkiaSweepGradientShader(float x, float y, uint32_t* colors, float* positions, int count,
+            SkShader* key, SkMatrix* matrix, bool blend);
+    ~SkiaSweepGradientShader();
+
+    virtual void describe(ProgramDescription& description, const Extensions& extensions);
+    virtual void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+            GLuint* textureUnit);
+    void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);
+
+protected:
+    SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors, float* positions,
+            int count, SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
+
+    float mX, mY;
+    uint32_t* mColors;
+    float* mPositions;
+    int mCount;
+}; // struct SkiaSweepGradientShader
+
+/**
+ * A shader that draws a circular gradient.
+ */
+struct SkiaCircularGradientShader: public SkiaSweepGradientShader {
+    SkiaCircularGradientShader(float x, float y, float radius, uint32_t* colors, float* positions,
+            int count, SkShader* key,SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+            GLuint* textureUnit);
+
+private:
+    float mRadius;
+}; // struct SkiaCircularGradientShader
+
+/**
+ * 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..3d74b4c
--- /dev/null
+++ b/libs/hwui/Snapshot.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <SkCanvas.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) {
+        transform = &mTransformRoot;
+        clipRect = &mClipRectRoot;
+    }
+
+    /**
+     * Copies the specified snapshot/ The specified snapshot is stored as
+     * the previous snapshot.
+     */
+    Snapshot(const sp<Snapshot>& s, int saveFlags):
+            flags(0), previous(s), layer(NULL),
+            fbo(s->fbo), viewport(s->viewport), height(s->height) {
+        if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
+            mTransformRoot.load(*s->transform);
+            transform = &mTransformRoot;
+        } else {
+            transform = s->transform;
+        }
+
+        if (saveFlags & SkCanvas::kClip_SaveFlag) {
+            mClipRectRoot.set(*s->clipRect);
+            clipRect = &mClipRectRoot;
+        } else {
+            clipRect = s->clipRect;
+        }
+
+        if ((s->flags & Snapshot::kFlagClipSet) &&
+                !(s->flags & Snapshot::kFlagDirtyLocalClip)) {
+            mLocalClip.set(s->mLocalClip);
+        } 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 is a special type of layer
+         * backed by an FBO. This flag only makes sense when the
+         * flag kFlagIsLayer is also set.
+         */
+        kFlagIsFboLayer = 0x4,
+        /**
+         * Indicates that the local clip should be recomputed.
+         */
+        kFlagDirtyLocalClip = 0x8,
+        /**
+         * Indicates that this snapshot has changed the ortho matrix.
+         */
+        kFlagDirtyOrtho = 0x10,
+    };
+
+    /**
+     * Modifies the current clip with the new clip rectangle and
+     * the specified operation. The specified rectangle is transformed
+     * by this snapshot's trasnformation.
+     */
+    bool clip(float left, float top, float right, float bottom,
+            SkRegion::Op op = SkRegion::kIntersect_Op) {
+        Rect r(left, top, right, bottom);
+        transform->mapRect(r);
+        return clipTransformed(r, op);
+    }
+
+    /**
+     * Modifies the current clip with the new clip rectangle and
+     * the specified operation. The specified rectangle is considered
+     * already transformed.
+     */
+    bool clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op) {
+        bool clipped = false;
+
+        // NOTE: The unimplemented operations require support for regions
+        // Supporting regions would require using a stencil buffer instead
+        // of the scissor. The stencil buffer itself is not too expensive
+        // (memory cost excluded) but on fillrate limited devices, managing
+        // the stencil might have a negative impact on the framerate.
+        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) {
+            clipRect->snapToPixelBoundaries();
+            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);
+
+            mLocalClip.set(*clipRect);
+            inverse.mapRect(mLocalClip);
+
+            flags &= ~Snapshot::kFlagDirtyLocalClip;
+        }
+        return mLocalClip;
+    }
+
+    void resetTransform(float x, float y, float z) {
+        transform = &mTransformRoot;
+        transform->loadTranslate(x, y, z);
+    }
+
+    void resetClip(float left, float top, float right, float bottom) {
+        clipRect = &mClipRectRoot;
+        clipRect->set(left, top, right, bottom);
+        flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
+    }
+
+    /**
+     * Dirty flags.
+     */
+    int flags;
+
+    /**
+     * Previous snapshot.
+     */
+    sp<Snapshot> previous;
+
+    /**
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    Layer* layer;
+
+    /**
+     * Only set when the flag kFlagIsFboLayer is set.
+     */
+    GLuint fbo;
+
+    /**
+     * Current viewport.
+     */
+    Rect viewport;
+
+    /**
+     * Height of the framebuffer the snapshot is rendering into.
+     */
+    int height;
+
+    /**
+     * Contains the previous ortho matrix.
+     */
+    mat4 orthoMatrix;
+
+    /**
+     * 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;
+
+private:
+    mat4 mTransformRoot;
+    Rect mClipRectRoot;
+    Rect mLocalClip;
+
+}; // class Snapshot
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_SNAPSHOT_H
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
new file mode 100644
index 0000000..9d54277
--- /dev/null
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "TextDropShadowCache.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+TextDropShadowCache::TextDropShadowCache():
+        mCache(GenerationCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(MB(DEFAULT_DROP_SHADOW_CACHE_SIZE)) {
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_DROP_SHADOW_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting drop shadow cache size to %sMB", property);
+        setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default drop shadow cache size of %.2fMB", DEFAULT_DROP_SHADOW_CACHE_SIZE);
+    }
+
+    mCache.setOnEntryRemovedListener(this);
+}
+
+TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize):
+        mCache(GenerationCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(maxByteSize) {
+    mCache.setOnEntryRemovedListener(this);
+}
+
+TextDropShadowCache::~TextDropShadowCache() {
+    mCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t TextDropShadowCache::getSize() {
+    return mSize;
+}
+
+uint32_t TextDropShadowCache::getMaxSize() {
+    return mMaxSize;
+}
+
+void TextDropShadowCache::setMaxSize(uint32_t maxSize) {
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        mCache.removeOldest();
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void TextDropShadowCache::operator()(ShadowText& text, ShadowTexture*& texture) {
+    const uint32_t size = texture->width * texture->height;
+    mSize -= size;
+
+    if (texture) {
+        glDeleteTextures(1, &texture->id);
+        delete texture;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void TextDropShadowCache::clear() {
+    mCache.clear();
+}
+
+ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32_t len,
+        int numGlyphs, uint32_t radius) {
+    ShadowText entry(paint, radius, len, text);
+    ShadowTexture* texture = mCache.get(entry);
+
+    if (!texture) {
+        FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(paint, text, 0,
+                len, numGlyphs, radius);
+
+        texture = new ShadowTexture;
+        texture->left = shadow.penX;
+        texture->top = shadow.penY;
+        texture->width = shadow.width;
+        texture->height = shadow.height;
+        texture->generation = 0;
+        texture->blend = true;
+
+        const uint32_t size = shadow.width * shadow.height;
+        // Don't even try to cache a bitmap that's bigger than the cache
+        if (size < mMaxSize) {
+            while (mSize + size > mMaxSize) {
+                mCache.removeOldest();
+            }
+        }
+
+        glGenTextures(1, &texture->id);
+
+        glBindTexture(GL_TEXTURE_2D, texture->id);
+        // Textures are Alpha8
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
+                GL_ALPHA, GL_UNSIGNED_BYTE, shadow.image);
+
+        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);
+
+        if (size < mMaxSize) {
+            mSize += size;
+            mCache.put(entry, texture);
+        } else {
+            texture->cleanup = true;
+        }
+
+        // Cleanup shadow
+        delete[] shadow.image;
+    }
+
+    return texture;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
new file mode 100644
index 0000000..b65d62a
--- /dev/null
+++ b/libs/hwui/TextDropShadowCache.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_TEXT_DROP_SHADOW_CACHE_H
+#define ANDROID_UI_TEXT_DROP_SHADOW_CACHE_H
+
+#include <GLES2/gl2.h>
+
+#include <SkPaint.h>
+
+#include "GenerationCache.h"
+#include "FontRenderer.h"
+#include "Texture.h"
+
+namespace android {
+namespace uirenderer {
+
+struct ShadowText {
+    ShadowText() {
+        text = NULL;
+    }
+
+    ShadowText(SkPaint* paint, uint32_t radius, uint32_t len, const char* srcText):
+            radius(radius), len(len) {
+        text = new char[len];
+        memcpy(text, srcText, len);
+
+        textSize = paint->getTextSize();
+        typeface = paint->getTypeface();
+
+        hash = 0;
+        uint32_t multiplier = 1;
+        for (uint32_t i = 0; i < len; i++) {
+            hash += text[i] * multiplier;
+            uint32_t shifted = multiplier << 5;
+            multiplier = shifted - multiplier;
+        }
+    }
+
+    ShadowText(const ShadowText& shadow):
+            radius(shadow.radius), len(shadow.len), hash(shadow.hash),
+            textSize(shadow.textSize), typeface(shadow.typeface) {
+        text = new char[shadow.len];
+        memcpy(text, shadow.text, shadow.len);
+    }
+
+    ~ShadowText() {
+        delete[] text;
+    }
+
+    uint32_t radius;
+    uint32_t len;
+    uint32_t hash;
+    float textSize;
+    SkTypeface* typeface;
+    char *text;
+
+    bool operator<(const ShadowText& rhs) const {
+        if (hash < rhs.hash) return true;
+        else if (hash == rhs.hash) {
+            if (len < rhs.len) return true;
+            else if (len == rhs.len) {
+                if (radius < rhs.radius) return true;
+                else if (radius == rhs.radius) {
+                    if (textSize < rhs.textSize) return true;
+                    else if (textSize == rhs.textSize) {
+                        if (typeface < rhs.typeface) return true;
+                        else if (typeface == rhs.typeface) {
+                            return strncmp(text, rhs.text, len) < 0;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+}; // struct ShadowText
+
+/**
+ * Alpha texture used to represent a shadow.
+ */
+struct ShadowTexture: public Texture {
+    ShadowTexture(): Texture() {
+    }
+
+    float left;
+    float top;
+}; // struct ShadowTexture
+
+class TextDropShadowCache: public OnEntryRemoved<ShadowText, ShadowTexture*> {
+public:
+    TextDropShadowCache();
+    TextDropShadowCache(uint32_t maxByteSize);
+    ~TextDropShadowCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(ShadowText& text, ShadowTexture*& texture);
+
+    ShadowTexture* get(SkPaint* paint, const char* text, uint32_t len,
+            int numGlyphs, uint32_t radius);
+
+    /**
+     * Clears the cache. This causes all textures to be deleted.
+     */
+    void clear();
+
+    void setFontRenderer(FontRenderer& fontRenderer) {
+        mRenderer = &fontRenderer;
+    }
+
+    /**
+     * 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:
+    GenerationCache<ShadowText, ShadowTexture*> mCache;
+
+    uint32_t mSize;
+    uint32_t mMaxSize;
+    FontRenderer* mRenderer;
+}; // class TextDropShadowCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_TEXT_DROP_SHADOW_CACHE_H
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
new file mode 100644
index 0000000..817f143
--- /dev/null
+++ b/libs/hwui/Texture.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 {
+    Texture() {
+        cleanup = false;
+        bitmapSize = 0;
+    }
+
+    /**
+     * 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;
+    /**
+     * Indicates whether this texture should be cleaned up after use.
+     */
+    bool cleanup;
+    /**
+     * Optional, size of the original bitmap.
+     */
+    uint32_t bitmapSize;
+}; // struct Texture
+
+class AutoTexture {
+public:
+    AutoTexture(const Texture* texture): mTexture(texture) { }
+    ~AutoTexture() {
+        if (mTexture && mTexture->cleanup) {
+            glDeleteTextures(1, &mTexture->id);
+            delete mTexture;
+        }
+    }
+
+private:
+    const Texture* mTexture;
+}; // class AutoTexture
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_TEXTURE_H
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
new file mode 100644
index 0000000..701df83
--- /dev/null
+++ b/libs/hwui/TextureCache.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <utils/threads.h>
+
+#include "TextureCache.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+TextureCache::TextureCache():
+        mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)) {
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting texture cache size to %sMB", property);
+        setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default texture cache size of %.2fMB", DEFAULT_TEXTURE_CACHE_SIZE);
+    }
+
+    init();
+}
+
+TextureCache::TextureCache(uint32_t maxByteSize):
+        mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(maxByteSize) {
+    init();
+}
+
+TextureCache::~TextureCache() {
+    Mutex::Autolock _l(mLock);
+    mCache.clear();
+}
+
+void TextureCache::init() {
+    mCache.setOnEntryRemovedListener(this);
+
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
+    LOGD("    Maximum texture dimension is %d pixels", mMaxTextureSize);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t TextureCache::getSize() {
+    Mutex::Autolock _l(mLock);
+    return mSize;
+}
+
+uint32_t TextureCache::getMaxSize() {
+    Mutex::Autolock _l(mLock);
+    return mMaxSize;
+}
+
+void TextureCache::setMaxSize(uint32_t maxSize) {
+    Mutex::Autolock _l(mLock);
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        mCache.removeOldest();
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) {
+    // This will be called already locked
+    if (texture) {
+        mSize -= texture->bitmapSize;
+        glDeleteTextures(1, &texture->id);
+        delete texture;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+Texture* TextureCache::get(SkBitmap* bitmap) {
+    mLock.lock();
+    Texture* texture = mCache.get(bitmap);
+    mLock.unlock();
+
+    if (!texture) {
+        if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
+            LOGW("Bitmap too large to be uploaded into a texture");
+            return NULL;
+        }
+
+        const uint32_t size = bitmap->rowBytes() * bitmap->height();
+        // Don't even try to cache a bitmap that's bigger than the cache
+        if (size < mMaxSize) {
+            mLock.lock();
+            while (mSize + size > mMaxSize) {
+                mCache.removeOldest();
+            }
+            mLock.unlock();
+        }
+
+        texture = new Texture;
+        texture->bitmapSize = size;
+        generateTexture(bitmap, texture, false);
+
+        if (size < mMaxSize) {
+            mLock.lock();
+            mSize += size;
+            mCache.put(bitmap, texture);
+            mLock.unlock();
+        } else {
+            texture->cleanup = true;
+        }
+    } else if (bitmap->getGenerationID() != texture->generation) {
+        generateTexture(bitmap, texture, true);
+    }
+
+    return texture;
+}
+
+void TextureCache::remove(SkBitmap* bitmap) {
+    Mutex::Autolock _l(mLock);
+    mCache.remove(bitmap);
+}
+
+void TextureCache::clear() {
+    Mutex::Autolock _l(mLock);
+    mCache.clear();
+}
+
+void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate) {
+    SkAutoLockPixels alp(*bitmap);
+
+    if (!bitmap->readyToDraw()) {
+        LOGE("Cannot generate texture from bitmap");
+        return;
+    }
+
+    const bool resize = !regenerate || bitmap->width() != int(texture->width) ||
+            bitmap->height() != int(texture->height);
+
+    if (!regenerate) {
+        glGenTextures(1, &texture->id);
+    }
+
+    texture->generation = bitmap->getGenerationID();
+    texture->width = bitmap->width();
+    texture->height = bitmap->height();
+
+    glBindTexture(GL_TEXTURE_2D, texture->id);
+    glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
+
+    switch (bitmap->getConfig()) {
+    case SkBitmap::kA8_Config:
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), texture->height,
+                GL_UNSIGNED_BYTE, bitmap->getPixels());
+        texture->blend = true;
+        break;
+    case SkBitmap::kRGB_565_Config:
+        uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), texture->height,
+                GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels());
+        texture->blend = false;
+        break;
+    case SkBitmap::kARGB_8888_Config:
+        uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), texture->height,
+                GL_UNSIGNED_BYTE, bitmap->getPixels());
+        // Do this after calling getPixels() to make sure Skia's deferred
+        // decoding happened
+        texture->blend = !bitmap->isOpaque();
+        break;
+    case SkBitmap::kIndex8_Config:
+        uploadPalettedTexture(resize, bitmap, texture->width, texture->height);
+        texture->blend = false;
+        break;
+    default:
+        LOGW("Unsupported bitmap config: %d", bitmap->getConfig());
+        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);
+}
+
+void TextureCache::uploadPalettedTexture(bool resize, SkBitmap* bitmap,
+        uint32_t width, uint32_t height) {
+    SkBitmap rgbaBitmap;
+    rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+    rgbaBitmap.allocPixels();
+    rgbaBitmap.eraseColor(0);
+
+    SkCanvas canvas(rgbaBitmap);
+    canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL);
+
+    uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), height,
+            GL_UNSIGNED_BYTE, rgbaBitmap.getPixels());
+}
+
+void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height,
+        GLenum type, const GLvoid * data) {
+    if (resize) {
+        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
+    } else {
+        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
+    }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
new file mode 100644
index 0000000..34c5455
--- /dev/null
+++ b/libs/hwui/TextureCache.h
@@ -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.
+ */
+
+#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();
+    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);
+
+    void uploadPalettedTexture(bool resize, SkBitmap* bitmap, uint32_t width, uint32_t height);
+    void uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height,
+            GLenum type, const GLvoid * data);
+
+    void init();
+
+    GenerationCache<SkBitmap*, Texture*> mCache;
+
+    uint32_t mSize;
+    uint32_t mMaxSize;
+    GLint mMaxTextureSize;
+
+    /**
+     * Used to access mCache and mSize. All methods are accessed from a single
+     * thread except for remove().
+     */
+    mutable Mutex mLock;
+}; // 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..05c1a48 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -76,34 +76,43 @@
 LOCAL_SRC_FILES:= \
 	rsAdapter.cpp \
 	rsAllocation.cpp \
+	rsAnimation.cpp \
 	rsComponent.cpp \
 	rsContext.cpp \
 	rsDevice.cpp \
 	rsElement.cpp \
-        rsFileA3D.cpp \
-	rsLight.cpp \
+	rsFileA3D.cpp \
+	rsFont.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..66e27f3 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -30,20 +30,22 @@
 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 *);
@@ -60,7 +62,6 @@
 RsContext rsContextCreate(RsDevice, uint32_t version);
 RsContext rsContextCreateGL(RsDevice, uint32_t version, bool useDepth);
 void rsContextDestroy(RsContext);
-void rsObjDestroyOOB(RsContext, void *);
 
 uint32_t rsContextGetMessage(RsContext, void *data, size_t *receiveLen, size_t bufferLen, bool wait);
 void rsContextInitToClient(RsContext);
@@ -83,11 +84,17 @@
     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,
 
-    RS_TYPE_ELEMENT,
+    RS_TYPE_MATRIX_4X4,
+    RS_TYPE_MATRIX_3X3,
+    RS_TYPE_MATRIX_2X2,
+
+    RS_TYPE_ELEMENT = 1000,
     RS_TYPE_TYPE,
     RS_TYPE_ALLOCATION,
     RS_TYPE_SAMPLER,
@@ -96,24 +103,17 @@
     RS_TYPE_PROGRAM_FRAGMENT,
     RS_TYPE_PROGRAM_VERTEX,
     RS_TYPE_PROGRAM_RASTER,
-    RS_TYPE_PROGRAM_STORE
+    RS_TYPE_PROGRAM_STORE,
 };
 
 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 {
@@ -121,7 +121,8 @@
     RS_SAMPLER_MAG_FILTER,
     RS_SAMPLER_WRAP_S,
     RS_SAMPLER_WRAP_T,
-    RS_SAMPLER_WRAP_R
+    RS_SAMPLER_WRAP_R,
+    RS_SAMPLER_ANISO
 };
 
 enum RsSamplerValue {
@@ -205,9 +206,70 @@
 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_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..b82eaf1 100644
--- a/libs/rs/RenderScriptEnv.h
+++ b/libs/rs/RenderScriptEnv.h
@@ -9,12 +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 * RsLight;
-
+typedef void * RsProgramStore;
 
 typedef struct {
     float m[16];
@@ -28,4 +26,4 @@
 #define RS_PROGRAM_VERTEX_MODELVIEW_OFFSET 0
 #define RS_PROGRAM_VERTEX_PROJECTION_OFFSET 16
 #define RS_PROGRAM_VERTEX_TEXTURE_OFFSET 32
-
+#define RS_PROGRAM_VERTEX_MVP_OFFSET 48
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/drawable/gadgets_clock_mp3.png b/libs/rs/java/Fountain/res/drawable/gadgets_clock_mp3.png
deleted file mode 100755
index e91bfb4..0000000
--- a/libs/rs/java/Fountain/res/drawable/gadgets_clock_mp3.png
+++ /dev/null
Binary files differ
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..0b26cfd 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..e806969 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 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 mScratchPixelsAllocation1;
+    private Allocation mScratchPixelsAllocation2;
 
     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;
+                mScriptVBlur.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
+            }
+            else if(seekBar == mOutBlackSeekBar) {
+                mOutBlack = (float)progress;
+                mScriptVBlur.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
+            }
+            else if(seekBar == mInWhiteSeekBar) {
+                mInWhite = (float)progress + 127.0f;
+                mScriptVBlur.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
+            }
+            else if(seekBar == mOutWhiteSeekBar) {
+                mOutWhite = (float)progress + 127.0f;
+                mScriptVBlur.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;
+                mScriptVBlur.invoke_setGamma(mGamma);
+            }
+            else if(seekBar == mSaturationSeekBar) {
+                mSaturation = (float)progress / 50.0f;
+                mScriptVBlur.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 not 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,38 @@
     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);
 
-        final int pixelCount = mParams.inWidth * mParams.inHeight;
+        Type.Builder tb = new Type.Builder(mRS, Element.F32_4(mRS));
+        tb.add(android.renderscript.Dimension.X, mBitmapIn.getWidth());
+        tb.add(android.renderscript.Dimension.Y, mBitmapIn.getHeight());
+        mScratchPixelsAllocation1 = Allocation.createTyped(mRS, tb.create());
+        mScratchPixelsAllocation2 = Allocation.createTyped(mRS, tb.create());
 
-        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);
+        mScriptVBlur = new ScriptC_vertical_blur(mRS, getResources(), R.raw.vertical_blur, false);
+        mScriptHBlur = new ScriptC_horizontal_blur(mRS, getResources(), R.raw.horizontal_blur, false);
 
-        mInData = new int[pixelCount];
-        mBitmap.getPixels(mInData, 0, mParams.inWidth, 0, 0, mParams.inWidth, mParams.inHeight);
-        mInPixelsAllocation.data(mInData);
+        mScript = new ScriptC_threshold(mRS, getResources(), R.raw.threshold, false);
+        mScript.set_width(mBitmapIn.getWidth());
+        mScript.set_height(mBitmapIn.getHeight());
+        mScript.set_radius(mRadius);
 
-        mOutData = new int[pixelCount];
-        mOutPixelsAllocation.data(mOutData);
+        mScriptVBlur.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
+        mScriptVBlur.invoke_setGamma(mGamma);
+        mScriptVBlur.invoke_setSaturation(mSaturation);
 
-        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);
+        mScript.bind_InPixel(mInPixelsAllocation);
+        mScript.bind_OutPixel(mOutPixelsAllocation);
+        mScript.bind_ScratchPixel1(mScratchPixelsAllocation1);
+        mScript.bind_ScratchPixel2(mScratchPixelsAllocation2);
 
-        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);
     }
 
     private Bitmap loadBitmap(int resource) {
@@ -229,4 +414,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_filter();
+        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..cfffac8
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/horizontal_blur.rs
@@ -0,0 +1,30 @@
+#pragma version(1)
+
+#include "ip.rsh"
+
+void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32_t y) {
+    float4 *output = (float4 *)v_out;
+    const FilterStruct *fs = (const FilterStruct *)usrData;
+    const float4 *input = (const float4 *)rsGetElementAt(fs->ain, 0, y);
+
+    float3 blurredPixel = 0;
+    const float *gPtr = fs->gaussian;
+    if ((x > fs->radius) && (x < (fs->width - fs->radius))) {
+        const float4 *i = input + (x - fs->radius);
+        for(int r = -fs->radius; r <= fs->radius; r ++) {
+            blurredPixel += i->xyz * gPtr[0];
+            gPtr++;
+            i++;
+        }
+    } else {
+        for(int r = -fs->radius; r <= fs->radius; r ++) {
+            // Stepping left and right away from the pixel
+            int validW = rsClamp(x + r, (uint)0, (uint)(fs->width - 1));
+            blurredPixel += input[validW].xyz * gPtr[0];
+            gPtr++;
+        }
+    }
+
+    output->xyz = blurredPixel;
+}
+
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..1d7a719
--- /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 FilterStruct_s {
+    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/threshold.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
new file mode 100644
index 0000000..f5fecba
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
@@ -0,0 +1,95 @@
+#pragma version(1)
+
+#include "ip.rsh"
+
+int height;
+int width;
+int radius;
+
+uchar4 * InPixel;
+uchar4 * OutPixel;
+float4 * ScratchPixel1;
+float4 * ScratchPixel2;
+
+#pragma rs export_var(height, width, radius, InPixel, OutPixel, ScratchPixel1, ScratchPixel2, vBlurScript, hBlurScript)
+#pragma rs export_func(filter);
+
+rs_script vBlurScript;
+rs_script hBlurScript;
+
+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 copyInput() {
+    rs_allocation ain = rsGetAllocation(InPixel);
+    uint32_t dimx = rsAllocationGetDimX(ain);
+    uint32_t dimy = rsAllocationGetDimY(ain);
+    for(uint32_t y = 0; y < dimy; y++) {
+        for(uint32_t x = 0; x < dimx; x++) {
+            ScratchPixel1[x + y * dimx] = convert_float4(InPixel[x + y * dimx]);
+        }
+    }
+}
+
+void filter() {
+    copyInput();
+    computeGaussianWeights();
+
+    FilterStruct fs;
+    fs.gaussian = gaussian;
+    fs.width = width;
+    fs.height = height;
+    fs.radius = radius;
+
+    fs.ain = rsGetAllocation(ScratchPixel1);
+    rsForEach(hBlurScript, fs.ain, rsGetAllocation(ScratchPixel2), &fs);
+
+    fs.ain = rsGetAllocation(ScratchPixel2);
+    rsForEach(vBlurScript, fs.ain, rsGetAllocation(OutPixel), &fs);
+    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..d901d2a
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs
@@ -0,0 +1,92 @@
+#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_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) {
+    uchar4 *output = (uchar4 *)v_out;
+    const FilterStruct *fs = (const FilterStruct *)usrData;
+    const float4 *input = (const float4 *)rsGetElementAt(fs->ain, x, 0);
+
+    float3 blurredPixel = 0;
+    const float *gPtr = fs->gaussian;
+    if ((y > fs->radius) && (y < (fs->height - fs->radius))) {
+        const float4 *i = input + ((y - fs->radius) * fs->width);
+        for(int r = -fs->radius; r <= fs->radius; r ++) {
+            blurredPixel += i->xyz * gPtr[0];
+            gPtr++;
+            i += fs->width;
+        }
+    } else {
+        for(int r = -fs->radius; r <= fs->radius; r ++) {
+            int validH = rsClamp(y + r, (uint)0, (uint)(fs->height - 1));
+            const float4 *i = input + validH * fs->width;
+            blurredPixel += i->xyz * gPtr[0];
+            gPtr++;
+        }
+    }
+
+    float3 temp = rsMatrixMultiply(&colorMat, blurredPixel);
+    temp = (clamp(temp, 0.f, 255.f) - inBlack) * overInWMinInB;
+    if (gamma.x != 1.0f)
+        temp = pow(temp, (float3)gamma);
+    temp = clamp(temp * outWMinOutB + outBlack, 0.f, 255.f);
+
+    output->xyz = convert_uchar3(temp);
+    //output->w = input->w;
+}
+
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..959fe53
--- /dev/null
+++ b/libs/rs/java/ModelViewer/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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="SimpleModel"
+                  android:label="SimpleModel"
+                  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>
+        <activity android:name="SceneGraph"
+                  android:label="SceneGraph"
+                  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..f7353fd
--- /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..f48895c
--- /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/SceneGraph.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraph.java
new file mode 100644
index 0000000..557e0cc
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraph.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 SceneGraph extends Activity {
+
+    private SceneGraphView 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 SceneGraphView(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/SceneGraphRS.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
new file mode 100644
index 0000000..b5592f0
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
@@ -0,0 +1,219 @@
+/*
+ * 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.Map;
+import java.util.Vector;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.renderscript.Element.Builder;
+import android.renderscript.ProgramStore.DepthFunc;
+import android.util.Log;
+
+
+public class SceneGraphRS {
+
+    private final int STATE_LAST_FOCUS = 1;
+
+    int mWidth;
+    int mHeight;
+    int mRotation;
+
+    public SceneGraphRS() {
+    }
+
+    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_scenegraph mScript;
+    private ScriptC_transform mTransformScript;
+
+    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);
+
+        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.CLAMP);
+        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);
+        mPVBackground = pvb.create();
+
+        mPVA = new ProgramVertex.MatrixAllocation(mRS);
+        mPVBackground.bindAllocation(mPVA);
+
+        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);
+    }
+
+    SgTransform mRootTransform;
+    SgTransform mGroup1;
+
+    SgTransform mRobot1;
+    SgTransform mRobot2;
+
+    void initTransformHierarchy() {
+        mRootTransform = new SgTransform(mRS);
+
+        mGroup1 = new SgTransform(mRS);
+        mRootTransform.addChild(mGroup1);
+
+        mRobot1 = new SgTransform(mRS);
+        mRobot2 = new SgTransform(mRS);
+
+        mGroup1.addChild(mRobot1);
+        mGroup1.addChild(mRobot2);
+
+        mGroup1.setTransform(0, new Float4(0.0f, 0.0f, -15.0f, 0.0f), TransformType.TRANSLATE);
+        mGroup1.setTransform(1, new Float4(0.0f, 1.0f, 0.0f, 15.0f), TransformType.ROTATE);
+
+        mRobot1.setTransform(0, new Float4(-3.0f, -0.5f, 0.0f, 0.0f), TransformType.TRANSLATE);
+        mRobot1.setTransform(1, new Float4(0.0f, 1.0f, 0.0f, 20.0f), TransformType.ROTATE);
+        mRobot1.setTransform(2, new Float4(0.2f, 0.2f, 0.2f, 0.0f), TransformType.SCALE);
+
+        mRobot2.setTransform(0, new Float4(3.0f, 0.0f, 0.0f, 0.0f), TransformType.TRANSLATE);
+        mRobot2.setTransform(1, new Float4(0.0f, 1.0f, 0.0f, -20.0f), TransformType.ROTATE);
+        mRobot2.setTransform(2, new Float4(0.3f, 0.3f, 0.3f, 0.0f), TransformType.SCALE);
+    }
+
+    private void initRS() {
+
+        mScript = new ScriptC_scenegraph(mRS, mRes, R.raw.scenegraph, true);
+        mTransformScript = new ScriptC_transform(mRS, mRes, R.raw.transform, false);
+        mTransformScript.set_transformScript(mTransformScript);
+
+        mScript.set_gTransformRS(mTransformScript);
+
+        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();
+
+        initTransformHierarchy();
+
+        Log.v("========SceneGraph========", "transform hierarchy initialized");
+
+        mScript.bind_gRootNode(mRootTransform.getField());
+
+        mScript.bind_gGroup(mGroup1.mParent.mChildField);
+        mScript.bind_gRobot1(mRobot1.mParent.mChildField);
+        mScript.set_gRobot1Index(mRobot1.mIndexInParentGroup);
+        mScript.bind_gRobot2(mRobot2.mParent.mChildField);
+        mScript.set_gRobot2Index(mRobot2.mIndexInParentGroup);
+
+        mRS.contextBindRootScript(mScript);
+    }
+}
+
+
+
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphView.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphView.java
new file mode 100644
index 0000000..44a59b2
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphView.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 SceneGraphView extends RSSurfaceView {
+
+    public SceneGraphView(Context context) {
+        super(context);
+        //setFocusable(true);
+    }
+
+    private RenderScriptGL mRS;
+    private SceneGraphRS 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 SceneGraphRS();
+            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/SgTransform.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SgTransform.java
new file mode 100644
index 0000000..e70e811
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SgTransform.java
@@ -0,0 +1,158 @@
+/*
+ * 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.Map;
+import java.util.Vector;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.renderscript.Element.Builder;
+import android.renderscript.ProgramStore.DepthFunc;
+import android.util.Log;
+
+enum TransformType {
+
+    NONE(0),
+    TRANSLATE(1),
+    ROTATE(2),
+    SCALE(3);
+
+    int mID;
+    TransformType(int id) {
+        mID = id;
+    }
+}
+
+public class SgTransform {
+
+
+    ScriptField_SgTransform mTransformField;
+    ScriptField_SgTransform mChildField;
+    public ScriptField_SgTransform.Item mTransformData;
+
+    Float4[] mTransforms;
+    TransformType[] mTransformTypes;
+
+    RenderScript mRS;
+
+    Vector mChildren;
+    SgTransform mParent;
+    int mIndexInParentGroup;
+
+    public void setParent(SgTransform parent, int parentIndex) {
+        mParent = parent;
+        mIndexInParentGroup = parentIndex;
+    }
+
+    public void addChild(SgTransform child) {
+        mChildren.add(child);
+        child.setParent(this, mChildren.size() - 1);
+    }
+
+    public void setTransform(int index, Float4 value, TransformType type) {
+        mTransforms[index] = value;
+        mTransformTypes[index] = type;
+    }
+
+    void initData() {
+        int numTransforms = 16;
+        mTransforms = new Float4[numTransforms];
+        mTransformTypes = new TransformType[numTransforms];
+        for(int i = 0; i < numTransforms; i ++) {
+            mTransforms[i] = new Float4(0, 0, 0, 0);
+            mTransformTypes[i] = TransformType.NONE;
+        }
+    }
+
+    void setData() {
+
+        mTransformData.globalMat = new Matrix4f();
+        mTransformData.localMat = new Matrix4f();
+
+        mTransformData.transforms0 = mTransforms[0];
+        mTransformData.transforms1 = mTransforms[1];
+        mTransformData.transforms2 = mTransforms[2];
+        mTransformData.transforms3 = mTransforms[3];
+        mTransformData.transforms4 = mTransforms[4];
+        mTransformData.transforms5 = mTransforms[5];
+        mTransformData.transforms6 = mTransforms[6];
+        mTransformData.transforms7 = mTransforms[7];
+        mTransformData.transforms8 = mTransforms[8];
+        mTransformData.transforms9 = mTransforms[9];
+        mTransformData.transforms10 = mTransforms[10];
+        mTransformData.transforms11 = mTransforms[11];
+        mTransformData.transforms12 = mTransforms[12];
+        mTransformData.transforms13 = mTransforms[13];
+        mTransformData.transforms14 = mTransforms[14];
+        mTransformData.transforms15 = mTransforms[15];
+
+        mTransformData.transformType0 = mTransformTypes[0].mID;
+        mTransformData.transformType1 = mTransformTypes[1].mID;
+        mTransformData.transformType2 = mTransformTypes[2].mID;
+        mTransformData.transformType3 = mTransformTypes[3].mID;
+        mTransformData.transformType4 = mTransformTypes[4].mID;
+        mTransformData.transformType5 = mTransformTypes[5].mID;
+        mTransformData.transformType6 = mTransformTypes[6].mID;
+        mTransformData.transformType7 = mTransformTypes[7].mID;
+        mTransformData.transformType8 = mTransformTypes[8].mID;
+        mTransformData.transformType9 = mTransformTypes[9].mID;
+        mTransformData.transformType10 = mTransformTypes[10].mID;
+        mTransformData.transformType11 = mTransformTypes[11].mID;
+        mTransformData.transformType12 = mTransformTypes[12].mID;
+        mTransformData.transformType13 = mTransformTypes[13].mID;
+        mTransformData.transformType14 = mTransformTypes[14].mID;
+        mTransformData.transformType15 = mTransformTypes[15].mID;
+
+        mTransformData.isDirty = 1;
+        mTransformData.children = null;
+
+    }
+
+    public SgTransform(RenderScript rs) {
+        mRS = rs;
+        mTransformData = new ScriptField_SgTransform.Item();
+        mChildren = new Vector();
+        initData();
+    }
+
+    public ScriptField_SgTransform.Item getData() {
+        setData();
+        if(mChildren.size() != 0) {
+            mChildField = new ScriptField_SgTransform(mRS, mChildren.size());
+            mTransformData.children = mChildField.getAllocation();
+
+            for(int i = 0; i < mChildren.size(); i ++) {
+                SgTransform child = (SgTransform)mChildren.get(i);
+                mChildField.set(child.getData(), i, false);
+            }
+            mChildField.copyAll();
+        }
+
+        return mTransformData;
+    }
+
+    public ScriptField_SgTransform getField() {
+        mTransformField = new ScriptField_SgTransform(mRS, 1);
+        mTransformField.set(getData(), 0, true);
+        return mTransformField;
+    }
+}
+
+
+
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModel.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModel.java
new file mode 100644
index 0000000..cb7c39c
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModel.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 SimpleModel extends Activity {
+
+    private SimpleModelView 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 SimpleModelView(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/SimpleModelRS.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java
new file mode 100644
index 0000000..afbf30b
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java
@@ -0,0 +1,169 @@
+/*
+ * 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 SimpleModelRS {
+
+    private final int STATE_LAST_FOCUS = 1;
+
+    int mWidth;
+    int mHeight;
+    int mRotation;
+
+    public SimpleModelRS() {
+    }
+
+    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_simplemodel 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);
+
+        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.CLAMP);
+        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);
+        mPVBackground = pvb.create();
+
+        mPVA = new ProgramVertex.MatrixAllocation(mRS);
+        mPVBackground.bindAllocation(mPVA);
+
+        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_simplemodel(mRS, mRes, R.raw.simplemodel, 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/SimpleModelView.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelView.java
new file mode 100644
index 0000000..2574fdd
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelView.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 SimpleModelView extends RSSurfaceView {
+
+    public SimpleModelView(Context context) {
+        super(context);
+        //setFocusable(true);
+    }
+
+    private RenderScriptGL mRS;
+    private SimpleModelRS 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 SimpleModelRS();
+            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/scenegraph.rs b/libs/rs/java/ModelViewer/src/com/android/modelviewer/scenegraph.rs
new file mode 100644
index 0000000..ce6bb1e
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/scenegraph.rs
@@ -0,0 +1,96 @@
+// 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"
+#include "transform_def.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;
+
+rs_script gTransformRS;
+
+SgTransform *gGroup;
+SgTransform *gRobot1;
+int gRobot1Index;
+SgTransform *gRobot2;
+int gRobot2Index;
+
+SgTransform *gRootNode;
+
+#pragma rs export_var(gPVBackground, gPFBackground, gTGrid, gTestMesh, gPFSBackground, gRotate, gItalic, gTextAlloc, gTransformRS, gGroup, gRobot1, gRobot1Index, gRobot2, gRobot2Index, gRootNode)
+
+float gDT;
+int64_t gLastTime;
+
+void init() {
+    gRotate = 0.0f;
+}
+
+int root(int launchID) {
+
+    gGroup->transforms1.w += 0.5f;
+    gGroup->isDirty = 1;
+
+    SgTransform *robot1Ptr = gRobot1 + gRobot1Index;
+
+    robot1Ptr->transforms1.w -= 1.5f;
+    robot1Ptr->isDirty = 1;
+
+    SgTransform *robot2Ptr = gRobot2 + gRobot2Index;
+    robot2Ptr->transforms1.w += 2.5f;
+    robot2Ptr->isDirty = 1;
+
+    rsForEach(gTransformRS, gRootNode->children, gRootNode->children, 0);
+
+    rsgClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgClearDepth(1.0f);
+
+    rsgBindProgramVertex(gPVBackground);
+    rs_matrix4x4 proj;
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+
+    rsgBindProgramFragment(gPFBackground);
+    rsgBindProgramStore(gPFSBackground);
+    rsgBindTexture(gPFBackground, 0, gTGrid);
+
+    rsgProgramVertexLoadModelMatrix(&robot1Ptr->globalMat);
+    rsgDrawMesh(gTestMesh);
+
+    rsgProgramVertexLoadModelMatrix(&robot2Ptr->globalMat);
+    rsgDrawMesh(gTestMesh);
+
+    color(0.3f, 0.3f, 0.3f, 1.0f);
+    rsgDrawText("Renderscript transform test", 30, 695);
+
+    rsgBindFont(gItalic);
+    rsgDrawText(gTextAlloc, 30, 730);
+
+    return 10;
+}
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/simplemodel.rs b/libs/rs/java/ModelViewer/src/com/android/modelviewer/simplemodel.rs
new file mode 100644
index 0000000..43be266
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/simplemodel.rs
@@ -0,0 +1,76 @@
+// 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);
+    rs_matrix4x4 proj;
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+
+    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, -10.0f);
+    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/java/ModelViewer/src/com/android/modelviewer/transform.rs b/libs/rs/java/ModelViewer/src/com/android/modelviewer/transform.rs
new file mode 100644
index 0000000..2ef29cf
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/transform.rs
@@ -0,0 +1,102 @@
+// 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 "transform_def.rsh"
+
+rs_script transformScript;
+
+#pragma rs export_var(transformScript)
+
+typedef struct {
+    int changed;
+    rs_matrix4x4 *mat;
+} ParentData;
+
+void appendTransformation(int type, float4 data, rs_matrix4x4 *mat) {
+    rs_matrix4x4 temp;
+
+    switch(type) {
+    case TRANSFORM_TRANSLATE:
+        rsMatrixLoadTranslate(&temp, data.x, data.y, data.z);
+        break;
+    case TRANSFORM_ROTATE:
+        rsMatrixLoadRotate(&temp, data.w, data.x, data.y, data.z);
+        break;
+    case TRANSFORM_SCALE:
+        rsMatrixLoadScale(&temp, data.x, data.y, data.z);
+        break;
+    }
+    rsMatrixMultiply(mat, &temp);
+}
+
+void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32_t y) {
+
+    SgTransform *data = (SgTransform *)v_out;
+    const ParentData *parent = (const ParentData *)usrData;
+
+    //rsDebug("Transform data", (int)data);
+    //rsDebug("Entering parent", (int)parent);
+
+    rs_matrix4x4 *localMat = &data->localMat;
+    rs_matrix4x4 *globalMat = &data->globalMat;
+
+    ParentData toChild;
+    toChild.changed = 0;
+    toChild.mat = globalMat;
+
+    //rsDebug("Transform is dirty", data->isDirty);
+
+    // Refresh matrices if dirty
+    if(data->isDirty) {
+        data->isDirty = 0;
+        toChild.changed = 1;
+
+        // Reset our local matrix
+        rsMatrixLoadIdentity(localMat);
+
+        float4 *transformSource = &data->transforms0;
+        int *transformTypes = &data->transformType0;
+
+        for(int i = 0; i < 16; i ++) {
+            if(transformTypes[i] == TRANSFORM_NONE) {
+                break;
+            }
+            //rsDebug("Transform adding transformation", transformTypes[i]);
+            appendTransformation(transformTypes[i], transformSource[i], localMat);
+        }
+    }
+
+    //rsDebug("Transform checking parent", (int)0);
+
+    if(parent) {
+        if(parent->changed) {
+            toChild.changed = 1;
+
+            rsMatrixLoad(globalMat, parent->mat);
+            rsMatrixMultiply(globalMat, localMat);
+        }
+    }
+    else {
+        rsMatrixLoad(globalMat, localMat);
+    }
+
+    //rsDebug("Transform calling self with child ", (int)data->children.p);
+    if(data->children.p) {
+        rsForEach(transformScript, data->children, data->children, (void*)&toChild);
+    }
+}
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/transform_def.rsh b/libs/rs/java/ModelViewer/src/com/android/modelviewer/transform_def.rsh
new file mode 100644
index 0000000..5c872a7
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/transform_def.rsh
@@ -0,0 +1,66 @@
+// 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)
+
+#define TRANSFORM_NONE 0
+#define TRANSFORM_TRANSLATE 1
+#define TRANSFORM_ROTATE 2
+#define TRANSFORM_SCALE 3
+
+typedef struct {
+    rs_matrix4x4 globalMat;
+    rs_matrix4x4 localMat;
+
+    float4 transforms0;
+    float4 transforms1;
+    float4 transforms2;
+    float4 transforms3;
+    float4 transforms4;
+    float4 transforms5;
+    float4 transforms6;
+    float4 transforms7;
+    float4 transforms8;
+    float4 transforms9;
+    float4 transforms10;
+    float4 transforms11;
+    float4 transforms12;
+    float4 transforms13;
+    float4 transforms14;
+    float4 transforms15;
+
+    int transformType0;
+    int transformType1;
+    int transformType2;
+    int transformType3;
+    int transformType4;
+    int transformType5;
+    int transformType6;
+    int transformType7;
+    int transformType8;
+    int transformType9;
+    int transformType10;
+    int transformType11;
+    int transformType12;
+    int transformType13;
+    int transformType14;
+    int transformType15;
+
+    int isDirty;
+
+    rs_allocation children;
+
+} SgTransform;
diff --git a/libs/rs/java/Samples/Android.mk b/libs/rs/java/Samples/Android.mk
new file mode 100644
index 0000000..65ae734
--- /dev/null
+++ b/libs/rs/java/Samples/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 := Samples
+
+include $(BUILD_PACKAGE)
+
+endif
diff --git a/libs/rs/java/Samples/AndroidManifest.xml b/libs/rs/java/Samples/AndroidManifest.xml
new file mode 100644
index 0000000..be191f2
--- /dev/null
+++ b/libs/rs/java/Samples/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.samples">
+    <application android:label="Samples"
+    android:icon="@drawable/test_pattern">
+        <activity android:name="RsList"
+                  android:label="RsList"                  
+                  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>
+        
+        <activity android:name="RsRenderStates"
+                  android:label="RsStates"                  
+                  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/Samples/res/drawable/checker.png b/libs/rs/java/Samples/res/drawable/checker.png
new file mode 100644
index 0000000..b631e1e
--- /dev/null
+++ b/libs/rs/java/Samples/res/drawable/checker.png
Binary files differ
diff --git a/libs/rs/java/Samples/res/drawable/data.png b/libs/rs/java/Samples/res/drawable/data.png
new file mode 100644
index 0000000..8e34714
--- /dev/null
+++ b/libs/rs/java/Samples/res/drawable/data.png
Binary files differ
diff --git a/libs/rs/java/Samples/res/drawable/leaf.png b/libs/rs/java/Samples/res/drawable/leaf.png
new file mode 100644
index 0000000..3cd3775
--- /dev/null
+++ b/libs/rs/java/Samples/res/drawable/leaf.png
Binary files differ
diff --git a/libs/rs/java/Samples/res/drawable/test_pattern.png b/libs/rs/java/Samples/res/drawable/test_pattern.png
new file mode 100644
index 0000000..e7d1455
--- /dev/null
+++ b/libs/rs/java/Samples/res/drawable/test_pattern.png
Binary files differ
diff --git a/libs/rs/java/Samples/res/drawable/torusmap.png b/libs/rs/java/Samples/res/drawable/torusmap.png
new file mode 100644
index 0000000..1e08f3b
--- /dev/null
+++ b/libs/rs/java/Samples/res/drawable/torusmap.png
Binary files differ
diff --git a/libs/rs/java/Samples/res/raw/multitexf.glsl b/libs/rs/java/Samples/res/raw/multitexf.glsl
new file mode 100644
index 0000000..351ff9b
--- /dev/null
+++ b/libs/rs/java/Samples/res/raw/multitexf.glsl
@@ -0,0 +1,12 @@
+varying vec2 varTex0;
+
+void main() {
+   vec2 t0 = varTex0.xy;
+   lowp vec4 col0 = texture2D(UNI_Tex0, t0).rgba;
+   lowp vec4 col1 = texture2D(UNI_Tex1, t0*4.0).rgba;
+   lowp vec4 col2 = texture2D(UNI_Tex2, t0).rgba;
+   col0.xyz = col0.xyz*col1.xyz*1.5;
+   col0.xyz = mix(col0.xyz, col2.xyz, col2.w);
+   gl_FragColor = col0;
+}
+
diff --git a/libs/rs/java/Samples/res/raw/shaderf.glsl b/libs/rs/java/Samples/res/raw/shaderf.glsl
new file mode 100644
index 0000000..fcbe7ee
--- /dev/null
+++ b/libs/rs/java/Samples/res/raw/shaderf.glsl
@@ -0,0 +1,16 @@
+
+varying lowp float light0_Diffuse;
+varying lowp float light0_Specular;
+varying lowp float light1_Diffuse;
+varying lowp float light1_Specular;
+varying vec2 varTex0;
+
+void main() {
+   vec2 t0 = varTex0.xy;
+   lowp vec4 col = texture2D(UNI_Tex0, t0).rgba;
+   col.xyz = col.xyz * (light0_Diffuse * UNI_light0_DiffuseColor + light1_Diffuse * UNI_light1_DiffuseColor);
+   col.xyz += light0_Specular * UNI_light0_SpecularColor;
+   col.xyz += light1_Specular * UNI_light1_SpecularColor;
+   gl_FragColor = col;
+}
+
diff --git a/libs/rs/java/Samples/res/raw/shaderv.glsl b/libs/rs/java/Samples/res/raw/shaderv.glsl
new file mode 100644
index 0000000..867589cf
--- /dev/null
+++ b/libs/rs/java/Samples/res/raw/shaderv.glsl
@@ -0,0 +1,30 @@
+varying float light0_Diffuse;
+varying float light0_Specular;
+varying float light1_Diffuse;
+varying float light1_Specular;
+varying vec2 varTex0;
+
+// This is where actual shader code begins
+void main() {
+   vec4 worldPos = UNI_model * ATTRIB_position;
+   gl_Position = UNI_proj * worldPos;
+
+   mat3 model3 = mat3(UNI_model[0].xyz, UNI_model[1].xyz, UNI_model[2].xyz);
+   vec3 worldNorm = model3 * ATTRIB_normal;
+   vec3 V = normalize(-worldPos.xyz);
+
+   vec3 light0Vec = normalize(UNI_light0_Posision - worldPos.xyz);
+   vec3 light0R = reflect(light0Vec, worldNorm);
+   light0_Diffuse = clamp(dot(worldNorm, light0Vec), 0.0, 1.0) * UNI_light0_Diffuse;
+   float light0Spec = clamp(dot(light0R, V), 0.001, 1.0);
+   light0_Specular = pow(light0Spec, UNI_light0_CosinePower) * UNI_light0_Specular;
+
+   vec3 light1Vec = normalize(UNI_light1_Posision - worldPos.xyz);
+   vec3 light1R = reflect(light1Vec, worldNorm);
+   light1_Diffuse = clamp(dot(worldNorm, light1Vec), 0.0, 1.0) * UNI_light1_Diffuse;
+   float light1Spec = clamp(dot(light1R, V), 0.001, 1.0);
+   light1_Specular = pow(light1Spec, UNI_light1_CosinePower) * UNI_light1_Specular;
+
+   gl_PointSize = 1.0;
+   varTex0 = ATTRIB_texture0;
+}
diff --git a/libs/rs/java/Samples/res/raw/torus.a3d b/libs/rs/java/Samples/res/raw/torus.a3d
new file mode 100644
index 0000000..0322b01
--- /dev/null
+++ b/libs/rs/java/Samples/res/raw/torus.a3d
Binary files differ
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsList.java b/libs/rs/java/Samples/src/com/android/samples/RsList.java
new file mode 100644
index 0000000..0f6b1ac
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/RsList.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.samples;
+
+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 RsList extends Activity {
+
+    private RsListView 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 RsListView(this);
+        setContentView(mView);
+    }
+
+    @Override
+    protected void onResume() {
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity loses focus
+        super.onResume();
+        mView.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity loses focus
+        super.onPause();
+        mView.onPause();
+    }
+
+}
+
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsListRS.java b/libs/rs/java/Samples/src/com/android/samples/RsListRS.java
new file mode 100644
index 0000000..aaeea876
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/RsListRS.java
@@ -0,0 +1,145 @@
+/*
+ * 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.samples;
+
+import java.io.Writer;
+import java.util.Vector;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.renderscript.ProgramStore.DepthFunc;
+import android.util.Log;
+
+
+public class RsListRS {
+
+    private final int STATE_LAST_FOCUS = 1;
+
+    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"
+    };
+
+    int mWidth;
+    int mHeight;
+
+    public RsListRS() {
+    }
+
+    public void init(RenderScriptGL rs, Resources res, int width, int height) {
+        mRS = rs;
+        mRes = res;
+        mWidth = width;
+        mHeight = height;
+        initRS();
+    }
+
+    private Resources mRes;
+    private RenderScriptGL mRS;
+    private Font mItalic;
+
+    ScriptField_ListAllocs_s mListAllocs;
+
+    private ScriptC_rslist mScript;
+
+    int mLastX;
+    int mLastY;
+
+    public void onActionDown(int x, int y) {
+        mScript.set_gDY(0.0f);
+
+        mLastX = x;
+        mLastY = y;
+    }
+
+    public void onActionMove(int x, int y) {
+        int dx = mLastX - x;
+        int dy = mLastY - y;
+
+        if(Math.abs(dy) <= 2) {
+            dy = 0;
+        }
+
+        mScript.set_gDY(dy);
+
+        mLastX = x;
+        mLastY = y;
+    }
+
+    private void initRS() {
+
+        mScript = new ScriptC_rslist(mRS, mRes, R.raw.rslist, true);
+
+        mListAllocs = new ScriptField_ListAllocs_s(mRS, DATA_LIST.length);
+        for(int i = 0; i < DATA_LIST.length; i ++) {
+            ScriptField_ListAllocs_s.Item listElem = new ScriptField_ListAllocs_s.Item();
+            listElem.text = Allocation.createFromString(mRS, DATA_LIST[i]);
+            mListAllocs.set(listElem, i, false);
+        }
+
+        mListAllocs.copyAll();
+
+        mScript.bind_gList(mListAllocs);
+
+        mItalic = Font.createFromFamily(mRS, mRes, "serif", Font.Style.BOLD_ITALIC, 8);
+        mScript.set_gItalic(mItalic);
+
+        mRS.contextBindRootScript(mScript);
+    }
+}
+
+
+
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsListView.java b/libs/rs/java/Samples/src/com/android/samples/RsListView.java
new file mode 100644
index 0000000..b98ea08
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/RsListView.java
@@ -0,0 +1,98 @@
+/*
+ * 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.samples;
+
+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 RsListView extends RSSurfaceView {
+
+    public RsListView(Context context) {
+        super(context);
+        //setFocusable(true);
+    }
+
+    private RenderScriptGL mRS;
+    private RsListRS 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 RsListRS();
+            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 = false;
+        int act = ev.getAction();
+        if (act == ev.ACTION_DOWN) {
+            mRender.onActionDown((int)ev.getX(), (int)ev.getY());
+            ret = true;
+        }
+        else if (act == ev.ACTION_MOVE) {
+            mRender.onActionMove((int)ev.getX(), (int)ev.getY());
+            ret = true;
+        }
+
+        return ret;
+    }
+}
+
+
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsRenderStates.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStates.java
new file mode 100644
index 0000000..391007e
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStates.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.samples;
+
+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 RsRenderStates extends Activity {
+
+    private RsRenderStatesView 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 RsRenderStatesView(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/Samples/src/com/android/samples/RsRenderStatesRS.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
new file mode 100644
index 0000000..0990da3
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
@@ -0,0 +1,352 @@
+/*
+ * 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.samples;
+
+import java.io.Writer;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.renderscript.*;
+import android.renderscript.ProgramStore.DepthFunc;
+import android.renderscript.Sampler.Value;
+import android.util.Log;
+
+
+public class RsRenderStatesRS {
+
+    int mWidth;
+    int mHeight;
+
+    public RsRenderStatesRS() {
+    }
+
+    public void init(RenderScriptGL rs, Resources res, int width, int height) {
+        mRS = rs;
+        mRes = res;
+        mWidth = width;
+        mHeight = height;
+        mOptionsARGB.inScaled = false;
+        mOptionsARGB.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        mMode = 0;
+        mMaxModes = 9;
+        initRS();
+    }
+
+    private Resources mRes;
+    private RenderScriptGL mRS;
+
+    private Sampler mLinearClamp;
+    private Sampler mLinearWrap;
+    private Sampler mMipLinearWrap;
+    private Sampler mNearestClamp;
+    private Sampler mMipLinearAniso8;
+    private Sampler mMipLinearAniso15;
+
+    private ProgramStore mProgStoreBlendNoneDepth;
+    private ProgramStore mProgStoreBlendNone;
+    private ProgramStore mProgStoreBlendAlpha;
+    private ProgramStore mProgStoreBlendAdd;
+
+    private ProgramFragment mProgFragmentTexture;
+    private ProgramFragment mProgFragmentColor;
+
+    private ProgramVertex mProgVertex;
+    private ProgramVertex.MatrixAllocation mPVA;
+
+    // Custom shaders
+    private ProgramVertex mProgVertexCustom;
+    private ProgramFragment mProgFragmentCustom;
+    private ProgramFragment mProgFragmentMultitex;
+    private ScriptField_VertexShaderConstants_s mVSConst;
+    private ScriptField_FragentShaderConstants_s mFSConst;
+
+    private ProgramRaster mCullBack;
+    private ProgramRaster mCullFront;
+    private ProgramRaster mCullNone;
+
+    private Allocation mTexTorus;
+    private Allocation mTexOpaque;
+    private Allocation mTexTransparent;
+    private Allocation mTexChecker;
+
+    private Mesh mMbyNMesh;
+    private Mesh mTorus;
+
+    Font mFontSans;
+    Font mFontSerif;
+    Font mFontSerifBold;
+    Font mFontSerifItalic;
+    Font mFontSerifBoldItalic;
+    Font mFontMono;
+    private Allocation mTextAlloc;
+
+    private ScriptC_rsrenderstates mScript;
+
+    private final BitmapFactory.Options mOptionsARGB = new BitmapFactory.Options();
+
+    int mMode;
+    int mMaxModes;
+
+    public void onActionDown(int x, int y) {
+        mMode ++;
+        mMode = mMode % mMaxModes;
+        mScript.set_gDisplayMode(mMode);
+    }
+
+    private Mesh getMbyNMesh(float width, float height, int wResolution, int hResolution) {
+
+        Mesh.TriangleMeshBuilder tmb = new Mesh.TriangleMeshBuilder(mRS,
+                                           2, Mesh.TriangleMeshBuilder.TEXTURE_0);
+
+        for (int y = 0; y <= hResolution; y++) {
+            final float normalizedY = (float)y / hResolution;
+            final float yOffset = (normalizedY - 0.5f) * height;
+            for (int x = 0; x <= wResolution; x++) {
+                float normalizedX = (float)x / wResolution;
+                float xOffset = (normalizedX - 0.5f) * width;
+                tmb.setTexture(normalizedX, normalizedY);
+                tmb.addVertex(xOffset, yOffset);
+             }
+        }
+
+        for (int y = 0; y < hResolution; y++) {
+            final int curY = y * (wResolution + 1);
+            final int belowY = (y + 1) * (wResolution + 1);
+            for (int x = 0; x < wResolution; x++) {
+                int curV = curY + x;
+                int belowV = belowY + x;
+                tmb.addTriangle(curV, belowV, curV + 1);
+                tmb.addTriangle(belowV, belowV + 1, curV + 1);
+            }
+        }
+
+        return tmb.create(true);
+    }
+
+    private void initProgramStore() {
+        // Use stock the stock program store object
+        mProgStoreBlendNoneDepth = ProgramStore.BLEND_NONE_DEPTH_TEST(mRS);
+        mProgStoreBlendNone = ProgramStore.BLEND_NONE_DEPTH_NO_DEPTH(mRS);
+
+        // Create a custom program store
+        ProgramStore.Builder builder = new ProgramStore.Builder(mRS);
+        builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+        builder.setBlendFunc(ProgramStore.BlendSrcFunc.SRC_ALPHA,
+                             ProgramStore.BlendDstFunc.ONE_MINUS_SRC_ALPHA);
+        builder.setDitherEnable(false);
+        builder.setDepthMask(false);
+        mProgStoreBlendAlpha = builder.create();
+
+        mProgStoreBlendAdd = ProgramStore.BLEND_ADD_DEPTH_NO_DEPTH(mRS);
+
+        mScript.set_gProgStoreBlendNoneDepth(mProgStoreBlendNoneDepth);
+        mScript.set_gProgStoreBlendNone(mProgStoreBlendNone);
+        mScript.set_gProgStoreBlendAlpha(mProgStoreBlendAlpha);
+        mScript.set_gProgStoreBlendAdd(mProgStoreBlendAdd);
+    }
+
+    private void initProgramFragment() {
+
+        ProgramFragment.Builder texBuilder = new ProgramFragment.Builder(mRS);
+        texBuilder.setTexture(ProgramFragment.Builder.EnvMode.REPLACE,
+                              ProgramFragment.Builder.Format.RGBA, 0);
+        mProgFragmentTexture = texBuilder.create();
+        mProgFragmentTexture.bindSampler(mLinearClamp, 0);
+
+        ProgramFragment.Builder colBuilder = new ProgramFragment.Builder(mRS);
+        colBuilder.setVaryingColor(false);
+        mProgFragmentColor = colBuilder.create();
+
+        mScript.set_gProgFragmentColor(mProgFragmentColor);
+        mScript.set_gProgFragmentTexture(mProgFragmentTexture);
+    }
+
+    private void initProgramVertex() {
+        ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS);
+        mProgVertex = pvb.create();
+
+        mPVA = new ProgramVertex.MatrixAllocation(mRS);
+        mProgVertex.bindAllocation(mPVA);
+        mPVA.setupOrthoWindow(mWidth, mHeight);
+
+        mScript.set_gProgVertex(mProgVertex);
+    }
+
+    private void initCustomShaders() {
+        mVSConst = new ScriptField_VertexShaderConstants_s(mRS, 1);
+        mFSConst = new ScriptField_FragentShaderConstants_s(mRS, 1);
+
+        mScript.bind_gVSConstants(mVSConst);
+        mScript.bind_gFSConstants(mFSConst);
+
+        // Initialize the shader builder
+        ProgramVertex.ShaderBuilder pvbCustom = new ProgramVertex.ShaderBuilder(mRS);
+        // Specify the resource that contains the shader string
+        pvbCustom.setShader(mRes, R.raw.shaderv);
+        // Use a script field to spcify the input layout
+        pvbCustom.addInput(ScriptField_VertexShaderInputs_s.createElement(mRS));
+        // Define the constant input layout
+        pvbCustom.addConstant(mVSConst.getAllocation().getType());
+        mProgVertexCustom = pvbCustom.create();
+        // Bind the source of constant data
+        mProgVertexCustom.bindConstants(mVSConst.getAllocation(), 0);
+
+        ProgramFragment.ShaderBuilder pfbCustom = new ProgramFragment.ShaderBuilder(mRS);
+        // Specify the resource that contains the shader string
+        pfbCustom.setShader(mRes, R.raw.shaderf);
+        //Tell the builder how many textures we have
+        pfbCustom.setTextureCount(1);
+        // Define the constant input layout
+        pfbCustom.addConstant(mFSConst.getAllocation().getType());
+        mProgFragmentCustom = pfbCustom.create();
+        // Bind the source of constant data
+        mProgFragmentCustom.bindConstants(mFSConst.getAllocation(), 0);
+
+        pfbCustom = new ProgramFragment.ShaderBuilder(mRS);
+        pfbCustom.setShader(mRes, R.raw.multitexf);
+        pfbCustom.setTextureCount(3);
+        mProgFragmentMultitex = pfbCustom.create();
+
+        mScript.set_gProgVertexCustom(mProgVertexCustom);
+        mScript.set_gProgFragmentCustom(mProgFragmentCustom);
+        mScript.set_gProgFragmentMultitex(mProgFragmentMultitex);
+    }
+
+    private Allocation loadTextureRGB(int id) {
+        final Allocation allocation = Allocation.createFromBitmapResource(mRS, mRes,
+                id, Element.RGB_565(mRS), true);
+        allocation.uploadToTexture(0);
+        return allocation;
+    }
+
+    private Allocation loadTextureARGB(int id) {
+        Bitmap b = BitmapFactory.decodeResource(mRes, id, mOptionsARGB);
+        final Allocation allocation = Allocation.createFromBitmap(mRS, b, Element.RGBA_8888(mRS), true);
+        allocation.uploadToTexture(0);
+        return allocation;
+    }
+
+    private void loadImages() {
+        mTexTorus = loadTextureRGB(R.drawable.torusmap);
+        mTexOpaque = loadTextureRGB(R.drawable.data);
+        mTexTransparent = loadTextureARGB(R.drawable.leaf);
+        mTexChecker = loadTextureRGB(R.drawable.checker);
+
+        mScript.set_gTexTorus(mTexTorus);
+        mScript.set_gTexOpaque(mTexOpaque);
+        mScript.set_gTexTransparent(mTexTransparent);
+        mScript.set_gTexChecker(mTexChecker);
+    }
+
+    private void initFonts() {
+        // Sans font by family name
+        mFontSans = Font.createFromFamily(mRS, mRes, "sans-serif", Font.Style.NORMAL, 8);
+        // Create font by file name
+        mFontSerif = Font.create(mRS, mRes, "DroidSerif-Regular.ttf", 8);
+        // Create fonts by family and style
+        mFontSerifBold = Font.createFromFamily(mRS, mRes, "serif", Font.Style.BOLD, 8);
+        mFontSerifItalic = Font.createFromFamily(mRS, mRes, "serif", Font.Style.ITALIC, 8);
+        mFontSerifBoldItalic = Font.createFromFamily(mRS, mRes, "serif", Font.Style.BOLD_ITALIC, 8);
+        mFontMono = Font.createFromFamily(mRS, mRes, "mono", Font.Style.NORMAL, 8);
+
+        mTextAlloc = Allocation.createFromString(mRS, "String from allocation");
+
+        mScript.set_gFontSans(mFontSans);
+        mScript.set_gFontSerif(mFontSerif);
+        mScript.set_gFontSerifBold(mFontSerifBold);
+        mScript.set_gFontSerifItalic(mFontSerifItalic);
+        mScript.set_gFontSerifBoldItalic(mFontSerifBoldItalic);
+        mScript.set_gFontMono(mFontMono);
+        mScript.set_gTextAlloc(mTextAlloc);
+    }
+
+    private void initMesh() {
+        mMbyNMesh = getMbyNMesh(256, 256, 10, 10);
+        mScript.set_gMbyNMesh(mMbyNMesh);
+
+        FileA3D model = FileA3D.createFromResource(mRS, mRes, R.raw.torus);
+        FileA3D.IndexEntry entry = model.getIndexEntry(0);
+        if(entry == null || entry.getClassID() != FileA3D.ClassID.MESH) {
+            Log.e("rs", "could not load model");
+        }
+        else {
+            mTorus = (Mesh)entry.getObject();
+            mScript.set_gTorusMesh(mTorus);
+        }
+    }
+
+    private void initSamplers() {
+        Sampler.Builder bs = new Sampler.Builder(mRS);
+        bs.setMin(Sampler.Value.LINEAR);
+        bs.setMag(Sampler.Value.LINEAR);
+        bs.setWrapS(Sampler.Value.WRAP);
+        bs.setWrapT(Sampler.Value.WRAP);
+        mLinearWrap = bs.create();
+
+        mLinearClamp = Sampler.CLAMP_LINEAR(mRS);
+        mNearestClamp = Sampler.CLAMP_NEAREST(mRS);
+        mMipLinearWrap = Sampler.WRAP_LINEAR_MIP_LINEAR(mRS);
+
+        bs = new Sampler.Builder(mRS);
+        bs.setMin(Sampler.Value.LINEAR_MIP_LINEAR);
+        bs.setMag(Sampler.Value.LINEAR);
+        bs.setWrapS(Sampler.Value.WRAP);
+        bs.setWrapT(Sampler.Value.WRAP);
+        bs.setAnisotropy(8.0f);
+        mMipLinearAniso8 = bs.create();
+        bs.setAnisotropy(15.0f);
+        mMipLinearAniso15 = bs.create();
+
+        mScript.set_gLinearClamp(mLinearClamp);
+        mScript.set_gLinearWrap(mLinearWrap);
+        mScript.set_gMipLinearWrap(mMipLinearWrap);
+        mScript.set_gMipLinearAniso8(mMipLinearAniso8);
+        mScript.set_gMipLinearAniso15(mMipLinearAniso15);
+        mScript.set_gNearestClamp(mNearestClamp);
+    }
+
+    private void initProgramRaster() {
+        mCullBack = ProgramRaster.CULL_BACK(mRS);
+        mCullFront = ProgramRaster.CULL_FRONT(mRS);
+        mCullNone = ProgramRaster.CULL_NONE(mRS);
+
+        mScript.set_gCullBack(mCullBack);
+        mScript.set_gCullFront(mCullFront);
+        mScript.set_gCullNone(mCullNone);
+    }
+
+    private void initRS() {
+
+        mScript = new ScriptC_rsrenderstates(mRS, mRes, R.raw.rsrenderstates, true);
+
+        initSamplers();
+        initProgramStore();
+        initProgramFragment();
+        initProgramVertex();
+        initFonts();
+        loadImages();
+        initMesh();
+        initProgramRaster();
+        initCustomShaders();
+
+        mRS.contextBindRootScript(mScript);
+    }
+}
+
+
+
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesView.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesView.java
new file mode 100644
index 0000000..5548de3
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesView.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.samples;
+
+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 RsRenderStatesView extends RSSurfaceView {
+
+    public RsRenderStatesView(Context context) {
+        super(context);
+        //setFocusable(true);
+    }
+
+    private RenderScriptGL mRS;
+    private RsRenderStatesRS 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 RsRenderStatesRS();
+            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 = false;
+        int act = ev.getAction();
+        if (act == ev.ACTION_DOWN) {
+            mRender.onActionDown((int)ev.getX(), (int)ev.getY());
+            ret = true;
+        }
+
+        return ret;
+    }
+}
+
+
diff --git a/libs/rs/java/Samples/src/com/android/samples/rslist.rs b/libs/rs/java/Samples/src/com/android/samples/rslist.rs
new file mode 100644
index 0000000..3c3f463
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/rslist.rs
@@ -0,0 +1,73 @@
+// 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.samples)
+
+#include "rs_graphics.rsh"
+
+float gDY;
+
+rs_font gItalic;
+
+typedef struct ListAllocs_s {
+    rs_allocation text;
+} ListAllocs;
+
+ListAllocs *gList;
+
+#pragma rs export_var(gDY, gItalic, gList)
+
+void init() {
+    gDY = 0.0f;
+}
+
+int textPos = 0;
+
+int root(int launchID) {
+
+    rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    rsgClearDepth(1.0f);
+
+    textPos -= (int)gDY*2;
+    gDY *= 0.95;
+
+    rsgFontColor(0.9f, 0.9f, 0.9f, 1.0f);
+    rsgBindFont(gItalic);
+    color(0.2, 0.2, 0.2, 0);
+
+    rs_allocation listAlloc = rsGetAllocation(gList);
+    int allocSize = rsAllocationGetDimX(listAlloc);
+
+    int width = rsgGetWidth();
+    int height = rsgGetHeight();
+
+    int itemHeight = 80;
+    int currentYPos = itemHeight + textPos;
+
+    for(int i = 0; i < allocSize; i ++) {
+        if(currentYPos - itemHeight > height) {
+            break;
+        }
+
+        if(currentYPos > 0) {
+            rsgDrawRect(0, currentYPos - 1, width, currentYPos, 0);
+            rsgDrawText(gList[i].text, 30, currentYPos - 32);
+        }
+        currentYPos += itemHeight;
+    }
+
+    return 10;
+}
diff --git a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
new file mode 100644
index 0000000..77384ef
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
@@ -0,0 +1,595 @@
+// 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.samples)
+
+#include "rs_graphics.rsh"
+#include "shader_def.rsh"
+
+rs_program_vertex gProgVertex;
+rs_program_fragment gProgFragmentColor;
+rs_program_fragment gProgFragmentTexture;
+
+rs_program_store gProgStoreBlendNoneDepth;
+rs_program_store gProgStoreBlendNone;
+rs_program_store gProgStoreBlendAlpha;
+rs_program_store gProgStoreBlendAdd;
+
+rs_allocation gTexOpaque;
+rs_allocation gTexTorus;
+rs_allocation gTexTransparent;
+rs_allocation gTexChecker;
+
+rs_mesh gMbyNMesh;
+rs_mesh gTorusMesh;
+
+rs_font gFontSans;
+rs_font gFontSerif;
+rs_font gFontSerifBold;
+rs_font gFontSerifItalic;
+rs_font gFontSerifBoldItalic;
+rs_font gFontMono;
+rs_allocation gTextAlloc;
+
+int gDisplayMode;
+
+rs_sampler gLinearClamp;
+rs_sampler gLinearWrap;
+rs_sampler gMipLinearWrap;
+rs_sampler gMipLinearAniso8;
+rs_sampler gMipLinearAniso15;
+rs_sampler gNearestClamp;
+
+rs_program_raster gCullBack;
+rs_program_raster gCullFront;
+rs_program_raster gCullNone;
+
+// Custom vertex shader compunents
+VertexShaderConstants *gVSConstants;
+FragentShaderConstants *gFSConstants;
+// Export these out to easily set the inputs to shader
+VertexShaderInputs *gVSInputs;
+// Custom shaders we use for lighting
+rs_program_vertex gProgVertexCustom;
+rs_program_fragment gProgFragmentCustom;
+rs_program_fragment gProgFragmentMultitex;
+
+#pragma rs export_var(gProgVertex, gProgFragmentColor, gProgFragmentTexture)
+#pragma rs export_var(gProgStoreBlendNoneDepth, gProgStoreBlendNone, gProgStoreBlendAlpha, gProgStoreBlendAdd)
+#pragma rs export_var(gTexOpaque, gTexTorus, gTexTransparent, gTexChecker)
+#pragma rs export_var(gMbyNMesh, gTorusMesh)
+#pragma rs export_var(gFontSans, gFontSerif, gFontSerifBold, gFontSerifItalic, gFontSerifBoldItalic, gFontMono, gTextAlloc)
+#pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gMipLinearAniso8, gMipLinearAniso15, gNearestClamp)
+#pragma rs export_var(gCullBack, gCullFront, gCullNone)
+#pragma rs export_var(gVSConstants, gFSConstants, gVSInputs, gProgVertexCustom, gProgFragmentCustom, gProgFragmentMultitex)
+
+//What we are showing
+#pragma rs export_var(gDisplayMode)
+
+float gDt = 0;
+
+void init() {
+}
+
+void displayFontSamples() {
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    int yPos = 100;
+    rsgBindFont(gFontSans);
+    rsgDrawText("Sans font sample", 30, yPos);
+    yPos += 30;
+    rsgFontColor(0.5f, 0.9f, 0.5f, 1.0f);
+    rsgBindFont(gFontSerif);
+    rsgDrawText("Serif font sample", 30, yPos);
+    yPos += 30;
+    rsgFontColor(0.7f, 0.7f, 0.7f, 1.0f);
+    rsgBindFont(gFontSerifBold);
+    rsgDrawText("Serif Bold font sample", 30, yPos);
+    yPos += 30;
+    rsgFontColor(0.5f, 0.5f, 0.9f, 1.0f);
+    rsgBindFont(gFontSerifItalic);
+    rsgDrawText("Serif Italic font sample", 30, yPos);
+    yPos += 30;
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontSerifBoldItalic);
+    rsgDrawText("Serif Bold Italic font sample", 30, yPos);
+    yPos += 30;
+    rsgBindFont(gFontMono);
+    rsgDrawText("Monospace font sample", 30, yPos);
+    yPos += 50;
+
+    // Now use text metrics to center the text
+    uint width = rsgGetWidth();
+    uint height = rsgGetHeight();
+    int left = 0, right = 0, top = 0, bottom = 0;
+
+    rsgFontColor(0.9f, 0.9f, 0.95f, 1.0f);
+    rsgBindFont(gFontSerifBoldItalic);
+
+    rsgMeasureText(gTextAlloc, &left, &right, &top, &bottom);
+    int centeredPos = width / 2 - (right - left) / 2;
+    rsgDrawText(gTextAlloc, centeredPos, yPos);
+    yPos += 30;
+
+    const char* text = "Centered Text Sample";
+    rsgMeasureText(text, &left, &right, &top, &bottom);
+    centeredPos = width / 2 - (right - left) / 2;
+    rsgDrawText(text, centeredPos, yPos);
+    yPos += 30;
+
+    rsgBindFont(gFontSans);
+    text = "More Centered Text Samples";
+    rsgMeasureText(text, &left, &right, &top, &bottom);
+    centeredPos = width / 2 - (right - left) / 2;
+    rsgDrawText(text, centeredPos, yPos);
+    yPos += 30;
+
+    // Now draw bottom and top right aligned text
+    text = "Top-right aligned text";
+    rsgMeasureText(text, &left, &right, &top, &bottom);
+    rsgDrawText(text, width - right, top);
+
+    text = "Top-left";
+    rsgMeasureText(text, &left, &right, &top, &bottom);
+    rsgDrawText(text, -left, top);
+
+    text = "Bottom-right aligned text";
+    rsgMeasureText(text, &left, &right, &top, &bottom);
+    rsgDrawText(text, width - right, height + bottom);
+
+}
+
+void bindProgramVertexOrtho() {
+    // Default vertex sahder
+    rsgBindProgramVertex(gProgVertex);
+    // Setup the projectioni matrix
+    rs_matrix4x4 proj;
+    rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -500, 500);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+}
+
+void displayShaderSamples() {
+    bindProgramVertexOrtho();
+    rs_matrix4x4 matrix;
+    rsMatrixLoadIdentity(&matrix);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNone);
+    rsgBindProgramFragment(gProgFragmentTexture);
+    rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp);
+    rsgBindTexture(gProgFragmentTexture, 0, gTexOpaque);
+
+    float startX = 0, startY = 0;
+    float width = 256, height = 256;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 1,
+                         startX + width, startY + height, 0, 1, 1,
+                         startX + width, startY, 0, 1, 0);
+
+    startX = 200; startY = 0;
+    width = 128; height = 128;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 1,
+                         startX + width, startY + height, 0, 1, 1,
+                         startX + width, startY, 0, 1, 0);
+
+    rsgBindProgramStore(gProgStoreBlendAlpha);
+    rsgBindTexture(gProgFragmentTexture, 0, gTexTransparent);
+    startX = 0; startY = 200;
+    width = 128; height = 128;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 1,
+                         startX + width, startY + height, 0, 1, 1,
+                         startX + width, startY, 0, 1, 0);
+
+    // Fragment program with simple color
+    rsgBindProgramFragment(gProgFragmentColor);
+    rsgProgramFragmentConstantColor(gProgFragmentColor, 0.9, 0.3, 0.3, 1);
+    rsgDrawRect(200, 300, 350, 450, 0);
+    rsgProgramFragmentConstantColor(gProgFragmentColor, 0.3, 0.9, 0.3, 1);
+    rsgDrawRect(50, 400, 400, 600, 0);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    rsgDrawText("Texture shader", 10, 50);
+    rsgDrawText("Alpha-blended texture shader", 10, 280);
+    rsgDrawText("Flat color shader", 100, 450);
+}
+
+void displayBlendingSamples() {
+    int i;
+
+    bindProgramVertexOrtho();
+    rs_matrix4x4 matrix;
+    rsMatrixLoadIdentity(&matrix);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    rsgBindProgramFragment(gProgFragmentColor);
+
+    rsgBindProgramStore(gProgStoreBlendNone);
+    for(i = 0; i < 3; i ++) {
+        float iPlusOne = (float)(i + 1);
+        rsgProgramFragmentConstantColor(gProgFragmentColor,
+                                        0.1f*iPlusOne, 0.2f*iPlusOne, 0.3f*iPlusOne, 1);
+        float yPos = 150 * (float)i;
+        rsgDrawRect(0, yPos, 200, yPos + 200, 0);
+    }
+
+    rsgBindProgramStore(gProgStoreBlendAlpha);
+    for(i = 0; i < 3; i ++) {
+        float iPlusOne = (float)(i + 1);
+        rsgProgramFragmentConstantColor(gProgFragmentColor,
+                                        0.2f*iPlusOne, 0.3f*iPlusOne, 0.1f*iPlusOne, 0.5);
+        float yPos = 150 * (float)i;
+        rsgDrawRect(150, yPos, 350, yPos + 200, 0);
+    }
+
+    rsgBindProgramStore(gProgStoreBlendAdd);
+    for(i = 0; i < 3; i ++) {
+        float iPlusOne = (float)(i + 1);
+        rsgProgramFragmentConstantColor(gProgFragmentColor,
+                                        0.3f*iPlusOne, 0.1f*iPlusOne, 0.2f*iPlusOne, 0.5);
+        float yPos = 150 * (float)i;
+        rsgDrawRect(300, yPos, 500, yPos + 200, 0);
+    }
+
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    rsgDrawText("No Blending", 10, 50);
+    rsgDrawText("Alpha Blending", 160, 150);
+    rsgDrawText("Additive Blending", 320, 250);
+
+}
+
+void displayMeshSamples() {
+
+    bindProgramVertexOrtho();
+    rs_matrix4x4 matrix;
+    rsMatrixLoadTranslate(&matrix, 128, 128, 0);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNone);
+    rsgBindProgramFragment(gProgFragmentTexture);
+    rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp);
+    rsgBindTexture(gProgFragmentTexture, 0, gTexOpaque);
+
+    rsgDrawMesh(gMbyNMesh);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    rsgDrawText("User gen 10 by 10 grid mesh", 10, 250);
+}
+
+void displayTextureSamplers() {
+
+    bindProgramVertexOrtho();
+    rs_matrix4x4 matrix;
+    rsMatrixLoadIdentity(&matrix);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNone);
+    rsgBindProgramFragment(gProgFragmentTexture);
+    rsgBindTexture(gProgFragmentTexture, 0, gTexOpaque);
+
+    // Linear clamp
+    rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp);
+    float startX = 0, startY = 0;
+    float width = 300, height = 300;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 1.1,
+                         startX + width, startY + height, 0, 1.1, 1.1,
+                         startX + width, startY, 0, 1.1, 0);
+
+    // Linear Wrap
+    rsgBindSampler(gProgFragmentTexture, 0, gLinearWrap);
+    startX = 0; startY = 300;
+    width = 300; height = 300;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 1.1,
+                         startX + width, startY + height, 0, 1.1, 1.1,
+                         startX + width, startY, 0, 1.1, 0);
+
+    // Nearest
+    rsgBindSampler(gProgFragmentTexture, 0, gNearestClamp);
+    startX = 300; startY = 0;
+    width = 300; height = 300;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 1.1,
+                         startX + width, startY + height, 0, 1.1, 1.1,
+                         startX + width, startY, 0, 1.1, 0);
+
+    rsgBindSampler(gProgFragmentTexture, 0, gMipLinearWrap);
+    startX = 300; startY = 300;
+    width = 300; height = 300;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 1.5,
+                         startX + width, startY + height, 0, 1.5, 1.5,
+                         startX + width, startY, 0, 1.5, 0);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    rsgDrawText("Filtering: linear clamp", 10, 290);
+    rsgDrawText("Filtering: linear wrap", 10, 590);
+    rsgDrawText("Filtering: nearest clamp", 310, 290);
+    rsgDrawText("Filtering: miplinear wrap", 310, 590);
+}
+
+float gTorusRotation = 0;
+
+void displayCullingSamples() {
+    rsgBindProgramVertex(gProgVertex);
+    // Setup the projectioni matrix with 60 degree field of view
+    rs_matrix4x4 proj;
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNoneDepth);
+    rsgBindProgramFragment(gProgFragmentTexture);
+    rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp);
+    rsgBindTexture(gProgFragmentTexture, 0, gTexTorus);
+
+    // Aplly a rotation to our mesh
+    gTorusRotation += 50.0f * gDt;
+    if(gTorusRotation > 360.0f) {
+        gTorusRotation -= 360.0f;
+    }
+
+    rs_matrix4x4 matrix;
+    // Position our model on the screen
+    rsMatrixLoadTranslate(&matrix, -2.0f, 0.0f, -10.0f);
+    rsMatrixRotate(&matrix, gTorusRotation, 1.0f, 0.0f, 0.0f);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+    // Use front face culling
+    rsgBindProgramRaster(gCullFront);
+    rsgDrawMesh(gTorusMesh);
+
+    rsMatrixLoadTranslate(&matrix, 2.0f, 0.0f, -10.0f);
+    rsMatrixRotate(&matrix, gTorusRotation, 1.0f, 0.0f, 0.0f);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+    // Use back face culling
+    rsgBindProgramRaster(gCullBack);
+    rsgDrawMesh(gTorusMesh);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    rsgDrawText("Displaying mesh front/back face culling", 10, rsgGetHeight() - 10);
+}
+
+float gLight0Rotation = 0;
+float gLight1Rotation = 0;
+
+void setupCustomShaderLights() {
+    float4 light0Pos = {-5.0f, 5.0f, -10.0f, 1.0f};
+    float4 light1Pos = {2.0f, 5.0f, 15.0f, 1.0f};
+    float3 light0DiffCol = {0.9f, 0.7f, 0.7f};
+    float3 light0SpecCol = {0.9f, 0.6f, 0.6f};
+    float3 light1DiffCol = {0.5f, 0.5f, 0.9f};
+    float3 light1SpecCol = {0.5f, 0.5f, 0.9f};
+
+    gLight0Rotation += 50.0f * gDt;
+    if(gLight0Rotation > 360.0f) {
+        gLight0Rotation -= 360.0f;
+    }
+    gLight1Rotation -= 50.0f * gDt;
+    if(gLight1Rotation > 360.0f) {
+        gLight1Rotation -= 360.0f;
+    }
+
+    rs_matrix4x4 l0Mat;
+    rsMatrixLoadRotate(&l0Mat, gLight0Rotation, 1.0f, 0.0f, 0.0f);
+    light0Pos = rsMatrixMultiply(&l0Mat, light0Pos);
+    rs_matrix4x4 l1Mat;
+    rsMatrixLoadRotate(&l1Mat, gLight1Rotation, 0.0f, 0.0f, 1.0f);
+    light1Pos = rsMatrixMultiply(&l1Mat, light1Pos);
+
+    // Set light 0 properties
+    gVSConstants->light0_Posision.x = light0Pos.x;
+    gVSConstants->light0_Posision.y = light0Pos.y;
+    gVSConstants->light0_Posision.z = light0Pos.z;
+    gVSConstants->light0_Diffuse = 1.0f;
+    gVSConstants->light0_Specular = 0.5f;
+    gVSConstants->light0_CosinePower = 40.0f;
+    // Set light 1 properties
+    gVSConstants->light1_Posision.x = light1Pos.x;
+    gVSConstants->light1_Posision.y = light1Pos.y;
+    gVSConstants->light1_Posision.z = light1Pos.z;
+    gVSConstants->light1_Diffuse = 1.0f;
+    gVSConstants->light1_Specular = 0.7f;
+    gVSConstants->light1_CosinePower = 50.0f;
+    rsAllocationMarkDirty(rsGetAllocation(gVSConstants));
+
+    // Update fragmetn shader constants
+    // Set light 0 colors
+    gFSConstants->light0_DiffuseColor = light0DiffCol;
+    gFSConstants->light0_SpecularColor = light0SpecCol;
+    // Set light 1 colors
+    gFSConstants->light1_DiffuseColor = light1DiffCol;
+    gFSConstants->light1_SpecularColor = light1SpecCol;
+    rsAllocationMarkDirty(rsGetAllocation(gFSConstants));
+}
+
+void displayCustomShaderSamples() {
+
+    // Update vertex shader constants
+    // Load model matrix
+    // Aplly a rotation to our mesh
+    gTorusRotation += 50.0f * gDt;
+    if(gTorusRotation > 360.0f) {
+        gTorusRotation -= 360.0f;
+    }
+
+    // Position our model on the screen
+    rsMatrixLoadTranslate(&gVSConstants->model, 0.0f, 0.0f, -10.0f);
+    rsMatrixRotate(&gVSConstants->model, gTorusRotation, 1.0f, 0.0f, 0.0f);
+    rsMatrixRotate(&gVSConstants->model, gTorusRotation, 0.0f, 0.0f, 1.0f);
+    // Setup the projectioni matrix
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rsMatrixLoadPerspective(&gVSConstants->proj, 30.0f, aspect, 0.1f, 100.0f);
+    setupCustomShaderLights();
+
+    rsgBindProgramVertex(gProgVertexCustom);
+
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNoneDepth);
+    rsgBindProgramFragment(gProgFragmentCustom);
+    rsgBindSampler(gProgFragmentCustom, 0, gLinearClamp);
+    rsgBindTexture(gProgFragmentCustom, 0, gTexTorus);
+
+    // Use back face culling
+    rsgBindProgramRaster(gCullBack);
+    rsgDrawMesh(gTorusMesh);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    rsgDrawText("Custom shader sample", 10, rsgGetHeight() - 10);
+}
+
+void displayMultitextureSample() {
+    bindProgramVertexOrtho();
+    rs_matrix4x4 matrix;
+    rsMatrixLoadIdentity(&matrix);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNone);
+    rsgBindProgramFragment(gProgFragmentMultitex);
+    rsgBindSampler(gProgFragmentMultitex, 0, gLinearClamp);
+    rsgBindSampler(gProgFragmentMultitex, 1, gLinearWrap);
+    rsgBindSampler(gProgFragmentMultitex, 2, gLinearClamp);
+    rsgBindTexture(gProgFragmentMultitex, 0, gTexChecker);
+    rsgBindTexture(gProgFragmentMultitex, 1, gTexTorus);
+    rsgBindTexture(gProgFragmentMultitex, 2, gTexTransparent);
+
+    float startX = 0, startY = 0;
+    float width = 256, height = 256;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 1,
+                         startX + width, startY + height, 0, 1, 1,
+                         startX + width, startY, 0, 1, 0);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    rsgDrawText("Custom shader with multitexturing", 10, 280);
+}
+
+float gAnisoTime = 0.0f;
+uint anisoMode = 0;
+void displayAnisoSample() {
+
+    gAnisoTime += gDt;
+
+    rsgBindProgramVertex(gProgVertex);
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rs_matrix4x4 proj;
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+
+    rs_matrix4x4 matrix;
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNone);
+    rsgBindProgramFragment(gProgFragmentTexture);
+    rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -10.0f);
+    rsMatrixRotate(&matrix, -80, 1.0f, 0.0f, 0.0f);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    rsgBindProgramRaster(gCullNone);
+
+    rsgBindTexture(gProgFragmentTexture, 0, gTexChecker);
+
+    if(gAnisoTime >= 5.0f) {
+        gAnisoTime = 0.0f;
+        anisoMode ++;
+        anisoMode = anisoMode % 3;
+    }
+
+    if(anisoMode == 0) {
+        rsgBindSampler(gProgFragmentTexture, 0, gMipLinearAniso8);
+    }
+    else if(anisoMode == 1) {
+        rsgBindSampler(gProgFragmentTexture, 0, gMipLinearAniso15);
+    }
+    else {
+        rsgBindSampler(gProgFragmentTexture, 0, gMipLinearWrap);
+    }
+
+    float startX = -15;
+    float startY = -15;
+    float width = 30;
+    float height = 30;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 10,
+                         startX + width, startY + height, 0, 10, 10,
+                         startX + width, startY, 0, 10, 0);
+
+    rsgBindProgramRaster(gCullBack);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    if(anisoMode == 0) {
+        rsgDrawText("Anisotropic filtering 8", 10, 40);
+    }
+    else if(anisoMode == 1) {
+        rsgDrawText("Anisotropic filtering 15", 10, 40);
+    }
+    else {
+        rsgDrawText("Miplinear filtering", 10, 40);
+    }
+}
+
+int root(int launchID) {
+
+    gDt = rsGetDt();
+
+    rsgClearColor(0.2f, 0.2f, 0.2f, 0.0f);
+    rsgClearDepth(1.0f);
+
+    switch(gDisplayMode) {
+    case 0:
+        displayFontSamples();
+        break;
+    case 1:
+        displayShaderSamples();
+        break;
+    case 2:
+        displayBlendingSamples();
+        break;
+    case 3:
+        displayMeshSamples();
+        break;
+    case 4:
+        displayTextureSamplers();
+        break;
+    case 5:
+        displayCullingSamples();
+        break;
+    case 6:
+        displayCustomShaderSamples();
+        break;
+    case 7:
+        displayMultitextureSample();
+        break;
+    case 8:
+        displayAnisoSample();
+        break;
+    }
+
+    return 10;
+}
diff --git a/libs/rs/java/Samples/src/com/android/samples/shader_def.rsh b/libs/rs/java/Samples/src/com/android/samples/shader_def.rsh
new file mode 100644
index 0000000..e3f6206
--- /dev/null
+++ b/libs/rs/java/Samples/src/com/android/samples/shader_def.rsh
@@ -0,0 +1,47 @@
+// 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.samples)
+
+typedef struct VertexShaderConstants_s {
+    rs_matrix4x4 model;
+    rs_matrix4x4 proj;
+    float3 light0_Posision;
+    float light0_Diffuse;
+    float light0_Specular;
+    float light0_CosinePower;
+
+    float3 light1_Posision;
+    float light1_Diffuse;
+    float light1_Specular;
+    float light1_CosinePower;
+} VertexShaderConstants;
+
+typedef struct FragentShaderConstants_s {
+    float3 light0_DiffuseColor;
+    float3 light0_SpecularColor;
+
+    float3 light1_DiffuseColor;
+    float3 light1_SpecularColor;
+
+} FragentShaderConstants;
+
+typedef struct VertexShaderInputs_s {
+    float4 position;
+    float3 normal;
+    float2 texture0;
+} VertexShaderInputs;
+
diff --git a/libs/rs/java/tests/Android.mk b/libs/rs/java/tests/Android.mk
new file mode 100644
index 0000000..6c992d5
--- /dev/null
+++ b/libs/rs/java/tests/Android.mk
@@ -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.
+#
+
+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_PACKAGE_NAME := RSTest
+
+include $(BUILD_PACKAGE)
+
+endif
diff --git a/libs/rs/java/tests/AndroidManifest.xml b/libs/rs/java/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b660398
--- /dev/null
+++ b/libs/rs/java/tests/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.rs.test">
+    <application 
+        android:label="_RS_Test"
+        android:icon="@drawable/test_pattern">
+        <activity android:name="RSTest"
+                  android:screenOrientation="portrait">
+            <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/tests/res/drawable/test_pattern.png b/libs/rs/java/tests/res/drawable/test_pattern.png
new file mode 100644
index 0000000..e7d1455
--- /dev/null
+++ b/libs/rs/java/tests/res/drawable/test_pattern.png
Binary files differ
diff --git a/libs/rs/java/tests/src/com/android/rs/test/RSTest.java b/libs/rs/java/tests/src/com/android/rs/test/RSTest.java
new file mode 100644
index 0000000..c264649
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/RSTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.rs.test;
+
+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 RSTest 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 RSTestView 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 RSTestView(this);
+        setContentView(mView);
+    }
+
+    @Override
+    protected void onResume() {
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity loses focus
+        super.onResume();
+        mView.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity loses focus
+        super.onPause();
+        mView.onPause();
+    }
+
+    static void log(String message) {
+        if (LOG_ENABLED) {
+            Log.v(LOG_TAG, message);
+        }
+    }
+
+
+}
diff --git a/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
new file mode 100644
index 0000000..835dea2
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
@@ -0,0 +1,199 @@
+/*
+ * 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.rs.test;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.util.Timer;
+import java.util.TimerTask;
+
+
+public class RSTestCore {
+    int mWidth;
+    int mHeight;
+
+    public RSTestCore() {
+    }
+
+    private Resources mRes;
+    private RenderScriptGL mRS;
+
+    private Font mFont;
+    ScriptField_ListAllocs_s mListAllocs;
+    int mLastX;
+    int mLastY;
+    private ScriptC_rslist mScript;
+
+    private ArrayList<UnitTest> unitTests;
+    private ListIterator<UnitTest> test_iter;
+    private UnitTest activeTest;
+    private boolean stopTesting;
+
+    /* Periodic timer for ensuring future tests get scheduled */
+    private Timer mTimer;
+    public static final int RS_TIMER_PERIOD = 100;
+
+    public void init(RenderScriptGL rs, Resources res, int width, int height) {
+        mRS = rs;
+        mRes = res;
+        mWidth = width;
+        mHeight = height;
+        stopTesting = false;
+
+        mScript = new ScriptC_rslist(mRS, mRes, R.raw.rslist, true);
+
+        unitTests = new ArrayList<UnitTest>();
+
+        unitTests.add(new UT_primitives(this, mRes));
+        unitTests.add(new UT_fp_mad(this, mRes));
+        /*
+        unitTests.add(new UnitTest(null, "<Pass>", 1));
+        unitTests.add(new UnitTest());
+        unitTests.add(new UnitTest(null, "<Fail>", -1));
+
+        for (int i = 0; i < 20; i++) {
+            unitTests.add(new UnitTest(null, "<Pass>", 1));
+        }
+        */
+
+        UnitTest [] uta = new UnitTest[unitTests.size()];
+        uta = unitTests.toArray(uta);
+
+        mListAllocs = new ScriptField_ListAllocs_s(mRS, uta.length);
+        for (int i = 0; i < uta.length; i++) {
+            ScriptField_ListAllocs_s.Item listElem = new ScriptField_ListAllocs_s.Item();
+            listElem.text = Allocation.createFromString(mRS, uta[i].name);
+            listElem.result = uta[i].result;
+            mListAllocs.set(listElem, i, false);
+            uta[i].setItem(listElem);
+        }
+
+        mListAllocs.copyAll();
+
+        mScript.bind_gList(mListAllocs);
+
+        mFont = Font.createFromFamily(mRS, mRes, "serif", Font.Style.BOLD, 8);
+        mScript.set_gFont(mFont);
+
+        mRS.contextBindRootScript(mScript);
+
+        test_iter = unitTests.listIterator();
+        refreshTestResults(); /* Kick off the first test */
+
+        TimerTask pTask = new TimerTask() {
+            public void run() {
+                refreshTestResults();
+            }
+        };
+
+        mTimer = new Timer();
+        mTimer.schedule(pTask, RS_TIMER_PERIOD, RS_TIMER_PERIOD);
+    }
+
+    public void checkAndRunNextTest() {
+        if (activeTest != null) {
+            if (!activeTest.isAlive()) {
+                /* Properly clean up on our last test */
+                try {
+                    activeTest.join();
+                }
+                catch (InterruptedException e) {
+                }
+                activeTest = null;
+            }
+        }
+
+        if (!stopTesting && activeTest == null) {
+            if (test_iter.hasNext()) {
+                activeTest = test_iter.next();
+                activeTest.start();
+                /* This routine will only get called once when a new test
+                 * should start running. The message handler in UnitTest.java
+                 * ensures this. */
+            }
+            else {
+                if (mTimer != null) {
+                    mTimer.cancel();
+                    mTimer.purge();
+                    mTimer = null;
+                }
+            }
+        }
+    }
+
+    public void refreshTestResults() {
+        checkAndRunNextTest();
+
+        if (mListAllocs != null && mScript != null && mRS != null) {
+            mListAllocs.copyAll();
+
+            mScript.bind_gList(mListAllocs);
+            mRS.contextBindRootScript(mScript);
+        }
+    }
+
+    public void cleanup() {
+        stopTesting = true;
+        UnitTest t = activeTest;
+
+        /* Stop periodic refresh of testing */
+        if (mTimer != null) {
+            mTimer.cancel();
+            mTimer.purge();
+            mTimer = null;
+        }
+
+        /* Wait to exit until we finish the current test */
+        if (t != null) {
+            try {
+                t.join();
+            }
+            catch (InterruptedException e) {
+            }
+            t = null;
+        }
+
+    }
+
+    public void newTouchPosition(float x, float y, float pressure, int id) {
+    }
+
+    public void onActionDown(int x, int y) {
+        mScript.set_gDY(0.0f);
+        mLastX = x;
+        mLastY = y;
+        refreshTestResults();
+    }
+
+    public void onActionMove(int x, int y) {
+        int dx = mLastX - x;
+        int dy = mLastY - y;
+
+        if (Math.abs(dy) <= 2) {
+            dy = 0;
+        }
+
+        mScript.set_gDY(dy);
+
+        mLastX = x;
+        mLastY = y;
+        refreshTestResults();
+    }
+}
diff --git a/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java b/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java
new file mode 100644
index 0000000..b811d48
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java
@@ -0,0 +1,95 @@
+/*
+ * 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.rs.test;
+
+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 RSTestView extends RSSurfaceView {
+
+    public RSTestView(Context context) {
+        super(context);
+        //setFocusable(true);
+    }
+
+    private RenderScriptGL mRS;
+    private RSTestCore mRender;
+
+    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+        super.surfaceChanged(holder, format, w, h);
+        if (mRS == null) {
+            mRS = createRenderScript(false);
+            mRS.contextSetSurface(w, h, holder.getSurface());
+            mRender = new RSTestCore();
+            mRender.init(mRS, getResources(), w, h);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        if(mRS != null) {
+            mRender.cleanup();
+            mRS = null;
+            destroyRenderScript();
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event)
+    {
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev)
+    {
+        boolean ret = false;
+        int act = ev.getAction();
+        if (act == ev.ACTION_DOWN) {
+            mRender.onActionDown((int)ev.getX(), (int)ev.getY());
+            ret = true;
+        }
+        else if (act == ev.ACTION_MOVE) {
+            mRender.onActionMove((int)ev.getX(), (int)ev.getY());
+            ret = true;
+        }
+
+        return ret;
+    }
+}
+
+
diff --git a/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java b/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java
new file mode 100644
index 0000000..9d57e90
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.rs.test;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+
+public class UT_fp_mad extends UnitTest {
+    private Resources mRes;
+
+    protected UT_fp_mad(RSTestCore rstc, Resources res) {
+        super(rstc, "Fp_Mad");
+        mRes = res;
+    }
+
+    public void run() {
+        RenderScript pRS = RenderScript.create();
+        ScriptC_fp_mad s = new ScriptC_fp_mad(pRS, mRes, R.raw.fp_mad, true);
+        pRS.mMessageCallback = mRsMessage;
+        s.invoke_fp_mad_test(0, 0);
+        pRS.finish();
+        waitForMessage();
+        pRS.destroy();
+    }
+}
+
diff --git a/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java b/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java
new file mode 100644
index 0000000..fb355dd
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.rs.test;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+
+public class UT_primitives extends UnitTest {
+    private Resources mRes;
+
+    protected UT_primitives(RSTestCore rstc, Resources res) {
+        super(rstc, "Primitives");
+        mRes = res;
+    }
+
+    public void run() {
+        RenderScript pRS = RenderScript.create();
+        ScriptC_primitives s = new ScriptC_primitives(pRS, mRes, R.raw.primitives, true);
+        pRS.mMessageCallback = mRsMessage;
+        s.invoke_primitives_test(0, 0);
+        pRS.finish();
+        waitForMessage();
+        pRS.destroy();
+    }
+}
+
diff --git a/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java b/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java
new file mode 100644
index 0000000..c9d88a6
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java
@@ -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.
+ */
+
+package com.android.rs.test;
+import android.renderscript.RenderScript.RSMessage;
+import android.util.Log;
+
+public class UnitTest extends Thread {
+    public String name;
+    public int result;
+    private ScriptField_ListAllocs_s.Item mItem;
+    private RSTestCore mRSTC;
+    private boolean msgHandled;
+
+    /* These constants must match those in shared.rsh */
+    public static final int RS_MSG_TEST_PASSED = 100;
+    public static final int RS_MSG_TEST_FAILED = 101;
+
+    private static int numTests = 0;
+    public int testID;
+
+    protected UnitTest(RSTestCore rstc, String n, int initResult) {
+        super();
+        mRSTC = rstc;
+        name = n;
+        msgHandled = false;
+        result = initResult;
+        testID = numTests++;
+    }
+
+    protected UnitTest(RSTestCore rstc, String n) {
+        this(rstc, n, 0);
+    }
+
+    protected UnitTest(RSTestCore rstc) {
+        this (rstc, "<Unknown>");
+    }
+
+    protected UnitTest() {
+        this (null);
+    }
+
+    protected RSMessage mRsMessage = new RSMessage() {
+        public void run() {
+            switch (mID) {
+                case RS_MSG_TEST_PASSED:
+                    result = 1;
+                    break;
+                case RS_MSG_TEST_FAILED:
+                    result = -1;
+                    break;
+                default:
+                    android.util.Log.v("RenderScript", "Unit test got unexpected message");
+                    return;
+            }
+
+            if (mItem != null) {
+                mItem.result = result;
+                msgHandled = true;
+                try {
+                    mRSTC.refreshTestResults();
+                }
+                catch (IllegalStateException e) {
+                    /* Ignore the case where our message receiver has been
+                       disconnected. This happens when we leave the application
+                       before it finishes running all of the unit tests. */
+                }
+            }
+        }
+    };
+
+    public void waitForMessage() {
+        while (!msgHandled) {
+            yield();
+        }
+    }
+
+    public void setItem(ScriptField_ListAllocs_s.Item item) {
+        mItem = item;
+    }
+
+    public void run() {
+        /* This method needs to be implemented for each subclass */
+        if (mRSTC != null) {
+            mRSTC.refreshTestResults();
+        }
+    }
+}
+
diff --git a/libs/rs/java/tests/src/com/android/rs/test/fp_mad.rs b/libs/rs/java/tests/src/com/android/rs/test/fp_mad.rs
new file mode 100644
index 0000000..dfd77e6
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/fp_mad.rs
@@ -0,0 +1,176 @@
+#include "shared.rsh"
+
+const int TEST_COUNT = 1;
+
+#pragma rs export_var(g_results)
+#pragma rs export_func(fp_mad_test)
+
+static float data_f1[1025];
+static float4 data_f4[1025];
+
+static void test_mad4(uint32_t index) {
+    start();
+
+    float total = 0;
+    // Do ~1 billion ops
+    for (int ct=0; ct < 1000 * (1000 / 80); ct++) {
+        for (int i=0; i < (1000); i++) {
+            data_f4[i] = (data_f4[i] * 0.02f +
+                          data_f4[i+1] * 0.04f +
+                          data_f4[i+2] * 0.05f +
+                          data_f4[i+3] * 0.1f +
+                          data_f4[i+4] * 0.2f +
+                          data_f4[i+5] * 0.2f +
+                          data_f4[i+6] * 0.1f +
+                          data_f4[i+7] * 0.05f +
+                          data_f4[i+8] * 0.04f +
+                          data_f4[i+9] * 0.02f + 1.f);
+        }
+    }
+
+    float time = end(index);
+    rsDebug("fp_mad4 M ops", 1000.f / time);
+}
+
+static void test_mad(uint32_t index) {
+    start();
+
+    float total = 0;
+    // Do ~1 billion ops
+    for (int ct=0; ct < 1000 * (1000 / 20); ct++) {
+        for (int i=0; i < (1000); i++) {
+            data_f1[i] = (data_f1[i] * 0.02f +
+                          data_f1[i+1] * 0.04f +
+                          data_f1[i+2] * 0.05f +
+                          data_f1[i+3] * 0.1f +
+                          data_f1[i+4] * 0.2f +
+                          data_f1[i+5] * 0.2f +
+                          data_f1[i+6] * 0.1f +
+                          data_f1[i+7] * 0.05f +
+                          data_f1[i+8] * 0.04f +
+                          data_f1[i+9] * 0.02f + 1.f);
+        }
+    }
+
+    float time = end(index);
+    rsDebug("fp_mad M ops", 1000.f / time);
+}
+
+static void test_norm(uint32_t index) {
+    start();
+
+    float total = 0;
+    // Do ~10 M ops
+    for (int ct=0; ct < 1000 * 10; ct++) {
+        for (int i=0; i < (1000); i++) {
+            data_f4[i] = normalize(data_f4[i]);
+        }
+    }
+
+    float time = end(index);
+    rsDebug("fp_norm M ops", 10.f / time);
+}
+
+static void test_sincos4(uint32_t index) {
+    start();
+
+    float total = 0;
+    // Do ~10 M ops
+    for (int ct=0; ct < 1000 * 10 / 4; ct++) {
+        for (int i=0; i < (1000); i++) {
+            data_f4[i] = sin(data_f4[i]) * cos(data_f4[i]);
+        }
+    }
+
+    float time = end(index);
+    rsDebug("fp_sincos4 M ops", 10.f / time);
+}
+
+static void test_sincos(uint32_t index) {
+    start();
+
+    float total = 0;
+    // Do ~10 M ops
+    for (int ct=0; ct < 1000 * 10; ct++) {
+        for (int i=0; i < (1000); i++) {
+            data_f1[i] = sin(data_f1[i]) * cos(data_f1[i]);
+        }
+    }
+
+    float time = end(index);
+    rsDebug("fp_sincos M ops", 10.f / time);
+}
+
+static void test_clamp(uint32_t index) {
+    start();
+
+    // Do ~100 M ops
+    for (int ct=0; ct < 1000 * 100; ct++) {
+        for (int i=0; i < (1000); i++) {
+            data_f1[i] = clamp(data_f1[i], -1.f, 1.f);
+        }
+    }
+
+    float time = end(index);
+    rsDebug("fp_clamp M ops", 100.f / time);
+
+    start();
+    // Do ~100 M ops
+    for (ct=0; ct < 1000 * 100; ct++) {
+        for (int i=0; i < (1000); i++) {
+            if (data_f1[i] < -1.f) data_f1[i] = -1.f;
+            if (data_f1[i] > -1.f) data_f1[i] = 1.f;
+        }
+    }
+
+    time = end(index);
+    rsDebug("fp_clamp ref M ops", 100.f / time);
+}
+
+static void test_clamp4(uint32_t index) {
+    start();
+
+    float total = 0;
+    // Do ~100 M ops
+    for (int ct=0; ct < 1000 * 100 /4; ct++) {
+        for (int i=0; i < (1000); i++) {
+            data_f4[i] = clamp(data_f4[i], -1.f, 1.f);
+        }
+    }
+
+    float time = end(index);
+    rsDebug("fp_clamp4 M ops", 100.f / time);
+}
+
+void fp_mad_test(uint32_t index, int test_num) {
+    for (int x=0; x < 1025; x++) {
+        data_f1[x] = (x & 0xf) * 0.1f;
+        data_f4[x].x = (x & 0xf) * 0.1f;
+        data_f4[x].y = (x & 0xf0) * 0.1f;
+        data_f4[x].z = (x & 0x33) * 0.1f;
+        data_f4[x].w = (x & 0x77) * 0.1f;
+    }
+
+    test_mad4(index);
+    test_mad(index);
+
+    for (x=0; x < 1025; x++) {
+        data_f1[x] = (x & 0xf) * 0.1f + 1.f;
+        data_f4[x].x = (x & 0xf) * 0.1f + 1.f;
+        data_f4[x].y = (x & 0xf0) * 0.1f + 1.f;
+        data_f4[x].z = (x & 0x33) * 0.1f + 1.f;
+        data_f4[x].w = (x & 0x77) * 0.1f + 1.f;
+    }
+
+    test_norm(index);
+    test_sincos4(index);
+    test_sincos(index);
+    test_clamp4(index);
+    test_clamp(index);
+
+    // TODO Actually verify test result accuracy
+    rsDebug("fp_mad_test PASSED", 0);
+    rsSendToClientBlocking(RS_MSG_TEST_PASSED);
+}
+
+
diff --git a/libs/rs/java/tests/src/com/android/rs/test/primitives.rs b/libs/rs/java/tests/src/com/android/rs/test/primitives.rs
new file mode 100644
index 0000000..5312bcc
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/primitives.rs
@@ -0,0 +1,51 @@
+#include "shared.rsh"
+
+#pragma rs export_func(primitives_test)
+
+// Testing primitive types
+static float floatTest = 1.99f;
+static double doubleTest = 2.05;
+static char charTest = -8;
+static short shortTest = -16;
+static int intTest = -32;
+static uchar ucharTest = 8;
+static ushort ushortTest = 16;
+static uint uintTest = 32;
+
+static bool test_primitive_types(uint32_t index) {
+    bool failed = false;
+    start();
+
+    _RS_ASSERT(floatTest == 1.99f);
+    _RS_ASSERT(doubleTest == 2.05);
+    _RS_ASSERT(charTest == -8);
+    _RS_ASSERT(shortTest == -16);
+    _RS_ASSERT(intTest == -32);
+    _RS_ASSERT(ucharTest == 8);
+    _RS_ASSERT(ushortTest == 16);
+    _RS_ASSERT(uintTest == 32);
+
+    float time = end(index);
+
+    if (failed) {
+        rsDebug("test_primitives FAILED", time);
+    }
+    else {
+        rsDebug("test_primitives PASSED", time);
+    }
+
+    return failed;
+}
+
+void primitives_test(uint32_t index, int test_num) {
+    bool failed = false;
+    failed |= test_primitive_types(index);
+
+    if (failed) {
+        rsSendToClientBlocking(RS_MSG_TEST_FAILED);
+    }
+    else {
+        rsSendToClientBlocking(RS_MSG_TEST_PASSED);
+    }
+}
+
diff --git a/libs/rs/java/tests/src/com/android/rs/test/rslist.rs b/libs/rs/java/tests/src/com/android/rs/test/rslist.rs
new file mode 100644
index 0000000..a5f0f6b
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/rslist.rs
@@ -0,0 +1,109 @@
+// 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.rs.test)
+
+#include "rs_graphics.rsh"
+
+float gDY;
+
+rs_font gFont;
+
+typedef struct ListAllocs_s {
+    rs_allocation text;
+    int result;
+} ListAllocs;
+
+ListAllocs *gList;
+
+#pragma rs export_var(gDY, gFont, gList)
+
+void init() {
+    gDY = 0.0f;
+}
+
+int textPos = 0;
+
+int root(int launchID) {
+
+    rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    rsgClearDepth(1.0f);
+
+    textPos -= (int)gDY*2;
+    gDY *= 0.95;
+
+    rsgFontColor(0.9f, 0.9f, 0.9f, 1.0f);
+    rsgBindFont(gFont);
+    color(0.2, 0.2, 0.2, 0);
+
+    rs_allocation listAlloc = rsGetAllocation(gList);
+    int allocSize = rsAllocationGetDimX(listAlloc);
+
+    int width = rsgGetWidth();
+    int height = rsgGetHeight();
+
+    int itemHeight = 80;
+    int totalItemHeight = itemHeight * allocSize;
+
+    /* Prevent scrolling above the top of the list */
+    int firstItem = height - totalItemHeight;
+    if (firstItem < 0) {
+        firstItem = 0;
+    }
+
+    /* Prevent scrolling past the last line of the list */
+    int lastItem = -1 * (totalItemHeight - height);
+    if (lastItem > 0) {
+        lastItem = 0;
+    }
+
+    if (textPos > firstItem) {
+        textPos = firstItem;
+    }
+    else if (textPos < lastItem) {
+        textPos = lastItem;
+    }
+
+    int currentYPos = itemHeight + textPos;
+
+    for(int i = 0; i < allocSize; i ++) {
+        if(currentYPos - itemHeight > height) {
+            break;
+        }
+
+        if(currentYPos > 0) {
+            switch(gList[i].result) {
+                case 1: /* Passed */
+                    rsgFontColor(0.5f, 0.9f, 0.5f, 1.0f);
+                    break;
+                case -1: /* Failed */
+                    rsgFontColor(0.9f, 0.5f, 0.5f, 1.0f);
+                    break;
+                case 0: /* Still Testing */
+                    rsgFontColor(0.9f, 0.9f, 0.5f, 1.0f);
+                    break;
+                default: /* Unknown */
+                    rsgFontColor(0.9f, 0.9f, 0.9f, 1.0f);
+                    break;
+            }
+            rsgDrawRect(0, currentYPos - 1, width, currentYPos, 0);
+            rsgDrawText(gList[i].text, 30, currentYPos - 32);
+        }
+        currentYPos += itemHeight;
+    }
+
+    return 10;
+}
diff --git a/libs/rs/java/tests/src/com/android/rs/test/shared.rsh b/libs/rs/java/tests/src/com/android/rs/test/shared.rsh
new file mode 100644
index 0000000..820fffd
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/shared.rsh
@@ -0,0 +1,38 @@
+#pragma version(1)
+
+#pragma rs java_package_name(com.android.rs.test)
+
+typedef struct TestResult_s {
+    rs_allocation name;
+    bool pass;
+    float score;
+    int64_t time;
+} TestResult;
+TestResult *g_results;
+
+static int64_t g_time;
+
+static void start(void) {
+    g_time = rsUptimeMillis();
+}
+
+static float end(uint32_t idx) {
+    int64_t t = rsUptimeMillis() - g_time;
+    //g_results[idx].time = t;
+    //rsDebug("test time", (int)t);
+    return ((float)t) / 1000.f;
+}
+
+#define _RS_ASSERT(b) \
+do { \
+    if (!(b)) { \
+        failed = true; \
+        rsDebug(#b " FAILED", 0); \
+    } \
+\
+} while (0)
+
+/* These constants must match those in UnitTest.java */
+static const int RS_MSG_TEST_PASSED = 100;
+static const int RS_MSG_TEST_FAILED = 101;
+
diff --git a/libs/rs/java/tests/src/com/android/rs/test/test_root.rs b/libs/rs/java/tests/src/com/android/rs/test/test_root.rs
new file mode 100644
index 0000000..72b391dd
--- /dev/null
+++ b/libs/rs/java/tests/src/com/android/rs/test/test_root.rs
@@ -0,0 +1,26 @@
+// Fountain test script
+#pragma version(1)
+
+#pragma rs java_package_name(com.android.rs.test)
+
+#pragma stateFragment(parent)
+
+#include "rs_graphics.rsh"
+
+
+typedef struct TestResult {
+    rs_allocation name;
+    bool pass;
+    float score;
+} TestResult_t;
+TestResult_t *results;
+
+#pragma rs export_var(results)
+//#pragma rs export_func(addParticles)
+
+int root() {
+
+    return 0;
+}
+
+
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index 5ae8d01..0da637e 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
 	}
@@ -68,9 +80,23 @@
 	param const RsElement * elements
 	param const char ** names
 	param const size_t * nameLengths
+	param const uint32_t * arraySize
 	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 +110,12 @@
 	ret RsType
 	}
 
+TypeGetNativeData {
+	param RsType type
+	param uint32_t * typeData
+	param uint32_t typeDataSize
+	}
+
 AllocationCreateTyped {
 	param RsType type
 	ret RsAllocation
@@ -153,6 +185,16 @@
 	togglePlay
 	}
 
+Allocation1DSubElementData {
+	param RsAllocation va
+	param uint32_t x
+	param const void *data
+	param uint32_t comp_offset
+	param uint32_t bytes
+	handcodeApi
+	togglePlay
+	}
+
 Allocation2DSubData {
 	param RsAllocation va
 	param uint32_t xoff
@@ -163,6 +205,15 @@
 	param uint32_t bytes
 	}
 
+Allocation2DSubElementData {
+	param RsAllocation va
+	param uint32_t x
+	param uint32_t y
+	param const void *data
+	param uint32_t element_offset
+	param uint32_t bytes
+	}
+
 AllocationRead {
 	param RsAllocation va
 	param void * data
@@ -224,6 +275,22 @@
 	param const void *data
 	}
 
+AllocationGetType {
+	param RsAllocation va
+	ret const void*
+	}
+
+AllocationResize1D {
+	param RsAllocation va
+	param uint32_t dimX
+	}
+
+AllocationResize2D {
+	param RsAllocation va
+	param uint32_t dimX
+	param uint32_t dimY
+	}
+
 SamplerBegin {
 	}
 
@@ -232,6 +299,11 @@
 	param RsSamplerValue value
 	}
 
+SamplerSet2 {
+	param RsSamplerParam p
+	param float value
+	}
+
 SamplerCreate {
 	ret RsSampler
 	}
@@ -248,13 +320,6 @@
 ScriptCBegin {
 	}
 
-ScriptSetClearColor {
-	param RsScript s
-	param float r
-	param float g
-	param float b
-	param float a
-	}
 
 ScriptSetTimeZone {
 	param RsScript s
@@ -262,43 +327,49 @@
 	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
 	}
 
-
-
-ScriptCSetScript {
-	param void * codePtr
+ScriptSetVarI {
+	param RsScript s
+	param uint32_t slot
+	param int value
 	}
 
+ScriptSetVarF {
+	param RsScript s
+	param uint32_t slot
+	param float value
+	}
+
+ScriptSetVarD {
+	param RsScript s
+	param uint32_t slot
+	param double value
+	}
+
+ScriptSetVarV {
+	param RsScript s
+	param uint32_t slot
+	param const void * data
+	param uint32_t dataLen
+	handcodeApi
+	togglePlay
+	}
+
+
 ScriptCSetText {
 	param const char * text
 	param uint32_t length
@@ -308,52 +379,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 +425,11 @@
 	param float lw
 }
 
-ProgramRasterSetPointSize{
+ProgramRasterSetCullMode {
 	param RsProgramRaster pr
-	param float ps
+	param RsCullMode mode
 }
 
-
 ProgramBindConstants {
 	param RsProgram vp
 	param uint32_t slot
@@ -391,12 +450,6 @@
 	}
 
 ProgramFragmentCreate {
-	param const uint32_t * params
-	param uint32_t paramLength
-	ret RsProgramFragment
-	}
-
-ProgramFragmentCreate2 {
 	param const char * shaderText
 	param uint32_t shaderLength
 	param const uint32_t * params
@@ -405,11 +458,6 @@
 	}
 
 ProgramVertexCreate {
-	param bool texMat
-	ret RsProgramVertex
-	}
-
-ProgramVertexCreate2 {
 	param const char * shaderText
 	param uint32_t shaderLength
 	param const uint32_t * params
@@ -417,34 +465,10 @@
 	ret RsProgramVertex
 	}
 
-LightBegin {
-	}
-
-LightSetLocal {
-	param bool isLocal
-	}
-
-LightSetMonochromatic {
-	param bool isMono
-	}
-
-LightCreate {
-	ret RsLight light
-	}
-
-
-LightSetPosition {
-	param RsLight light
-	param float x
-	param float y
-	param float z
-	}
-
-LightSetColor {
-	param RsLight light
-	param float r
-	param float g
-	param float b
+FileA3DCreateFromAssetStream {
+	param const void * data
+	param size_t len
+	ret RsFile
 	}
 
 FileOpen {
@@ -453,30 +477,79 @@
 	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..ef69b75 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 {
 
@@ -169,7 +183,6 @@
 
     uint32_t eSize = mAllocation.get()->getType()->getElementSizeBytes();
     uint32_t lineSize = eSize * w;
-    uint32_t destW = getDimX();
 
     const uint8_t *src = static_cast<const uint8_t *>(data);
     for (uint32_t line=yoff; line < (yoff+h); line++) {
@@ -185,6 +198,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..2e9e0b3 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;
@@ -28,6 +34,9 @@
     init(rsc, type);
 
     mPtr = malloc(mType->getSizeBytes());
+    if (mType->getElement()->getHasReferences()) {
+        memset(mPtr, 0, mType->getSizeBytes());
+    }
     if (!mPtr) {
         LOGE("Allocation::Allocation, alloc failure");
     }
@@ -167,9 +176,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 +213,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)
@@ -217,13 +230,19 @@
 }
 
 
-void Allocation::data(const void *data, uint32_t sizeBytes)
+void Allocation::data(Context *rsc, const void *data, uint32_t sizeBytes)
 {
     uint32_t size = mType->getSizeBytes();
     if (size != sizeBytes) {
         LOGE("Allocation::data called with mismatched size expected %i, got %i", size, sizeBytes);
         return;
     }
+
+    if (mType->getElement()->getHasReferences()) {
+        incRefs(data, sizeBytes / mType->getElement()->getSizeBytes());
+        decRefs(mPtr, sizeBytes / mType->getElement()->getSizeBytes());
+    }
+
     memcpy(mPtr, data, size);
     sendDirty();
     mUploadDefered = true;
@@ -234,7 +253,7 @@
     memcpy(data, mPtr, mType->getSizeBytes());
 }
 
-void Allocation::subData(uint32_t xoff, uint32_t count, const void *data, uint32_t sizeBytes)
+void Allocation::subData(Context *rsc, uint32_t xoff, uint32_t count, const void *data, uint32_t sizeBytes)
 {
     uint32_t eSize = mType->getElementSizeBytes();
     uint8_t * ptr = static_cast<uint8_t *>(mPtr);
@@ -246,12 +265,18 @@
         mType->dumpLOGV("type info");
         return;
     }
+
+    if (mType->getElement()->getHasReferences()) {
+        incRefs(data, count);
+        decRefs(ptr, count);
+    }
+
     memcpy(ptr, data, size);
     sendDirty();
     mUploadDefered = true;
 }
 
-void Allocation::subData(uint32_t xoff, uint32_t yoff,
+void Allocation::subData(Context *rsc, uint32_t xoff, uint32_t yoff,
              uint32_t w, uint32_t h, const void *data, uint32_t sizeBytes)
 {
     uint32_t eSize = mType->getElementSizeBytes();
@@ -268,7 +293,10 @@
     }
 
     for (uint32_t line=yoff; line < (yoff+h); line++) {
-        uint8_t * ptr = static_cast<uint8_t *>(mPtr);
+        if (mType->getElement()->getHasReferences()) {
+            incRefs(src, w);
+            decRefs(dst, w);
+        }
         memcpy(dst, src, lineSize);
         src += lineSize;
         dst += destW * eSize;
@@ -277,14 +305,96 @@
     mUploadDefered = true;
 }
 
-void Allocation::subData(uint32_t xoff, uint32_t yoff, uint32_t zoff,
+void Allocation::subData(Context *rsc, uint32_t xoff, uint32_t yoff, uint32_t zoff,
              uint32_t w, uint32_t h, uint32_t d, const void *data, uint32_t sizeBytes)
 {
 }
 
+void Allocation::subElementData(Context *rsc, uint32_t x, const void *data,
+                                uint32_t cIdx, uint32_t sizeBytes)
+{
+    uint32_t eSize = mType->getElementSizeBytes();
+    uint8_t * ptr = static_cast<uint8_t *>(mPtr);
+    ptr += eSize * x;
+
+    if (cIdx >= mType->getElement()->getFieldCount()) {
+        LOGE("Error Allocation::subElementData component %i out of range.", cIdx);
+        rsc->setError(RS_ERROR_BAD_VALUE, "subElementData component out of range.");
+        return;
+    }
+
+    if (x >= mType->getDimX()) {
+        LOGE("Error Allocation::subElementData X offset %i out of range.", x);
+        rsc->setError(RS_ERROR_BAD_VALUE, "subElementData X offset out of range.");
+        return;
+    }
+
+    const Element * e = mType->getElement()->getField(cIdx);
+    ptr += mType->getElement()->getFieldOffsetBytes(cIdx);
+
+    if (sizeBytes != e->getSizeBytes()) {
+        LOGE("Error Allocation::subElementData data size %i does not match field size %i.", sizeBytes, e->getSizeBytes());
+        rsc->setError(RS_ERROR_BAD_VALUE, "subElementData bad size.");
+        return;
+    }
+
+    if (e->getHasReferences()) {
+        e->incRefs(data);
+        e->decRefs(ptr);
+    }
+
+    memcpy(ptr, data, sizeBytes);
+    sendDirty();
+    mUploadDefered = true;
+}
+
+void Allocation::subElementData(Context *rsc, uint32_t x, uint32_t y,
+                                const void *data, uint32_t cIdx, uint32_t sizeBytes)
+{
+    uint32_t eSize = mType->getElementSizeBytes();
+    uint8_t * ptr = static_cast<uint8_t *>(mPtr);
+    ptr += eSize * (x + y * mType->getDimX());
+
+    if (x >= mType->getDimX()) {
+        LOGE("Error Allocation::subElementData X offset %i out of range.", x);
+        rsc->setError(RS_ERROR_BAD_VALUE, "subElementData X offset out of range.");
+        return;
+    }
+
+    if (y >= mType->getDimY()) {
+        LOGE("Error Allocation::subElementData X offset %i out of range.", x);
+        rsc->setError(RS_ERROR_BAD_VALUE, "subElementData X offset out of range.");
+        return;
+    }
+
+    if (cIdx >= mType->getElement()->getFieldCount()) {
+        LOGE("Error Allocation::subElementData component %i out of range.", cIdx);
+        rsc->setError(RS_ERROR_BAD_VALUE, "subElementData component out of range.");
+        return;
+    }
+
+    const Element * e = mType->getElement()->getField(cIdx);
+    ptr += mType->getElement()->getFieldOffsetBytes(cIdx);
+
+    if (sizeBytes != e->getSizeBytes()) {
+        LOGE("Error Allocation::subElementData data size %i does not match field size %i.", sizeBytes, e->getSizeBytes());
+        rsc->setError(RS_ERROR_BAD_VALUE, "subElementData bad size.");
+        return;
+    }
+
+    if (e->getHasReferences()) {
+        e->incRefs(data);
+        e->decRefs(ptr);
+    }
+
+    memcpy(ptr, data, sizeBytes);
+    sendDirty();
+    mUploadDefered = true;
+}
+
 void Allocation::addProgramToDirty(const Program *p)
 {
-    mToDirtyList.add(p);
+    mToDirtyList.push(p);
 }
 
 void Allocation::removeProgramToDirty(const Program *p)
@@ -316,6 +426,61 @@
 
 }
 
+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
+    alloc->data(rsc, stream->getPtr() + stream->getPos(), dataSize);
+    stream->reset(stream->getPos() + dataSize);
+
+    return alloc;
+}
+
 void Allocation::sendDirty() const
 {
     for (size_t ct=0; ct < mToDirtyList.size(); ct++) {
@@ -323,6 +488,65 @@
     }
 }
 
+void Allocation::incRefs(const void *ptr, size_t ct, size_t startOff) const
+{
+    const uint8_t *p = static_cast<const uint8_t *>(ptr);
+    const Element *e = mType->getElement();
+    uint32_t stride = e->getSizeBytes();
+
+    p += stride * startOff;
+    while (ct > 0) {
+        e->incRefs(p);
+        ct --;
+        p += stride;
+    }
+}
+
+void Allocation::decRefs(const void *ptr, size_t ct, size_t startOff) const
+{
+    const uint8_t *p = static_cast<const uint8_t *>(ptr);
+    const Element *e = mType->getElement();
+    uint32_t stride = e->getSizeBytes();
+
+    p += stride * startOff;
+    while (ct > 0) {
+        e->decRefs(p);
+        ct --;
+        p += stride;
+    }
+}
+
+void Allocation::copyRange1D(Context *rsc, const Allocation *src, int32_t srcOff, int32_t destOff, int32_t len)
+{
+}
+
+void Allocation::resize1D(Context *rsc, uint32_t dimX)
+{
+    Type *t = mType->cloneAndResize1D(rsc, dimX);
+
+    uint32_t oldDimX = mType->getDimX();
+    if (dimX == oldDimX) {
+        return;
+    }
+
+    if (dimX < oldDimX) {
+        decRefs(mPtr, oldDimX - dimX, dimX);
+    }
+    mPtr = realloc(mPtr, t->getSizeBytes());
+
+    if (dimX > oldDimX) {
+        const Element *e = mType->getElement();
+        uint32_t stride = e->getSizeBytes();
+        memset(((uint8_t *)mPtr) + stride * oldDimX, 0, stride * (dimX - oldDimX));
+    }
+    mType.set(t);
+}
+
+void Allocation::resize2D(Context *rsc, uint32_t dimX, uint32_t dimY)
+{
+    LOGE("not implemented");
+}
+
 /////////////////
 //
 
@@ -495,7 +719,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 +727,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)
 {
@@ -526,10 +756,7 @@
     const Element *src = static_cast<const Element *>(_src);
     const Element *dst = static_cast<const Element *>(_dst);
 
-    // Check for pow2 on pre es 2.0 versions.
-    rsAssert(rsc->checkVersion2_0() || (!(w & (w-1)) && !(h & (h-1))));
-
-    //LOGE("rsi_AllocationCreateFromBitmap %i %i %i %i %i", w, h, dstFmt, srcFmt, genMips);
+    //LOGE("%p rsi_AllocationCreateFromBitmap %i %i %i", rsc, w, h, genMips);
     rsi_TypeBegin(rsc, _dst);
     rsi_TypeAdd(rsc, RS_DIMENSION_X, w);
     rsi_TypeAdd(rsc, RS_DIMENSION_Y, h);
@@ -546,16 +773,19 @@
     }
 
     ElementConverter_t cvt = pickConverter(dst, src);
-    cvt(texAlloc->getPtr(), data, w * h);
-
-    if (genMips) {
-        Adapter2D adapt(rsc, texAlloc);
-        Adapter2D adapt2(rsc, texAlloc);
-        for(uint32_t lod=0; lod < (texAlloc->getType()->getLODCount() -1); lod++) {
-            adapt.setLOD(lod);
-            adapt2.setLOD(lod + 1);
-            mip(adapt2, adapt);
+    if (cvt) {
+        cvt(texAlloc->getPtr(), data, w * h);
+        if (genMips) {
+            Adapter2D adapt(rsc, texAlloc);
+            Adapter2D adapt2(rsc, texAlloc);
+            for(uint32_t lod=0; lod < (texAlloc->getType()->getLODCount() -1); lod++) {
+                adapt.setLOD(lod);
+                adapt2.setLOD(lod + 1);
+                mip(adapt2, adapt);
+            }
         }
+    } else {
+        rsc->setError(RS_ERROR_BAD_VALUE, "Unsupported bitmap format");
     }
 
     return texAlloc;
@@ -592,19 +822,31 @@
 void rsi_AllocationData(Context *rsc, RsAllocation va, const void *data, uint32_t sizeBytes)
 {
     Allocation *a = static_cast<Allocation *>(va);
-    a->data(data, sizeBytes);
+    a->data(rsc, data, sizeBytes);
 }
 
 void rsi_Allocation1DSubData(Context *rsc, RsAllocation va, uint32_t xoff, uint32_t count, const void *data, uint32_t sizeBytes)
 {
     Allocation *a = static_cast<Allocation *>(va);
-    a->subData(xoff, count, data, sizeBytes);
+    a->subData(rsc, xoff, count, data, sizeBytes);
+}
+
+void rsi_Allocation2DSubElementData(Context *rsc, RsAllocation va, uint32_t x, uint32_t y, const void *data, uint32_t eoff, uint32_t sizeBytes)
+{
+    Allocation *a = static_cast<Allocation *>(va);
+    a->subElementData(rsc, x, y, data, eoff, sizeBytes);
+}
+
+void rsi_Allocation1DSubElementData(Context *rsc, RsAllocation va, uint32_t x, const void *data, uint32_t eoff, uint32_t sizeBytes)
+{
+    Allocation *a = static_cast<Allocation *>(va);
+    a->subElementData(rsc, x, data, eoff, sizeBytes);
 }
 
 void rsi_Allocation2DSubData(Context *rsc, RsAllocation va, uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h, const void *data, uint32_t sizeBytes)
 {
     Allocation *a = static_cast<Allocation *>(va);
-    a->subData(xoff, yoff, w, h, data, sizeBytes);
+    a->subData(rsc, xoff, yoff, w, h, data, sizeBytes);
 }
 
 void rsi_AllocationRead(Context *rsc, RsAllocation va, void *data)
@@ -613,6 +855,27 @@
     a->read(data);
 }
 
+void rsi_AllocationResize1D(Context *rsc, RsAllocation va, uint32_t dimX)
+{
+    Allocation *a = static_cast<Allocation *>(va);
+    a->resize1D(rsc, dimX);
+}
+
+void rsi_AllocationResize2D(Context *rsc, RsAllocation va, uint32_t dimX, uint32_t dimY)
+{
+    Allocation *a = static_cast<Allocation *>(va);
+    a->resize2D(rsc, dimX, dimY);
+}
+
+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..24e245f 100644
--- a/libs/rs/rsAllocation.h
+++ b/libs/rs/rsAllocation.h
@@ -55,14 +55,23 @@
     void uploadToBufferObject(const Context *rsc);
     uint32_t getBufferObjectID() const {return mBufferID;}
 
+    void copyRange1D(Context *rsc, const Allocation *src, int32_t srcOff, int32_t destOff, int32_t len);
 
-    void data(const void *data, uint32_t sizeBytes);
-    void subData(uint32_t xoff, uint32_t count, const void *data, uint32_t sizeBytes);
-    void subData(uint32_t xoff, uint32_t yoff,
+    void resize1D(Context *rsc, uint32_t dimX);
+    void resize2D(Context *rsc, uint32_t dimX, uint32_t dimY);
+
+    void data(Context *rsc, const void *data, uint32_t sizeBytes);
+    void subData(Context *rsc, uint32_t xoff, uint32_t count, const void *data, uint32_t sizeBytes);
+    void subData(Context *rsc, uint32_t xoff, uint32_t yoff,
                  uint32_t w, uint32_t h, const void *data, uint32_t sizeBytes);
-    void subData(uint32_t xoff, uint32_t yoff, uint32_t zoff,
+    void subData(Context *rsc, uint32_t xoff, uint32_t yoff, uint32_t zoff,
                  uint32_t w, uint32_t h, uint32_t d, const void *data, uint32_t sizeBytes);
 
+    void subElementData(Context *rsc, uint32_t x,
+                        const void *data, uint32_t elementOff, uint32_t sizeBytes);
+    void subElementData(Context *rsc, uint32_t x, uint32_t y,
+                        const void *data, uint32_t elementOff, uint32_t sizeBytes);
+
     void read(void *data);
 
     void enableGLVertexBuffers() const;
@@ -72,12 +81,22 @@
     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);
 
-protected:
-    void sendDirty() const;
+    bool getIsTexture() const {return mIsTexture;}
+    bool getIsBufferObject() const {return mIsVertexBuffer;}
 
+    void incRefs(const void *ptr, size_t ct, size_t startOff = 0) const;
+    void decRefs(const void *ptr, size_t ct, size_t startOff = 0) const;
+
+    void sendDirty() const;
+    bool getHasGraphicsMipmaps() const {return mTextureGenMipmap;}
+
+protected:
     ObjectBaseRef<const Type> mType;
     void * mPtr;
 
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..f51b23e 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;
@@ -91,6 +95,26 @@
         mNormalized = true;
         rsAssert(mKind == RS_KIND_PIXEL_RGBA);
         return;
+
+    case RS_TYPE_MATRIX_4X4:
+        mTypeBits = 16 * 32;
+        rsAssert(mVectorSize == 1);
+        rsAssert(mNormalized == false);
+        rsAssert(mKind == RS_KIND_USER);
+        break;
+    case RS_TYPE_MATRIX_3X3:
+        mTypeBits = 9 * 32;
+        rsAssert(mVectorSize == 1);
+        rsAssert(mNormalized == false);
+        rsAssert(mKind == RS_KIND_USER);
+        break;
+    case RS_TYPE_MATRIX_2X2:
+        mTypeBits = 4 * 32;
+        rsAssert(mVectorSize == 1);
+        rsAssert(mNormalized == false);
+        rsAssert(mKind == RS_KIND_USER);
+        break;
+
     case RS_TYPE_ELEMENT:
     case RS_TYPE_TYPE:
     case RS_TYPE_ALLOCATION:
@@ -148,11 +172,19 @@
     case RS_TYPE_UNSIGNED_64:
         mTypeBits = 64;
         break;
+
+    case RS_TYPE_BOOLEAN:
+        mTypeBits = 8;
+        break;
     }
 
     mBits = mTypeBits * mVectorSize;
 }
 
+bool Component::isReference() const
+{
+    return (mType >= RS_TYPE_ELEMENT);
+}
 
 
 
@@ -188,82 +220,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) {
@@ -282,10 +238,19 @@
         case 4: return String8("vec4");
         }
     }
+    if ((mType == RS_TYPE_MATRIX_4X4) && (mVectorSize == 1)) {
+        return String8("mat4");
+    }
+    if ((mType == RS_TYPE_MATRIX_3X3) && (mVectorSize == 1)) {
+        return String8("mat3");
+    }
+    if ((mType == RS_TYPE_MATRIX_2X2) && (mVectorSize == 1)) {
+        return String8("mat2");
+    }
     return String8();
 }
 
-static const char * gTypeStrings[] = {
+static const char * gTypeBasicStrings[] = {
     "NONE",
     "F16",
     "F32",
@@ -298,9 +263,16 @@
     "U16",
     "U32",
     "U64",
+    "BOOLEAN",
     "UP_565",
     "UP_5551",
     "UP_4444",
+    "MATRIX_4X4",
+    "MATRIX_3X3",
+    "MATRIX_2X2",
+};
+
+static const char * gTypeObjStrings[] = {
     "ELEMENT",
     "TYPE",
     "ALLOCATION",
@@ -330,8 +302,34 @@
 
 void Component::dumpLOGV(const char *prefix) const
 {
-    LOGV("%s   Component: %s, %s, vectorSize=%i, bits=%i",
-         prefix, gTypeStrings[mType], gKindStrings[mKind], mVectorSize, mBits);
+    if (mType >= RS_TYPE_ELEMENT) {
+        LOGV("%s   Component: %s, %s, vectorSize=%i, bits=%i",
+             prefix, gTypeObjStrings[mType - RS_TYPE_ELEMENT], gKindStrings[mKind], mVectorSize, mBits);
+    } else {
+        LOGV("%s   Component: %s, %s, vectorSize=%i, bits=%i",
+             prefix, gTypeBasicStrings[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..a775051 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,12 @@
     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);
+
+    bool isReference() const;
+
 protected:
     RsDataType mType;
     RsDataKind mKind;
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index 596f533..30add62 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>
 
@@ -32,6 +33,8 @@
 #include <GLES2/gl2ext.h>
 
 #include <cutils/sched_policy.h>
+#include <sys/syscall.h>
+#include <string.h>
 
 using namespace android;
 using namespace android::renderscript;
@@ -87,7 +90,7 @@
     configAttribsPtr[0] = EGL_NONE;
     rsAssert(configAttribsPtr < (configAttribs + (sizeof(configAttribs) / sizeof(EGLint))));
 
-    LOGV("initEGL start");
+    LOGV("%p initEGL start", this);
     mEGL.mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
     checkEglError("eglGetDisplay");
 
@@ -96,7 +99,7 @@
 
     status_t err = EGLUtils::selectConfigForNativeWindow(mEGL.mDisplay, configAttribs, mWndSurface, &mEGL.mConfig);
     if (err) {
-       LOGE("couldn't find an EGLConfig matching the screen format\n");
+       LOGE("%p, couldn't find an EGLConfig matching the screen format\n", this);
     }
     //eglChooseConfig(mEGL.mDisplay, configAttribs, &mEGL.mConfig, 1, &mEGL.mNumConfigs);
 
@@ -108,14 +111,14 @@
     }
     checkEglError("eglCreateContext");
     if (mEGL.mContext == EGL_NO_CONTEXT) {
-        LOGE("eglCreateContext returned EGL_NO_CONTEXT");
+        LOGE("%p, eglCreateContext returned EGL_NO_CONTEXT", this);
     }
     gGLContextCount++;
 }
 
 void Context::deinitEGL()
 {
-    LOGV("deinitEGL");
+    LOGV("%p, deinitEGL", this);
     setSurface(0, 0, NULL);
     eglDestroyContext(mEGL.mDisplay, mEGL.mContext);
     checkEglError("eglDestroyContext");
@@ -127,19 +130,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;
 }
 
@@ -147,42 +152,19 @@
 {
     GLenum err = glGetError();
     if (err != GL_NO_ERROR) {
-        LOGE("GL Error, 0x%x, from %s", err, msg);
+        LOGE("%p, GL Error, 0x%x, from %s", this, err, msg);
     }
 }
 
 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) {
-        // If we have an error condition we stop rendering until
-        // somthing changes that might fix it.
-        ret = 0;
-    }
     return ret;
 }
 
@@ -206,6 +188,9 @@
     mTimeFrame = mTimeLast;
     mTimeLastFrame = mTimeLast;
     mTimerActive = RS_TIMER_INTERNAL;
+    mAverageFPSFrameCount = 0;
+    mAverageFPSStartTime = mTimeLast;
+    mAverageFPS = 0;
     timerReset();
 }
 
@@ -213,6 +198,16 @@
 {
     mTimeLastFrame = mTimeFrame;
     mTimeFrame = getTime();
+    // Update average fps
+    const uint64_t averageFramerateInterval = 1000 * 1000000;
+    mAverageFPSFrameCount ++;
+    uint64_t inverval = mTimeFrame - mAverageFPSStartTime;
+    if(inverval >= averageFramerateInterval) {
+        inverval = inverval / 1000000;
+        mAverageFPS = (mAverageFPSFrameCount * 1000) / inverval;
+        mAverageFPSFrameCount = 0;
+        mAverageFPSStartTime = mTimeFrame;
+    }
 }
 
 void Context::timerSet(Timers tm)
@@ -236,37 +231,34 @@
 
 
     if (props.mLogTimes) {
-        LOGV("RS: Frame (%i),   Script %2.1f (%i),  Clear & Swap %2.1f (%i),  Idle %2.1f (%lli),  Internal %2.1f (%lli)",
+        LOGV("RS: Frame (%i),   Script %2.1f (%i),  Clear & Swap %2.1f (%i),  Idle %2.1f (%lli),  Internal %2.1f (%lli), Avg fps: %u",
              mTimeMSLastFrame,
              100.0 * mTimers[RS_TIMER_SCRIPT] / total, mTimeMSLastScript,
              100.0 * mTimers[RS_TIMER_CLEAR_SWAP] / total, mTimeMSLastSwap,
              100.0 * mTimers[RS_TIMER_IDLE] / total, mTimers[RS_TIMER_IDLE] / 1000000,
-             100.0 * mTimers[RS_TIMER_INTERNAL] / total, mTimers[RS_TIMER_INTERNAL] / 1000000);
+             100.0 * mTimers[RS_TIMER_INTERNAL] / total, mTimers[RS_TIMER_INTERNAL] / 1000000,
+             mAverageFPS);
     }
 }
 
 bool Context::setupCheck()
 {
-    if (checkVersion2_0()) {
-        if (!mShaderCache.lookup(this, mVertex.get(), mFragment.get())) {
-            LOGE("Context::setupCheck() 1 fail");
-            return false;
-        }
-
-        mFragmentStore->setupGL2(this, &mStateFragmentStore);
-        mFragment->setupGL2(this, &mStateFragment, &mShaderCache);
-        mRaster->setupGL2(this, &mStateRaster);
-        mVertex->setupGL2(this, &mStateVertex, &mShaderCache);
-
-    } else {
-        mFragmentStore->setupGL(this, &mStateFragmentStore);
-        mFragment->setupGL(this, &mStateFragment);
-        mRaster->setupGL(this, &mStateRaster);
-        mVertex->setupGL(this, &mStateVertex);
+    if (!mShaderCache.lookup(this, mVertex.get(), mFragment.get())) {
+        LOGE("Context::setupCheck() 1 fail");
+        return false;
     }
+
+    mFragmentStore->setupGL2(this, &mStateFragmentStore);
+    mFragment->setupGL2(this, &mStateFragment, &mShaderCache);
+    mRaster->setupGL2(this, &mStateRaster);
+    mVertex->setupGL2(this, &mStateVertex, &mShaderCache);
     return true;
 }
 
+void Context::setupProgramStore() {
+    mFragmentStore->setupGL2(this, &mStateFragmentStore);
+}
+
 static bool getProp(const char *str)
 {
     char buf[PROPERTY_VALUE_MAX];
@@ -274,6 +266,24 @@
     return 0 != strcmp(buf, "0");
 }
 
+void Context::displayDebugStats()
+{
+    char buffer[128];
+    sprintf(buffer, "Avg fps %u, Frame %i ms, Script %i ms", mAverageFPS, mTimeMSLastFrame, mTimeMSLastScript);
+    float oldR, oldG, oldB, oldA;
+    mStateFont.getFontColor(&oldR, &oldG, &oldB, &oldA);
+    uint32_t bufferLen = strlen(buffer);
+
+    float shadowCol = 0.1f;
+    mStateFont.setFontColor(shadowCol, shadowCol, shadowCol, 1.0f);
+    mStateFont.renderText(buffer, bufferLen, 5, getHeight() - 6);
+
+    mStateFont.setFontColor(1.0f, 0.7f, 0.0f, 1.0f);
+    mStateFont.renderText(buffer, bufferLen, 4, getHeight() - 7);
+
+    mStateFont.setFontColor(oldR, oldG, oldB, oldA);
+}
+
 void * Context::threadProc(void *vrsc)
 {
      Context *rsc = static_cast<Context *>(vrsc);
@@ -286,28 +296,34 @@
      rsc->props.mLogScripts = getProp("debug.rs.script");
      rsc->props.mLogObjects = getProp("debug.rs.object");
      rsc->props.mLogShaders = getProp("debug.rs.shader");
+     rsc->props.mLogShadersAttr = getProp("debug.rs.shader.attributes");
+     rsc->props.mLogShadersUniforms = getProp("debug.rs.shader.uniforms");
+     rsc->props.mLogVisual = getProp("debug.rs.visual");
 
-     ScriptTLSStruct *tlsStruct = new ScriptTLSStruct;
-     if (!tlsStruct) {
+     rsc->mTlsStruct = new ScriptTLSStruct;
+     if (!rsc->mTlsStruct) {
          LOGE("Error allocating tls storage");
          return NULL;
      }
-     tlsStruct->mContext = rsc;
-     tlsStruct->mScript = NULL;
-     int status = pthread_setspecific(rsc->gThreadTLSKey, tlsStruct);
+     rsc->mTlsStruct->mContext = rsc;
+     rsc->mTlsStruct->mScript = NULL;
+     int status = pthread_setspecific(rsc->gThreadTLSKey, rsc->mTlsStruct);
      if (status) {
          LOGE("pthread_setspecific %i", status);
      }
 
+     rsc->mScriptC.init(rsc);
      if (rsc->mIsGraphicsContext) {
-         rsc->mStateRaster.init(rsc, rsc->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 +337,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);
@@ -329,9 +350,6 @@
              rsc->timerPrint();
              rsc->timerReset();
          }
-         if (rsc->mObjDestroy.mNeedToEmpty) {
-             rsc->objDestroyOOBRun();
-         }
          if (rsc->mThreadPriority > 0 && targetTime) {
              int32_t t = (targetTime - (int32_t)(rsc->mTimeMSLastScript + rsc->mTimeMSLastSwap)) * 1000;
              if (t > 0) {
@@ -340,33 +358,85 @@
          }
      }
 
-     LOGV("RS Thread exiting");
+     LOGV("%p, RS Thread exiting", rsc);
      if (rsc->mIsGraphicsContext) {
          rsc->mRaster.clear();
          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);
 
-     rsc->mObjDestroy.mNeedToEmpty = true;
-     rsc->objDestroyOOBRun();
-
      if (rsc->mIsGraphicsContext) {
          pthread_mutex_lock(&gInitMutex);
          rsc->deinitEGL();
          pthread_mutex_unlock(&gInitMutex);
      }
+     delete rsc->mTlsStruct;
 
-     LOGV("RS Thread exited");
+     LOGV("%p, RS Thread exited", rsc);
      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();
+
+#if 0
+     typedef struct {uint64_t bits[1024 / 64]; } cpu_set_t;
+     cpu_set_t cpuset;
+     memset(&cpuset, 0, sizeof(cpuset));
+     cpuset.bits[idx / 64] |= 1ULL << (idx % 64);
+     int ret = syscall(241, rsc->mWorkers.mNativeThreadId[idx],
+               sizeof(cpuset), &cpuset);
+     LOGE("SETAFFINITY ret = %i %s", ret, EGLUtils::strerror(ret));
+#endif
+
+     setpriority(PRIO_PROCESS, rsc->mWorkers.mNativeThreadId[idx], rsc->mThreadPriority);
+     int status = pthread_setspecific(rsc->gThreadTLSKey, rsc->mTlsStruct);
+     if (status) {
+         LOGE("pthread_setspecific %i", status);
+     }
+
+     while(rsc->mRunning) {
+         rsc->mWorkers.mLaunchSignals[idx].wait();
+         if (rsc->mWorkers.mLaunchCallback) {
+            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 +453,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
 }
 
@@ -429,20 +502,40 @@
 
     mWndSurface = NULL;
 
-    objDestroyOOBInit();
     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.mCompleteSignal.init();
+    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);
 }
 
@@ -455,8 +548,6 @@
 
     mIO.shutdown();
     int status = pthread_join(mThreadId, &res);
-    mObjDestroy.mNeedToEmpty = true;
-    objDestroyOOBRun();
 
     // Global structure cleanup.
     pthread_mutex_lock(&gInitMutex);
@@ -469,8 +560,6 @@
         mDev = NULL;
     }
     pthread_mutex_unlock(&gInitMutex);
-
-    objDestroyOOBDestroy();
 }
 
 void Context::setSurface(uint32_t w, uint32_t h, ANativeWindow *sur)
@@ -486,14 +575,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 +600,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);
@@ -560,6 +641,11 @@
             glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &mGL.mMaxFragmentUniformVectors);
 
             mGL.OES_texture_npot = NULL != strstr((const char *)mGL.mExtensions, "GL_OES_texture_npot");
+            mGL.EXT_texture_max_aniso = 1.0f;
+            bool hasAniso = NULL != strstr((const char *)mGL.mExtensions, "GL_EXT_texture_filter_anisotropic");
+            if(hasAniso) {
+                glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &mGL.EXT_texture_max_aniso);
+            }
         }
 
     }
@@ -583,7 +669,7 @@
     mRootScript.set(s);
 }
 
-void Context::setFragmentStore(ProgramFragmentStore *pfs)
+void Context::setFragmentStore(ProgramStore *pfs)
 {
     rsAssert(mIsGraphicsContext);
     if (pfs == NULL) {
@@ -623,6 +709,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,90 +736,13 @@
     }
 }
 
-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) {
-        LOGE("Context::ObjDestroyOOBInit mutex init failure");
-        return false;
-    }
-    return true;
-}
-
-void Context::objDestroyOOBRun()
-{
-    if (mObjDestroy.mNeedToEmpty) {
-        int status = pthread_mutex_lock(&mObjDestroy.mMutex);
-        if (status) {
-            LOGE("Context::ObjDestroyOOBRun: error %i locking for OOBRun.", status);
-            return;
-        }
-
-        for (size_t ct = 0; ct < mObjDestroy.mDestroyList.size(); ct++) {
-            mObjDestroy.mDestroyList[ct]->decUserRef();
-        }
-        mObjDestroy.mDestroyList.clear();
-        mObjDestroy.mNeedToEmpty = false;
-
-        status = pthread_mutex_unlock(&mObjDestroy.mMutex);
-        if (status) {
-            LOGE("Context::ObjDestroyOOBRun: error %i unlocking for set condition.", status);
-        }
-    }
-}
-
-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);
-        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);
-    }
-}
-
 uint32_t Context::getMessageToClient(void *data, size_t *receiveLen, size_t bufferLen, bool wait)
 {
     //LOGE("getMessageToClient %i %i", bufferLen, wait);
+    *receiveLen = 0;
     if (!wait) {
         if (mIO.mToClient.isEmpty()) {
             // No message to get and not going to wait for one.
-            receiveLen = 0;
             return 0;
         }
     }
@@ -751,15 +770,19 @@
         return false;
     }
     if (!waitForSpace) {
-        if (mIO.mToClient.getFreeSpace() < len) {
+        if (!mIO.mToClient.makeSpaceNonBlocking(len + 8)) {
             // Not enough room, and not waiting.
             return false;
         }
     }
     //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 +822,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 +844,9 @@
 namespace android {
 namespace renderscript {
 
+void rsi_ContextFinish(Context *rsc)
+{
+}
 
 void rsi_ContextBindRootScript(Context *rsc, RsScript vs)
 {
@@ -841,9 +866,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 +890,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);
@@ -929,6 +966,7 @@
     LOGV("rsContextCreateGL %p, %i", vdev, useDepth);
     Device * dev = static_cast<Device *>(vdev);
     Context *rsc = new Context(dev, true, useDepth);
+    LOGV("rsContextCreateGL ret %p ", rsc);
     return rsc;
 }
 
@@ -938,12 +976,6 @@
     delete rsc;
 }
 
-void rsObjDestroyOOB(RsContext vrsc, void *obj)
-{
-    Context * rsc = static_cast<Context *>(vrsc);
-    rsc->objDestroyAdd(static_cast<ObjectBase *>(obj));
-}
-
 uint32_t rsContextGetMessage(RsContext vrsc, void *data, size_t *receiveLen, size_t bufferLen, bool wait)
 {
     Context * rsc = static_cast<Context *>(vrsc);
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 709730e..8a8b8a8 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -18,21 +18,21 @@
 #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"
 #include "rsAllocation.h"
 #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"
@@ -48,6 +48,24 @@
 
 namespace renderscript {
 
+#if 0
+#define CHECK_OBJ(o) { \
+    GET_TLS(); \
+    if(!ObjectBase::isValid(rsc, (const ObjectBase *)o)) {  \
+        LOGE("Bad object %p at %s, %i", o, __FILE__, __LINE__);  \
+    } \
+}
+#define CHECK_OBJ_OR_NULL(o) { \
+    GET_TLS(); \
+    if(o && !ObjectBase::isValid(rsc, (const ObjectBase *)o)) {  \
+        LOGE("Bad object %p at %s, %i", o, __FILE__, __LINE__);  \
+    } \
+}
+#else
+#define CHECK_OBJ(o)
+#define CHECK_OBJ_OR_NULL(o)
+#endif
+
 class Context
 {
 public:
@@ -63,18 +81,20 @@
         Context * mContext;
         Script * mScript;
     };
+    ScriptTLSStruct *mTlsStruct;
 
+    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,16 +104,19 @@
     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();
+    void setupProgramStore();
     bool checkDriver() const {return mEGL.mSurface != 0;}
 
     void pause();
@@ -103,12 +126,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,19 +140,21 @@
     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;
-    void objDestroyAdd(ObjectBase *);
 
     // Timers
     enum Timers {
@@ -148,24 +171,31 @@
     void timerPrint();
     void timerFrame();
 
-    bool checkVersion1_1() const {return (mGL.mMajorVersion > 1) || (mGL.mMinorVersion >= 1); }
-    bool checkVersion2_0() const {return mGL.mMajorVersion >= 2; }
-
     struct {
         bool mLogTimes;
         bool mLogScripts;
         bool mLogObjects;
         bool mLogShaders;
+        bool mLogShadersAttr;
+        bool mLogShadersUniforms;
+        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;}
+    float ext_texture_max_aniso() const {return mGL.EXT_texture_max_aniso; }
+    uint32_t getMaxFragmentTextures() const {return mGL.mMaxFragmentTextureImageUnits;}
+    uint32_t getMaxFragmentUniformVectors() const {return mGL.mMaxFragmentUniformVectors;}
+    uint32_t getMaxVertexUniformVectors() const {return mGL.mMaxVertexUniformVectors;}
+
+    void launchThreads(WorkerCallback_t cbk, void *data);
+    uint32_t getWorkerPoolSize() const {return (uint32_t)mWorkers.mCount;}
 
 protected:
     Device *mDev;
@@ -177,8 +207,6 @@
         EGLConfig mConfig;
         EGLContext mContext;
         EGLSurface mSurface;
-        EGLint mWidth;
-        EGLint mHeight;
         EGLDisplay mDisplay;
     } mEGL;
 
@@ -202,6 +230,7 @@
         int32_t mMaxVertexTextureUnits;
 
         bool OES_texture_npot;
+        float EXT_texture_max_aniso;
     } mGL;
 
     uint32_t mWidth;
@@ -219,22 +248,28 @@
     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;
-        Vector<ObjectBase *> mDestroyList;
-        bool mNeedToEmpty;
-    };
-    ObjDestroyOOB mObjDestroy;
-    bool objDestroyOOBInit();
-    void objDestroyOOBRun();
-    void objDestroyOOBDestroy();
+    void displayDebugStats();
 
 private:
     Context();
@@ -245,6 +280,7 @@
     uint32_t runRootScript();
 
     static void * threadProc(void *);
+    static void * helperThreadProc(void *);
 
     ANativeWindow *mWndSurface;
 
@@ -258,6 +294,9 @@
     uint32_t mTimeMSLastFrame;
     uint32_t mTimeMSLastScript;
     uint32_t mTimeMSLastSwap;
+    uint32_t mAverageFPSFrameCount;
+    uint64_t mAverageFPSStartTime;
+    uint32_t mAverageFPS;
 };
 
 }
diff --git a/libs/rs/rsContextHostStub.h b/libs/rs/rsContextHostStub.h
new file mode 100644
index 0000000..aa0205d
--- /dev/null
+++ b/libs/rs/rsContextHostStub.h
@@ -0,0 +1,157 @@
+/*
+ * 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 "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;
+    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;
+        bool mLogShadersAttr;
+        bool mLogShadersUniforms;
+        bool mLogVisual;
+    } 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;}
+    float ext_texture_max_aniso() const {return 1.0f;}
+    uint32_t getMaxFragmentTextures() const {return mGL.mMaxFragmentTextureImageUnits;}
+    uint32_t getMaxFragmentUniformVectors() const {return mGL.mMaxFragmentUniformVectors;}
+    uint32_t getMaxVertexUniformVectors() const {return mGL.mMaxVertexUniformVectors;}
+
+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..0b9e28c 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;
@@ -29,6 +34,7 @@
     mAllocLine = __LINE__;
     mFields = NULL;
     mFieldCount = 0;
+    mHasReference = false;
 }
 
 
@@ -48,6 +54,7 @@
     delete [] mFields;
     mFields = NULL;
     mFieldCount = 0;
+    mHasReference = false;
 }
 
 size_t Element::getSizeBits() const
@@ -58,20 +65,11 @@
 
     size_t total = 0;
     for (size_t ct=0; ct < mFieldCount; ct++) {
-        total += mFields[ct].e->mBits;
+        total += mFields[ct].e->mBits * mFields[ct].arraySize;;
     }
     return total;
 }
 
-size_t Element::getFieldOffsetBits(uint32_t componentNumber) const
-{
-    size_t offset = 0;
-    for (uint32_t ct = 0; ct < componentNumber; ct++) {
-        offset += mFields[ct].e->mBits;
-    }
-    return offset;
-}
-
 void Element::dumpLOGV(const char *prefix) const
 {
     ObjectBase::dumpLOGV(prefix);
@@ -83,6 +81,100 @@
     }
 }
 
+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);
+        stream->addU32(mFields[ct].arraySize);
+        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->mHasReference = elem->mComponent.isReference();
+
+    elem->mFieldCount = stream->loadU32();
+    if(elem->mFieldCount) {
+        uint32_t offset = 0;
+        elem->mFields = new ElementField_t [elem->mFieldCount];
+        for(uint32_t ct = 0; ct < elem->mFieldCount; ct ++) {
+            stream->loadString(&elem->mFields[ct].name);
+            elem->mFields[ct].arraySize = stream->loadU32();
+            Element *fieldElem = Element::createFromStream(rsc, stream);
+            elem->mFields[ct].e.set(fieldElem);
+            elem->mFields[ct].offsetBits = offset;
+            offset += fieldElem->getSizeBits();
+            // Check if our sub-elements have references
+            if(fieldElem->mHasReference) {
+                elem->mHasReference = true;
+            }
+        }
+    }
+
+    // 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->isEqual(elem)) {
+            delete elem;
+            ee->incUserRef();
+            return ee;
+        }
+    }
+
+    rsc->mStateElement.mElements.push(elem);
+    return elem;
+}
+
+bool Element::isEqual(const Element *other) const {
+    if(other == NULL) {
+        return false;
+    }
+    if (!other->getFieldCount() && !mFieldCount) {
+        if((other->getType() == getType()) &&
+           (other->getKind() == getKind()) &&
+           (other->getComponent().getIsNormalized() == getComponent().getIsNormalized()) &&
+           (other->getComponent().getVectorSize() == getComponent().getVectorSize())) {
+            return true;
+        }
+        return false;
+    }
+    if (other->getFieldCount() == mFieldCount) {
+        for (uint32_t i=0; i < mFieldCount; i++) {
+            if ((!other->mFields[i].e->isEqual(mFields[i].e.get())) ||
+                (other->mFields[i].name.length() != mFields[i].name.length()) ||
+                (other->mFields[i].name != mFields[i].name) ||
+                (other->mFields[i].arraySize != mFields[i].arraySize)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    return false;
+}
 
 const Element * Element::create(Context *rsc, RsDataType dt, RsDataKind dk,
                             bool isNorm, uint32_t vecSize)
@@ -104,12 +196,13 @@
     Element *e = new Element(rsc);
     e->mComponent.set(dt, dk, isNorm, vecSize);
     e->mBits = e->mComponent.getBits();
+    e->mHasReference = e->mComponent.isReference();
     rsc->mStateElement.mElements.push(e);
     return e;
 }
 
 const Element * Element::create(Context *rsc, size_t count, const Element **ein,
-                            const char **nin, const size_t * lengths)
+                            const char **nin, const size_t * lengths, const uint32_t *asin)
 {
     // Look for an existing match.
     for (uint32_t ct=0; ct < rsc->mStateElement.mElements.size(); ct++) {
@@ -119,7 +212,8 @@
             for (uint32_t i=0; i < count; i++) {
                 if ((ee->mFields[i].e.get() != ein[i]) ||
                     (ee->mFields[i].name.length() != lengths[i]) ||
-                    (ee->mFields[i].name != nin[i])) {
+                    (ee->mFields[i].name != nin[i]) ||
+                    (ee->mFields[i].arraySize != asin[i])) {
                     match = false;
                     break;
                 }
@@ -134,54 +228,23 @@
     Element *e = new Element(rsc);
     e->mFields = new ElementField_t [count];
     e->mFieldCount = count;
+    size_t bits = 0;
     for (size_t ct=0; ct < count; ct++) {
         e->mFields[ct].e.set(ein[ct]);
         e->mFields[ct].name.setTo(nin[ct], lengths[ct]);
+        e->mFields[ct].offsetBits = bits;
+        e->mFields[ct].arraySize = asin[ct];
+        bits += ein[ct]->getSizeBits();
+
+        if (ein[ct]->mHasReference) {
+            e->mHasReference = true;
+        }
     }
 
     rsc->mStateElement.mElements.push(e);
     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;
@@ -201,10 +264,60 @@
     return s;
 }
 
+void Element::incRefs(const void *ptr) const
+{
+    if (!mFieldCount) {
+        if (mComponent.isReference()) {
+            ObjectBase *const*obp = static_cast<ObjectBase *const*>(ptr);
+            ObjectBase *ob = obp[0];
+            if (ob) ob->incSysRef();
+        }
+        return;
+    }
+
+    const uint8_t *p = static_cast<const uint8_t *>(ptr);
+    for (uint32_t i=0; i < mFieldCount; i++) {
+        if (mFields[i].e->mHasReference) {
+            p = &p[mFields[i].offsetBits >> 3];
+            for (uint32_t ct=0; ct < mFields[i].arraySize; ct++) {
+                mFields[i].e->incRefs(p);
+                p += mFields[i].e->getSizeBytes();
+            }
+        }
+    }
+}
+
+void Element::decRefs(const void *ptr) const
+{
+    if (!mFieldCount) {
+        if (mComponent.isReference()) {
+            ObjectBase *const*obp = static_cast<ObjectBase *const*>(ptr);
+            ObjectBase *ob = obp[0];
+            if (ob) ob->decSysRef();
+        }
+        return;
+    }
+
+    const uint8_t *p = static_cast<const uint8_t *>(ptr);
+    for (uint32_t i=0; i < mFieldCount; i++) {
+        if (mFields[i].e->mHasReference) {
+            p = &p[mFields[i].offsetBits >> 3];
+            for (uint32_t ct=0; ct < mFields[i].arraySize; ct++) {
+                mFields[i].e->decRefs(p);
+                p += mFields[i].e->getSizeBytes();
+            }
+        }
+    }
+}
 
 
 ElementState::ElementState()
 {
+    const uint32_t initialCapacity = 32;
+    mBuilderElements.setCapacity(initialCapacity);
+    mBuilderNameStrings.setCapacity(initialCapacity);
+    mBuilderNameLengths.setCapacity(initialCapacity);
+    mBuilderArrays.setCapacity(initialCapacity);
 }
 
 ElementState::~ElementState()
@@ -212,6 +325,29 @@
     rsAssert(!mElements.size());
 }
 
+void ElementState::elementBuilderBegin() {
+    mBuilderElements.clear();
+    mBuilderNameStrings.clear();
+    mBuilderNameLengths.clear();
+    mBuilderArrays.clear();
+}
+
+void ElementState::elementBuilderAdd(const Element *e, const char *nameStr, uint32_t arraySize) {
+    mBuilderElements.push(e);
+    mBuilderNameStrings.push(nameStr);
+    mBuilderNameLengths.push(strlen(nameStr));
+    mBuilderArrays.push(arraySize);
+
+}
+
+const Element *ElementState::elementBuilderCreate(Context *rsc) {
+    return Element::create(rsc, mBuilderElements.size(),
+                                &(mBuilderElements.editArray()[0]),
+                                &(mBuilderNameStrings.editArray()[0]),
+                                mBuilderNameLengths.editArray(),
+                                mBuilderArrays.editArray());
+}
+
 
 /////////////////////////////////////////
 //
@@ -235,14 +371,41 @@
                              size_t count,
                              const RsElement * ein,
                              const char ** names,
-                             const size_t * nameLengths)
+                             const size_t * nameLengths,
+                             const uint32_t * arraySizes)
 {
     //LOGE("rsi_ElementCreate2 %i", count);
-    const Element *e = Element::create(rsc, count, (const Element **)ein, names, nameLengths);
+    const Element *e = Element::create(rsc, count, (const Element **)ein, names, nameLengths, arraySizes);
     e->incUserRef();
     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..50bca85 100644
--- a/libs/rs/rsElement.h
+++ b/libs/rs/rsElement.h
@@ -40,9 +40,11 @@
         return (getSizeBits() + 7) >> 3;
     }
 
-    size_t getFieldOffsetBits(uint32_t componentNumber) const;
+    size_t getFieldOffsetBits(uint32_t componentNumber) const {
+        return mFields[componentNumber].offsetBits;
+    }
     size_t getFieldOffsetBytes(uint32_t componentNumber) const {
-        return (getFieldOffsetBits(componentNumber) + 7) >> 3;
+        return mFields[componentNumber].offsetBits >> 3;
     }
 
     uint32_t getFieldCount() const {return mFieldCount;}
@@ -54,16 +56,23 @@
     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);
     static const Element * create(Context *rsc, size_t count, const Element **,
-                            const char **, const size_t * lengths);
+                            const char **, const size_t * lengths, const uint32_t *asin);
+
+    void incRefs(const void *) const;
+    void decRefs(const void *) const;
+    bool getHasReferences() const {return mHasReference;}
+
+    bool isEqual(const Element *other) const;
 
 protected:
     // deallocate any components that are part of this element.
@@ -72,9 +81,12 @@
     typedef struct {
         String8 name;
         ObjectBaseRef<const Element> e;
+        uint32_t offsetBits;
+        uint32_t arraySize;
     } ElementField_t;
     ElementField_t *mFields;
     size_t mFieldCount;
+    bool mHasReference;
 
 
     Element(Context *);
@@ -89,8 +101,17 @@
     ElementState();
     ~ElementState();
 
+    void elementBuilderBegin();
+    void elementBuilderAdd(const Element *e, const char *nameStr, uint32_t arraySize);
+    const Element *elementBuilderCreate(Context *rsc);
+
     // Cache of all existing elements.
-    Vector<const Element *> mElements;
+    Vector<Element *> mElements;
+private:
+    Vector<const Element *> mBuilderElements;
+    Vector<const char*> mBuilderNameStrings;
+    Vector<size_t> mBuilderNameLengths;
+    Vector<uint32_t> mBuilderArrays;
 };
 
 
diff --git a/libs/rs/rsFileA3D.cpp b/libs/rs/rsFileA3D.cpp
index e3272c5..c90edc2 100644
--- a/libs/rs/rsFileA3D.cpp
+++ b/libs/rs/rsFileA3D.cpp
@@ -15,81 +15,193 @@
  * 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;
+
+    uint32_t numIndexEntries = headerStream->loadU32();
+    for(uint32_t i = 0; i < numIndexEntries; i ++) {
+        A3DIndexEntry *entry = new A3DIndexEntry();
+        headerStream->loadString(&entry->mObjectName);
+        LOGV("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)
+{
+    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);
+
+    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);
+
+    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;
 
-    LOGE("file open 1");
+    LOGV("file open 1");
     len = fread(magicString, 1, 12, f);
     if ((len != 12) ||
         memcmp(magicString, "Android3D_ff", 12)) {
         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);
+    LOGV("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 +211,252 @@
         return false;
     }
 
-    LOGE("file start processing");
-    return process(rsc);
+    mReadStream = new IStream(mData, mUse64BitOffsets);
+
+    LOGV("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_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);
+    LOGV("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..a951005
--- /dev/null
+++ b/libs/rs/rsFont.cpp
@@ -0,0 +1,889 @@
+
+/*
+ * 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 <cutils/properties.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, int32_t x, int32_t y)
+{
+    FontState *state = &mRSC->mStateFont;
+
+    int32_t nPenX = x + glyph->mBitmapLeft;
+    int32_t nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
+
+    float u1 = glyph->mBitmapMinU;
+    float u2 = glyph->mBitmapMaxU;
+    float v1 = glyph->mBitmapMinV;
+    float v2 = glyph->mBitmapMaxV;
+
+    int32_t width = (int32_t) glyph->mBitmapWidth;
+    int32_t height = (int32_t) glyph->mBitmapHeight;
+
+    state->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, int32_t x, int32_t y,
+                           uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+    int32_t nPenX = x + glyph->mBitmapLeft;
+    int32_t nPenY = y + glyph->mBitmapTop;
+
+    uint32_t endX = glyph->mBitmapMinX + glyph->mBitmapWidth;
+    uint32_t endY = glyph->mBitmapMinY + glyph->mBitmapHeight;
+
+    FontState *state = &mRSC->mStateFont;
+    uint32_t cacheWidth = state->getCacheTextureType()->getDimX();
+    const uint8_t* cacheBuffer = state->getTextTextureData();
+
+    uint32_t cacheX = 0, cacheY = 0;
+    int32_t bX = 0, bY = 0;
+    for (cacheX = glyph->mBitmapMinX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
+        for (cacheY = glyph->mBitmapMinY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
+            if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) {
+                LOGE("Skipping invalid index");
+                continue;
+            }
+            uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
+            bitmap[bY * bitmapW + bX] = tempCol;
+        }
+    }
+
+}
+
+void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y, Rect *bounds) {
+    int32_t nPenX = x + glyph->mBitmapLeft;
+    int32_t nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
+
+    int32_t width = (int32_t) glyph->mBitmapWidth;
+    int32_t height = (int32_t) glyph->mBitmapHeight;
+
+    if (bounds->bottom > nPenY) {
+        bounds->bottom = nPenY;
+    }
+    if (bounds->left > nPenX) {
+        bounds->left = nPenX;
+    }
+    if (bounds->right < nPenX + width) {
+        bounds->right = nPenX + width;
+    }
+    if (bounds->top < nPenY + height) {
+        bounds->top = nPenY + height;
+    }
+}
+
+void Font::renderUTF(const char *text, uint32_t len, int32_t x, int32_t y,
+                     uint32_t start, int32_t numGlyphs,
+                     RenderMode mode, Rect *bounds,
+                     uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH)
+{
+    if(!mInitialized || numGlyphs == 0 || text == NULL || len == 0) {
+        return;
+    }
+
+    if(mode == Font::MEASURE) {
+        if (bounds == NULL) {
+            LOGE("No return rectangle provided to measure text");
+            return;
+        }
+        // Reset min and max of the bounding box to something large
+        bounds->set(1e6, -1e6, -1e6, 1e6);
+    }
+
+    int32_t penX = x, penY = y;
+    int32_t 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 = getCachedUTFChar(utfChar);
+
+        // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
+        if(cachedGlyph->mIsValid) {
+            switch(mode) {
+            case FRAMEBUFFER:
+                drawCachedGlyph(cachedGlyph, penX, penY);
+                break;
+            case BITMAP:
+                drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
+                break;
+            case MEASURE:
+                measureCachedGlyph(cachedGlyph, penX, penY, bounds);
+                break;
+            }
+        }
+
+        penX += (cachedGlyph->mAdvance.x >> 6);
+
+        // If we were given a specific number of glyphs, decrement
+        if(numGlyphs > 0) {
+            glyphsLeft --;
+        }
+    }
+}
+
+Font::CachedGlyphInfo* Font::getCachedUTFChar(int32_t utfChar) {
+
+    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);
+    }
+
+    return cachedGlyph;
+}
+
+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)
+{
+    rsc->mStateFont.checkInit();
+    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);
+        rsc->mStateFont.precacheLatin(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);
+
+    // Get the renderer properties
+    char property[PROPERTY_VALUE_MAX];
+
+    // Get the gamma
+    float gamma = DEFAULT_TEXT_GAMMA;
+    if (property_get(PROPERTY_TEXT_GAMMA, property, NULL) > 0) {
+        LOGD("  Setting text gamma to %s", property);
+        gamma = atof(property);
+    } else {
+        LOGD("  Using default text gamma of %.2f", DEFAULT_TEXT_GAMMA);
+    }
+
+    // Get the black gamma threshold
+    int32_t blackThreshold = DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD;
+    if (property_get(PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD, property, NULL) > 0) {
+        LOGD("  Setting text black gamma threshold to %s", property);
+        blackThreshold = atoi(property);
+    } else {
+        LOGD("  Using default text black gamma threshold of %d",
+                DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD);
+    }
+    mBlackThreshold = (float)(blackThreshold) / 255.0f;
+
+    // Get the white gamma threshold
+    int32_t whiteThreshold = DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD;
+    if (property_get(PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD, property, NULL) > 0) {
+        LOGD("  Setting text white gamma threshold to %s", property);
+        whiteThreshold = atoi(property);
+    } else {
+        LOGD("  Using default white black gamma threshold of %d",
+                DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD);
+    }
+    mWhiteThreshold = (float)(whiteThreshold) / 255.0f;
+
+    // Compute the gamma tables
+    mBlackGamma = gamma;
+    mWhiteGamma = 1.0f / gamma;
+}
+
+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();
+
+    uint8_t *cacheBuffer = (uint8_t*)mTextTexture->getPtr();
+    uint8_t *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 ++) {
+            uint8_t 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(mRSC, 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()
+{
+    String8 shaderString("varying vec2 varTex0;\n");
+    shaderString.append("void main() {\n");
+    shaderString.append("  lowp vec4 col = UNI_Color;\n");
+    shaderString.append("  col.a = texture2D(UNI_Tex0, varTex0.xy).a;\n");
+    shaderString.append("  col.a = pow(col.a, UNI_Gamma);\n");
+    shaderString.append("  gl_FragColor = col;\n");
+    shaderString.append("}\n");
+
+    const Element *colorElem = Element::create(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 4);
+    const Element *gammaElem = Element::create(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 1);
+    mRSC->mStateElement.elementBuilderBegin();
+    mRSC->mStateElement.elementBuilderAdd(colorElem, "Color", 1);
+    mRSC->mStateElement.elementBuilderAdd(gammaElem, "Gamma", 1);
+    const Element *constInput = mRSC->mStateElement.elementBuilderCreate(mRSC);
+
+    Type *inputType = new Type(mRSC);
+    inputType->setElement(constInput);
+    inputType->setDimX(1);
+    inputType->compute();
+
+    uint32_t tmp[4];
+    tmp[0] = RS_PROGRAM_PARAM_CONSTANT;
+    tmp[1] = (uint32_t)inputType;
+    tmp[2] = RS_PROGRAM_PARAM_TEXTURE_COUNT;
+    tmp[3] = 1;
+
+    mFontShaderFConstant.set(new Allocation(mRSC, inputType));
+    ProgramFragment *pf = new ProgramFragment(mRSC, shaderString.string(),
+                                              shaderString.length(), tmp, 4);
+    mFontShaderF.set(pf);
+    mFontShaderF->bindAllocation(mRSC, mFontShaderFConstant.get(), 0);
+
+    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(mRSC, 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
+    int32_t 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(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 ++) {
+        int32_t i6 = i * 6;
+        int32_t 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();
+    uint32_t arraySizes[2] = {1, 1};
+
+    const Element *vertexDataElem = Element::create(mRSC, 2, elemArray, nameArray, lengths, arraySizes);
+
+    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();
+
+    // We store a string with letters in a rough frequency of occurrence
+    mLatinPrecache = String8(" eisarntolcdugpmhbyfvkwzxjq");
+    mLatinPrecache += String8("EISARNTOLCDUGPMHBYFVKWZXJQ");
+    mLatinPrecache += String8(",.?!()-+@;:`'");
+    mLatinPrecache += String8("0123456789");
+
+    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(mConstantsDirty) {
+        mFontShaderFConstant->data(mRSC, &mConstants, sizeof(mConstants));
+        mConstantsDirty = 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, "ATTRIB_position");
+    va.add(GL_FLOAT, 2, 20, false, (uint32_t)tex, "ATTRIB_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;
+    }
+}
+
+uint32_t FontState::getRemainingCacheCapacity() {
+    uint32_t remainingCapacity = 0;
+    uint32_t 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 FontState::precacheLatin(Font *font) {
+    // Remaining capacity is measured in %
+    uint32_t remainingCapacity = getRemainingCacheCapacity();
+    uint32_t precacheIdx = 0;
+    while(remainingCapacity > 25 && precacheIdx < mLatinPrecache.size()) {
+        font->getCachedUTFChar((int32_t)mLatinPrecache[precacheIdx]);
+        remainingCapacity = getRemainingCacheCapacity();
+        precacheIdx ++;
+    }
+}
+
+
+void FontState::renderText(const char *text, uint32_t len, int32_t x, int32_t y,
+                           uint32_t startIndex, int32_t numGlyphs,
+                           Font::RenderMode mode,
+                           Font::Rect *bounds,
+                           uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH)
+{
+    checkInit();
+
+    // 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, x, y, startIndex, numGlyphs,
+                           mode, bounds, bitmap, bitmapW, bitmapH);
+
+    if(mCurrentQuadIndex != 0) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+}
+
+void FontState::measureText(const char *text, uint32_t len, Font::Rect *bounds) {
+    renderText(text, len, 0, 0, 0, -1, Font::MEASURE, bounds);
+}
+
+void FontState::setFontColor(float r, float g, float b, float a) {
+    mConstants.mFontColor[0] = r;
+    mConstants.mFontColor[1] = g;
+    mConstants.mFontColor[2] = b;
+    mConstants.mFontColor[3] = a;
+
+    mConstants.mGamma = 1.0f;
+    const float luminance = (r * 2.0f + g * 5.0f + b) / 8.0f;
+    if (luminance <= mBlackThreshold) {
+        mConstants.mGamma = mBlackGamma;
+    } else if (luminance >= mWhiteThreshold) {
+        mConstants.mGamma = mWhiteGamma;
+    }
+    mConstantsDirty = true;
+}
+
+void FontState::getFontColor(float *r, float *g, float *b, float *a) const {
+    *r = mConstants.mFontColor[0];
+    *g = mConstants.mFontColor[1];
+    *b = mConstants.mFontColor[2];
+    *a = mConstants.mFontColor[3];
+}
+
+void FontState::deinit(Context *rsc)
+{
+    mInitialized = false;
+
+    mFontShaderFConstant.clear();
+
+    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..0012b84
--- /dev/null
+++ b/libs/rs/rsFont.h
@@ -0,0 +1,271 @@
+/*
+ * 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 {
+
+// Gamma (>= 1.0, <= 10.0)
+#define PROPERTY_TEXT_GAMMA "ro.text_gamma"
+#define PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD "ro.text_gamma.black_threshold"
+#define PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD "ro.text_gamma.white_threshold"
+
+#define DEFAULT_TEXT_GAMMA 1.4f
+#define DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD 64
+#define DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD 192
+
+class FontState;
+
+class Font : public ObjectBase
+{
+public:
+    enum RenderMode {
+        FRAMEBUFFER,
+        BITMAP,
+        MEASURE,
+    };
+
+    struct Rect {
+        int32_t left;
+        int32_t top;
+        int32_t right;
+        int32_t bottom;
+        void set(int32_t l, int32_t r, int32_t t, int32_t b) {
+            left = l;
+            right = r;
+            top = t;
+            bottom = b;
+        }
+    };
+
+    ~Font();
+
+    // 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;
+
+    // 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, int32_t x, int32_t y,
+                   uint32_t start, int32_t numGlyphs,
+                   RenderMode mode = FRAMEBUFFER, Rect *bounds = NULL,
+                   uint8_t *bitmap = NULL, uint32_t bitmapW = 0, uint32_t bitmapH = 0);
+
+    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* getCachedUTFChar(int32_t utfChar);
+
+    CachedGlyphInfo *cacheGlyph(uint32_t glyph);
+    void updateGlyphCache(CachedGlyphInfo *glyph);
+    void measureCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y, Rect *bounds);
+    void drawCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y);
+    void drawCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y,
+                         uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
+};
+
+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, int32_t x, int32_t y,
+                    uint32_t startIndex = 0, int numGlyphs = -1,
+                    Font::RenderMode mode = Font::FRAMEBUFFER,
+                    Font::Rect *bounds = NULL,
+                    uint8_t *bitmap = NULL, uint32_t bitmapW = 0, uint32_t bitmapH = 0);
+
+    void measureText(const char *text, uint32_t len, Font::Rect *bounds);
+
+    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;
+        bool mDirty;
+
+        CacheTextureLine(uint32_t maxHeight, uint32_t maxWidth, uint32_t currentRow, uint32_t currentCol) :
+            mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow), mCurrentCol(currentCol),
+            mDirty(false)  {
+        }
+
+        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;
+               mDirty = true;
+               return true;
+            }
+
+            return false;
+        }
+    };
+
+    Vector<CacheTextureLine*> mCacheLines;
+    uint32_t getRemainingCacheCapacity();
+
+    void precacheLatin(Font *font);
+    String8 mLatinPrecache;
+
+    Context *mRSC;
+
+    struct {
+        float mFontColor[4];
+        float mGamma;
+    } mConstants;
+    bool mConstantsDirty;
+
+    float mBlackGamma;
+    float mWhiteGamma;
+
+    float mBlackThreshold;
+    float mWhiteThreshold;
+
+    // Free type library, we only need one copy
+    FT_Library mLibrary;
+    FT_Library getLib();
+    Vector<Font*> mActiveFonts;
+
+    // Render state for the font
+    ObjectBaseRef<Allocation> mFontShaderFConstant;
+    ObjectBaseRef<ProgramFragment> mFontShaderF;
+    ObjectBaseRef<Sampler> mFontSampler;
+    ObjectBaseRef<ProgramStore> mFontProgramStore;
+    void initRenderState();
+
+    // Texture to cache glyph bitmaps
+    ObjectBaseRef<Allocation> mTextTexture;
+    void initTextTexture();
+    const uint8_t* getTextTextureData() const {
+        return (uint8_t*)mTextTexture->getPtr();
+    }
+
+    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..c02fd42 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;
@@ -45,3 +96,26 @@
 
 }
 
+static inline void rsHCAPI_Allocation1DSubElementData (RsContext rsc, RsAllocation va, uint32_t x, const void * data, uint32_t comp_offset, uint32_t sizeBytes)
+{
+    ThreadIO *io = &((Context *)rsc)->mIO;
+    uint32_t size = sizeof(RS_CMD_Allocation1DSubElementData);
+    if (sizeBytes < DATA_SYNC_SIZE) {
+        size += (sizeBytes + 3) & ~3;
+    }
+    RS_CMD_Allocation1DSubElementData *cmd = static_cast<RS_CMD_Allocation1DSubElementData *>(io->mToCore.reserve(size));
+    cmd->va = va;
+    cmd->x = x;
+    cmd->data = data;
+    cmd->comp_offset = comp_offset;
+    cmd->bytes = sizeBytes;
+    if (sizeBytes < DATA_SYNC_SIZE) {
+        cmd->data = (void *)(cmd+1);
+        memcpy(cmd+1, data, sizeBytes);
+        io->mToCore.commit(RS_CMD_ID_Allocation1DSubElementData, size);
+    } else {
+        io->mToCore.commitSync(RS_CMD_ID_Allocation1DSubElementData, size);
+    }
+
+}
+
diff --git a/libs/rs/rsLight.cpp b/libs/rs/rsLight.cpp
deleted file mode 100644
index 6f2cf3e..0000000
--- a/libs/rs/rsLight.cpp
+++ /dev/null
@@ -1,130 +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 <GLES/gl.h>
-
-using namespace android;
-using namespace android::renderscript;
-
-
-Light::Light(Context *rsc, bool isLocal, bool isMono) : ObjectBase(rsc)
-{
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
-    mIsLocal = isLocal;
-    mIsMono = isMono;
-
-    mPosition[0] = 0;
-    mPosition[1] = 0;
-    mPosition[2] = 1;
-    mPosition[3] = 0;
-
-    mColor[0] = 1.f;
-    mColor[1] = 1.f;
-    mColor[2] = 1.f;
-    mColor[3] = 1.f;
-}
-
-Light::~Light()
-{
-}
-
-void Light::setPosition(float x, float y, float z)
-{
-    mPosition[0] = x;
-    mPosition[1] = y;
-    mPosition[2] = z;
-}
-
-void Light::setColor(float r, float g, float b)
-{
-    mColor[0] = r;
-    mColor[1] = g;
-    mColor[2] = b;
-}
-
-void Light::setupGL(uint32_t num) const
-{
-    glLightfv(GL_LIGHT0 + num, GL_DIFFUSE, mColor);
-    glLightfv(GL_LIGHT0 + num, GL_SPECULAR, mColor);
-    glLightfv(GL_LIGHT0 + num, GL_POSITION, mPosition);
-}
-
-////////////////////////////////////////////
-
-LightState::LightState()
-{
-    clear();
-}
-
-LightState::~LightState()
-{
-}
-
-void LightState::clear()
-{
-    mIsLocal = false;
-    mIsMono = false;
-}
-
-
-////////////////////////////////////////////////////
-//
-
-namespace android {
-namespace renderscript {
-
-void rsi_LightBegin(Context *rsc)
-{
-    rsc->mStateLight.clear();
-}
-
-void rsi_LightSetLocal(Context *rsc, bool isLocal)
-{
-    rsc->mStateLight.mIsLocal = isLocal;
-}
-
-void rsi_LightSetMonochromatic(Context *rsc, bool isMono)
-{
-    rsc->mStateLight.mIsMono = isMono;
-}
-
-RsLight rsi_LightCreate(Context *rsc)
-{
-    Light *l = new Light(rsc, rsc->mStateLight.mIsLocal,
-                         rsc->mStateLight.mIsMono);
-    l->incUserRef();
-    return l;
-}
-
-void rsi_LightSetColor(Context *rsc, RsLight vl, float r, float g, float b)
-{
-    Light *l = static_cast<Light *>(vl);
-    l->setColor(r, g, b);
-}
-
-void rsi_LightSetPosition(Context *rsc, RsLight vl, float x, float y, float z)
-{
-    Light *l = static_cast<Light *>(vl);
-    l->setPosition(x, y, z);
-}
-
-
-
-}
-}
diff --git a/libs/rs/rsLight.h b/libs/rs/rsLight.h
deleted file mode 100644
index d8796e6..0000000
--- a/libs/rs/rsLight.h
+++ /dev/null
@@ -1,64 +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_LIGHT_H
-#define ANDROID_LIGHT_H
-
-
-#include "rsObjectBase.h"
-
-// ---------------------------------------------------------------------------
-namespace android {
-namespace renderscript {
-
-
-// An element is a group of Components that occupies one cell in a structure.
-class Light : public ObjectBase
-{
-public:
-    Light(Context *, bool isLocal, bool isMono);
-    virtual ~Light();
-
-    // Values, mutable after creation.
-    void setPosition(float x, float y, float z);
-    void setColor(float r, float g, float b);
-
-    void setupGL(uint32_t num) const;
-
-protected:
-    float mColor[4];
-    float mPosition[4];
-    bool mIsLocal;
-    bool mIsMono;
-};
-
-
-class LightState {
-public:
-    LightState();
-    ~LightState();
-
-    void clear();
-
-    bool mIsMono;
-    bool mIsLocal;
-};
-
-
-}
-}
-#endif //ANDROID_LIGHT_H
-
diff --git a/libs/rs/rsLocklessFifo.cpp b/libs/rs/rsLocklessFifo.cpp
index c796520..019ea72 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;
@@ -165,6 +169,30 @@
     //dumpState("next");
 }
 
+bool LocklessCommandFifo::makeSpaceNonBlocking(uint32_t bytes)
+{
+    //dumpState("make space non-blocking");
+    if ((mPut+bytes) > mEnd) {
+        // Need to loop regardless of where get is.
+        if((mGet > mPut) && (mBuffer+4 >= mGet)) {
+            return false;
+        }
+
+        // Toss in a reset then the normal wait for space will do the rest.
+        reinterpret_cast<uint16_t *>(mPut)[0] = 0;
+        reinterpret_cast<uint16_t *>(mPut)[1] = 0;
+        mPut = mBuffer;
+        mSignalToWorker.set();
+    }
+
+    // it will fit here so we just need to wait for space.
+    if(getFreeSpace() < bytes) {
+        return false;
+    }
+
+    return true;
+}
+
 void LocklessCommandFifo::makeSpace(uint32_t bytes)
 {
     //dumpState("make space");
@@ -178,6 +206,7 @@
         reinterpret_cast<uint16_t *>(mPut)[0] = 0;
         reinterpret_cast<uint16_t *>(mPut)[1] = 0;
         mPut = mBuffer;
+        mSignalToWorker.set();
     }
 
     // it will fit here so we just need to wait for space.
@@ -189,82 +218,6 @@
 
 void LocklessCommandFifo::dumpState(const char *s) const
 {
-    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);
-    }
+    LOGV("%s %p  put %p, get %p,  buf %p,  end %p", s, this, mPut, mGet, mBuffer, mEnd);
 }
 
diff --git a/libs/rs/rsLocklessFifo.h b/libs/rs/rsLocklessFifo.h
index d0a4356..b8ceeed 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,18 +50,19 @@
     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();
 
     void makeSpace(uint32_t bytes);
+    bool makeSpaceNonBlocking(uint32_t bytes);
 
     bool isEmpty() const;
     uint32_t getFreeSpace() const;
@@ -88,4 +74,5 @@
 
 
 }
+}
 #endif
diff --git a/libs/rs/rsMatrix.cpp b/libs/rs/rsMatrix.cpp
index 2f21405..94eef13 100644
--- a/libs/rs/rsMatrix.cpp
+++ b/libs/rs/rsMatrix.cpp
@@ -73,7 +73,7 @@
     s = sinf(rot);
 
     const float len = sqrtf(x*x + y*y + z*z);
-    if (!(len != 1)) {
+    if (len != 1) {
         const float recipLen = 1.f / len;
         x *= recipLen;
         y *= recipLen;
diff --git a/libs/rs/rsMesh.cpp b/libs/rs/rsMesh.cpp
index d595b4e..8e43f24 100644
--- a/libs/rs/rsMesh.cpp
+++ b/libs/rs/rsMesh.cpp
@@ -14,28 +14,256 @@
  * 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);
+        }
+    }
+}
+
+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;
+}
+
+void Mesh::computeBBox() {
+    float *posPtr = NULL;
+    uint32_t vectorSize = 0;
+    uint32_t stride = 0;
+    uint32_t numVerts = 0;
+    // First we need to find the position ptr and stride
+    for (uint32_t ct=0; ct < mVertexBufferCount; ct++) {
+        const Type *bufferType = mVertexBuffers[ct]->getType();
+        const Element *bufferElem = bufferType->getElement();
+
+        for (uint32_t ct=0; ct < bufferElem->getFieldCount(); ct++) {
+            if(strcmp(bufferElem->getFieldName(ct), "position") == 0) {
+                vectorSize = bufferElem->getField(ct)->getComponent().getVectorSize();
+                stride = bufferElem->getSizeBytes() / sizeof(float);
+                uint32_t offset = bufferElem->getFieldOffsetBytes(ct);
+                posPtr = (float*)((uint8_t*)mVertexBuffers[ct]->getPtr() + offset);
+                numVerts = bufferType->getDimX();
+                break;
+            }
+        }
+        if(posPtr) {
+            break;
+        }
+    }
+
+    mBBoxMin[0] = mBBoxMin[1] = mBBoxMin[2] = 1e6;
+    mBBoxMax[0] = mBBoxMax[1] = mBBoxMax[2] = -1e6;
+    if(!posPtr) {
+        LOGE("Unable to compute bounding box");
+        mBBoxMin[0] = mBBoxMin[1] = mBBoxMin[2] = 0.0f;
+        mBBoxMax[0] = mBBoxMax[1] = mBBoxMax[2] = 0.0f;
+        return;
+    }
+
+    for(uint32_t i = 0; i < numVerts; i ++) {
+        for(uint32_t v = 0; v < vectorSize; v ++) {
+            mBBoxMin[v] = rsMin(mBBoxMin[v], posPtr[v]);
+            mBBoxMax[v] = rsMax(mBBoxMax[v], posPtr[v]);
+        }
+        posPtr += stride;
+    }
+}
 
 
 MeshContext::MeshContext()
@@ -46,3 +274,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..ed01c38 100644
--- a/libs/rs/rsMesh.h
+++ b/libs/rs/rsMesh.h
@@ -32,45 +32,40 @@
     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();
+    // Bounding volumes
+    float mBBoxMin[3];
+    float mBBoxMax[3];
+    void computeBBox();
+
 protected:
 };
 
@@ -88,3 +83,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..f69cb15 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);
@@ -58,6 +61,7 @@
     if (mRSC) {
         remove();
     }
+    rsAssert(rsc);
     mRSC = rsc;
     if (rsc) {
         add();
@@ -113,18 +117,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
@@ -197,3 +195,15 @@
     }
 }
 
+bool ObjectBase::isValid(const Context *rsc, const ObjectBase *obj)
+{
+    const ObjectBase * o = rsc->mObjHead;
+    while (o) {
+        if (o == obj) {
+            return true;
+        }
+        o = o->mNext;
+    }
+    return false;
+}
+
diff --git a/libs/rs/rsObjectBase.h b/libs/rs/rsObjectBase.h
index bb03b87..59fb4a6 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,10 @@
     static void dumpAll(Context *rsc);
 
     virtual void dumpLOGV(const char *prefix) const;
+    virtual void serialize(OStream *stream) const = 0;
+    virtual RsA3DClassID getClassId() const = 0;
+
+    static bool isValid(const Context *rsc, const ObjectBase *obj);
 
 protected:
     const char *mAllocFile;
@@ -64,7 +69,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..10e00e6 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -14,16 +14,21 @@
  * 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;
 
-
 Program::Program(Context *rsc) : ObjectBase(rsc)
 {
     mAllocFile = __FILE__;
@@ -32,7 +37,10 @@
     mShaderID = 0;
     mAttribCount = 0;
     mUniformCount = 0;
+    mTextureCount = 0;
 
+    mTextures = NULL;
+    mSamplers = NULL;
     mInputElements = NULL;
     mOutputElements = NULL;
     mConstantTypes = NULL;
@@ -40,6 +48,7 @@
     mOutputCount = 0;
     mConstantCount = 0;
     mIsValid = false;
+    mIsInternal = false;
 }
 
 Program::Program(Context *rsc, const char * shaderText, uint32_t shaderLength,
@@ -73,6 +82,8 @@
         }
     }
 
+    mTextures = new ObjectBaseRef<Allocation>[mTextureCount];
+    mSamplers = new ObjectBaseRef<Sampler>[mTextureCount];
     mInputElements = new ObjectBaseRef<Element>[mInputCount];
     mOutputElements = new ObjectBaseRef<Element>[mOutputCount];
     mConstantTypes = new ObjectBaseRef<Type>[mConstantCount];
@@ -91,15 +102,37 @@
             mConstantTypes[constant++].set(reinterpret_cast<Type *>(params[ct+1]));
         }
     }
+    mIsInternal = false;
+    uint32_t internalTokenLen = strlen(RS_SHADER_INTERNAL);
+    if(shaderLength > internalTokenLen &&
+       strncmp(RS_SHADER_INTERNAL, shaderText, internalTokenLen) == 0) {
+        mIsInternal = true;
+        shaderText += internalTokenLen;
+        shaderLength -= internalTokenLen;
+    }
     mUserShader.setTo(shaderText, shaderLength);
 }
 
 Program::~Program()
 {
-    for (uint32_t ct=0; ct < MAX_UNIFORMS; ct++) {
-        bindAllocation(NULL, ct);
+    if(mRSC->props.mLogShaders) {
+        LOGV("Program::~Program with shader id %u", mShaderID);
     }
 
+    if(mShaderID) {
+        glDeleteShader(mShaderID);
+    }
+
+    for (uint32_t ct=0; ct < MAX_UNIFORMS; ct++) {
+        bindAllocation(NULL, NULL, ct);
+    }
+
+    for (uint32_t ct=0; ct < mTextureCount; ct++) {
+        bindTexture(NULL, ct, NULL);
+        bindSampler(NULL, ct, NULL);
+    }
+    delete[] mTextures;
+    delete[] mSamplers;
     delete[] mInputElements;
     delete[] mOutputElements;
     delete[] mConstantTypes;
@@ -109,8 +142,22 @@
 }
 
 
-void Program::bindAllocation(Allocation *alloc, uint32_t slot)
+void Program::bindAllocation(Context *rsc, Allocation *alloc, uint32_t slot)
 {
+    if (alloc != NULL) {
+        if (slot >= mConstantCount) {
+            LOGE("Attempt to bind alloc at slot %u, on shader id %u, but const count is %u",
+                 slot, (uint32_t)this, mConstantCount);
+            rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind allocation");
+            return;
+        }
+        if (!alloc->getType()->isEqual(mConstantTypes[slot].get())) {
+            LOGE("Attempt to bind alloc at slot %u, on shader id %u, but types mismatch",
+                 slot, (uint32_t)this);
+            rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind allocation");
+            return;
+        }
+    }
     if (mConstants[slot].get() == alloc) {
         return;
     }
@@ -124,10 +171,11 @@
     mDirty = true;
 }
 
-void Program::bindTexture(uint32_t slot, Allocation *a)
+void Program::bindTexture(Context *rsc, uint32_t slot, Allocation *a)
 {
-    if (slot >= MAX_TEXTURE) {
-        LOGE("Attempt to bind a texture to a slot > MAX_TEXTURE");
+    if (slot >= mTextureCount) {
+        LOGE("Attempt to bind texture to slot %u but tex count is %u", slot, mTextureCount);
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind texture");
         return;
     }
 
@@ -136,10 +184,11 @@
     mDirty = true;
 }
 
-void Program::bindSampler(uint32_t slot, Sampler *s)
+void Program::bindSampler(Context *rsc, uint32_t slot, Sampler *s)
 {
-    if (slot >= MAX_TEXTURE) {
-        LOGE("Attempt to bind a Sampler to a slot > MAX_TEXTURE");
+    if (slot >= mTextureCount) {
+        LOGE("Attempt to bind sampler to slot %u but tex count is %u", slot, mTextureCount);
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind sampler");
         return;
     }
 
@@ -235,7 +284,159 @@
     mUserShader.setTo(txt, len);
 }
 
+void Program::appendUserConstants() {
+    for (uint32_t ct=0; ct < mConstantCount; ct++) {
+        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());
+            if(f->getType() == RS_TYPE_MATRIX_4X4) {
+                mShader.append("uniform mat4 UNI_");
+            }
+            else if(f->getType() == RS_TYPE_MATRIX_3X3) {
+                mShader.append("uniform mat3 UNI_");
+            }
+            else if(f->getType() == RS_TYPE_MATRIX_2X2) {
+                mShader.append("uniform mat2 UNI_");
+            }
+            else {
+                switch(f->getComponent().getVectorSize()) {
+                case 1: mShader.append("uniform float UNI_"); break;
+                case 2: mShader.append("uniform vec2 UNI_"); break;
+                case 3: mShader.append("uniform vec3 UNI_"); break;
+                case 4: mShader.append("uniform vec4 UNI_"); break;
+                default:
+                    rsAssert(0);
+                }
+            }
+
+            mShader.append(fn);
+            mShader.append(";\n");
+        }
+    }
+}
+
+void Program::setupUserConstants(Context *rsc, ShaderCache *sc, bool isFragment) {
+    uint32_t uidx = 0;
+    for (uint32_t ct=0; ct < mConstantCount; ct++) {
+        Allocation *alloc = mConstants[ct].get();
+        if (!alloc) {
+            LOGE("Attempting to set constants on shader id %u, but alloc at slot %u is not set", (uint32_t)this, ct);
+            rsc->setError(RS_ERROR_BAD_SHADER, "No constant allocation bound");
+            continue;
+        }
+
+        const uint8_t *data = static_cast<const uint8_t *>(alloc->getPtr());
+        const Element *e = mConstantTypes[ct]->getElement();
+        for (uint32_t field=0; field < e->getFieldCount(); field++) {
+            const Element *f = e->getField(field);
+            const char *fieldName = e->getFieldName(field);
+            // If this field is padding, skip it
+            if(fieldName[0] == '#') {
+                continue;
+            }
+
+            uint32_t offset = e->getFieldOffsetBytes(field);
+            const float *fd = reinterpret_cast<const float *>(&data[offset]);
+
+            int32_t slot = -1;
+            if(!isFragment) {
+                slot = sc->vtxUniformSlot(uidx);
+            }
+            else {
+                slot = sc->fragUniformSlot(uidx);
+            }
+
+            if(rsc->props.mLogShadersUniforms) {
+                LOGV("Uniform  slot=%i, offset=%i, constant=%i, field=%i, uidx=%i, name=%s", slot, offset, ct, field, uidx, fieldName);
+            }
+            if (slot >= 0) {
+                if(f->getType() == RS_TYPE_MATRIX_4X4) {
+                    if(rsc->props.mLogShadersUniforms) {
+                        LOGV("Matrix4x4");
+                        LOGV("{%f, %f, %f, %f",  fd[0], fd[4], fd[8], fd[12]);
+                        LOGV(" %f, %f, %f, %f",  fd[1], fd[5], fd[9], fd[13]);
+                        LOGV(" %f, %f, %f, %f",  fd[2], fd[6], fd[10], fd[14]);
+                        LOGV(" %f, %f, %f, %f}", fd[3], fd[7], fd[11], fd[15]);
+                    }
+                    glUniformMatrix4fv(slot, 1, GL_FALSE, fd);
+                }
+                else if(f->getType() == RS_TYPE_MATRIX_3X3) {
+                    if(rsc->props.mLogShadersUniforms) {
+                        LOGV("Matrix3x3");
+                        LOGV("{%f, %f, %f",  fd[0], fd[3], fd[6]);
+                        LOGV(" %f, %f, %f",  fd[1], fd[4], fd[7]);
+                        LOGV(" %f, %f, %f}", fd[2], fd[5], fd[8]);
+                    }
+                    glUniformMatrix3fv(slot, 1, GL_FALSE, fd);
+                }
+                else if(f->getType() == RS_TYPE_MATRIX_2X2) {
+                    if(rsc->props.mLogShadersUniforms){
+                        LOGV("Matrix2x2");
+                        LOGV("{%f, %f",  fd[0], fd[2]);
+                        LOGV(" %f, %f}", fd[1], fd[3]);
+                    }
+                    glUniformMatrix2fv(slot, 1, GL_FALSE, fd);
+                }
+                else {
+                    switch(f->getComponent().getVectorSize()) {
+                    case 1:
+                        if(rsc->props.mLogShadersUniforms) {
+                            LOGV("Uniform 1 = %f", fd[0]);
+                        }
+                        glUniform1fv(slot, 1, fd);
+                        break;
+                    case 2:
+                        if(rsc->props.mLogShadersUniforms) {
+                            LOGV("Uniform 2 = %f %f", fd[0], fd[1]);
+                        }
+                        glUniform2fv(slot, 1, fd);
+                        break;
+                    case 3:
+                        if(rsc->props.mLogShadersUniforms) {
+                            LOGV("Uniform 3 = %f %f %f", fd[0], fd[1], fd[2]);
+                        }
+                        glUniform3fv(slot, 1, fd);
+                        break;
+                    case 4:
+                        if(rsc->props.mLogShadersUniforms) {
+                            LOGV("Uniform 4 = %f %f %f %f", fd[0], fd[1], fd[2], fd[3]);
+                        }
+                        glUniform4fv(slot, 1, fd);
+                        break;
+                    default:
+                        rsAssert(0);
+                    }
+                }
+            }
+            uidx ++;
+        }
+    }
+}
+
+void Program::initAddUserElement(const Element *e, String8 *names, uint32_t *count, const char *prefix)
+{
+    rsAssert(e->getFieldCount());
+    for (uint32_t ct=0; ct < e->getFieldCount(); ct++) {
+        const Element *ce = e->getField(ct);
+        if (ce->getFieldCount()) {
+            initAddUserElement(ce, names, count, prefix);
+        }
+        else if(e->getFieldName(ct)[0] != '#') {
+            String8 tmp(prefix);
+            tmp.append(e->getFieldName(ct));
+            names[*count].setTo(tmp.string());
+            (*count)++;
+        }
+    }
+}
 
 namespace android {
 namespace renderscript {
@@ -244,19 +445,19 @@
 void rsi_ProgramBindConstants(Context *rsc, RsProgram vp, uint32_t slot, RsAllocation constants)
 {
     Program *p = static_cast<Program *>(vp);
-    p->bindAllocation(static_cast<Allocation *>(constants), slot);
+    p->bindAllocation(rsc, static_cast<Allocation *>(constants), slot);
 }
 
 void rsi_ProgramBindTexture(Context *rsc, RsProgram vpf, uint32_t slot, RsAllocation a)
 {
     Program *p = static_cast<Program *>(vpf);
-    p->bindTexture(slot, static_cast<Allocation *>(a));
+    p->bindTexture(rsc, slot, static_cast<Allocation *>(a));
 }
 
 void rsi_ProgramBindSampler(Context *rsc, RsProgram vpf, uint32_t slot, RsSampler s)
 {
     Program *p = static_cast<Program *>(vpf);
-    p->bindSampler(slot, static_cast<Sampler *>(s));
+    p->bindSampler(rsc, slot, static_cast<Sampler *>(s));
 }
 
 }
diff --git a/libs/rs/rsProgram.h b/libs/rs/rsProgram.h
index 86f85fb..c93033b 100644
--- a/libs/rs/rsProgram.h
+++ b/libs/rs/rsProgram.h
@@ -23,29 +23,30 @@
 // ---------------------------------------------------------------------------
 namespace android {
 namespace renderscript {
-
-
 class ShaderCache;
 
+#define RS_SHADER_INTERNAL "//rs_shader_internal\n"
+#define RS_SHADER_ATTR "ATTRIB_"
+#define RS_SHADER_UNI "UNI_"
+
 class Program : public ObjectBase
 {
 public:
     const static uint32_t MAX_ATTRIBS = 8;
     const static uint32_t MAX_UNIFORMS = 16;
-    const static uint32_t MAX_TEXTURE = 2;
 
     Program(Context *);
     Program(Context *, const char * shaderText, uint32_t shaderLength,
                        const uint32_t * params, uint32_t paramLength);
     virtual ~Program();
 
-    void bindAllocation(Allocation *, uint32_t slot);
+    void bindAllocation(Context *, Allocation *, uint32_t slot);
     virtual void createShader();
 
-    bool isUserProgram() const {return mUserShader.size() > 0;}
+    bool isUserProgram() const {return !mIsInternal;}
 
-    void bindTexture(uint32_t slot, Allocation *);
-    void bindSampler(uint32_t slot, Sampler *);
+    void bindTexture(Context *, uint32_t slot, Allocation *);
+    void bindSampler(Context *, uint32_t slot, Sampler *);
 
     uint32_t getShaderID() const {return mShaderID;}
     void setShader(const char *, uint32_t len);
@@ -71,6 +72,12 @@
     uint32_t mOutputCount;
     uint32_t mConstantCount;
     bool mIsValid;
+    bool mIsInternal;
+
+    // Applies to vertex and fragment shaders only
+    void appendUserConstants();
+    void setupUserConstants(Context *rsc, ShaderCache *sc, bool isFragment);
+    void initAddUserElement(const Element *e, String8 *names, uint32_t *count, const char *prefix);
 
     ObjectBaseRef<Allocation> mConstants[MAX_UNIFORMS];
 
@@ -91,8 +98,8 @@
     // and filtered.
     //
     // Constants are strictly accessed by programetic loads.
-    ObjectBaseRef<Allocation> mTextures[MAX_TEXTURE];
-    ObjectBaseRef<Sampler> mSamplers[MAX_TEXTURE];
+    ObjectBaseRef<Allocation> *mTextures;
+    ObjectBaseRef<Sampler> *mSamplers;
 
     bool loadShader(Context *, uint32_t type);
 
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index c17b94c..81b4fa4 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -14,42 +14,23 @@
  * 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;
 
-
-ProgramFragment::ProgramFragment(Context *rsc, const uint32_t * params,
-                                 uint32_t paramLength) :
-    Program(rsc)
-{
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
-    rsAssert(paramLength = 5);
-
-    mEnvModes[0] = (RsTexEnvMode)params[0];
-    mTextureFormats[0] = params[1];
-    mEnvModes[1] = (RsTexEnvMode)params[2];
-    mTextureFormats[1] = params[3];
-    mPointSpriteEnable = params[4] != 0;
-
-    mTextureEnableMask = 0;
-    if (mEnvModes[0]) {
-        mTextureEnableMask |= 1;
-    }
-    if (mEnvModes[1]) {
-        mTextureEnableMask |= 2;
-    }
-    init(rsc);
-}
-
 ProgramFragment::ProgramFragment(Context *rsc, const char * shaderText,
                                  uint32_t shaderLength, const uint32_t * params,
                                  uint32_t paramLength) :
@@ -58,100 +39,68 @@
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
 
-    init(rsc);
-    mTextureEnableMask = (1 << mTextureCount) -1;
-}
+    mConstantColor[0] = 1.f;
+    mConstantColor[1] = 1.f;
+    mConstantColor[2] = 1.f;
+    mConstantColor[3] = 1.f;
 
+    init(rsc);
+}
 
 ProgramFragment::~ProgramFragment()
 {
+    if(mShaderID) {
+        mRSC->mShaderCache.cleanupFragment(mShaderID);
+    }
 }
 
-void ProgramFragment::setupGL(const Context *rsc, ProgramFragmentState *state)
+void ProgramFragment::setConstantColor(Context *rsc, float r, float g, float b, float a)
 {
+    if(isUserProgram()) {
+        LOGE("Attempting to set fixed function emulation color on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot  set fixed function emulation color on user program");
+        return;
+    }
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to set fixed function emulation color because allocation is missing");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Unable to set fixed function emulation color because allocation is missing");
+        return;
+    }
+    mConstantColor[0] = r;
+    mConstantColor[1] = g;
+    mConstantColor[2] = b;
+    mConstantColor[3] = a;
+    memcpy(mConstants[0]->getPtr(), mConstantColor, 4*sizeof(float));
+    mDirty = true;
+}
+
+void ProgramFragment::setupGL2(Context *rsc, ProgramFragmentState *state, ShaderCache *sc)
+{
+    //LOGE("sgl2 frag1 %x", glGetError());
     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)
-{
-
-    //LOGE("sgl2 frag1 %x", glGetError());
-    if ((state->mLast.get() == this) && !mDirty) {
-        //return;
-    }
-    state->mLast.set(this);
-
     rsc->checkError("ProgramFragment::setupGL2 start");
-    for (uint32_t ct=0; ct < MAX_TEXTURE; ct++) {
+
+    rsc->checkError("ProgramFragment::setupGL2 begin uniforms");
+    setupUserConstants(rsc, sc, true);
+
+    uint32_t numTexturesToBind = mTextureCount;
+    uint32_t numTexturesAvailable = rsc->getMaxFragmentTextures();
+    if(numTexturesToBind >= numTexturesAvailable) {
+        LOGE("Attempting to bind %u textures on shader id %u, but only %u are available",
+             mTextureCount, (uint32_t)this, numTexturesAvailable);
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind more textuers than available");
+        numTexturesToBind = numTexturesAvailable;
+    }
+
+    for (uint32_t ct=0; ct < numTexturesToBind; ct++) {
         glActiveTexture(GL_TEXTURE0 + ct);
-        if (!(mTextureEnableMask & (1 << ct)) || !mTextures[ct].get()) {
+        if (!mTextures[ct].get()) {
+            LOGE("No texture bound for shader id %u, texture unit %u", (uint)this, ct);
+            rsc->setError(RS_ERROR_BAD_SHADER, "No texture bound");
             continue;
         }
 
@@ -159,16 +108,16 @@
         glBindTexture(GL_TEXTURE_2D, mTextures[ct]->getTextureID());
         rsc->checkError("ProgramFragment::setupGL2 tex bind");
         if (mSamplers[ct].get()) {
-            mSamplers[ct]->setupGL(rsc, mTextures[ct]->getType()->getIsNp2());
+            mSamplers[ct]->setupGL(rsc, mTextures[ct].get());
         } 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);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
             rsc->checkError("ProgramFragment::setupGL2 tex env");
         }
 
-        glUniform1i(sc->fragUniformSlot(ct), ct);
+        glUniform1i(sc->fragUniformSlot(mTextureUniformIndexStart + ct), ct);
         rsc->checkError("ProgramFragment::setupGL2 uniforms");
     }
 
@@ -183,112 +132,49 @@
 
 void ProgramFragment::createShader()
 {
-    mShader.setTo("precision mediump float;\n");
-    mShader.append("varying vec4 varColor;\n");
-    mShader.append("varying vec4 varTex0;\n");
-
     if (mUserShader.length() > 1) {
+        mShader.append("precision mediump float;\n");
+        appendUserConstants();
+        char buf[256];
         for (uint32_t ct=0; ct < mTextureCount; ct++) {
-            char buf[256];
-            sprintf(buf, "uniform sampler2D uni_Tex%i;\n", ct);
+            sprintf(buf, "uniform sampler2D UNI_Tex%i;\n", ct);
             mShader.append(buf);
         }
-
         mShader.append(mUserShader);
     } else {
-        uint32_t mask = mTextureEnableMask;
-        uint32_t texNum = 0;
-        while (mask) {
-            if (mask & 1) {
-                char buf[64];
-                mShader.append("uniform sampler2D uni_Tex");
-                sprintf(buf, "%i", texNum);
-                mShader.append(buf);
-                mShader.append(";\n");
-            }
-            mask >>= 1;
-            texNum++;
-        }
-
-
-        mShader.append("void main() {\n");
-        mShader.append("  vec4 col = varColor;\n");
-
-        if (mTextureEnableMask) {
-            if (mPointSpriteEnable) {
-                mShader.append("  vec2 t0 = gl_PointCoord;\n");
-            } else {
-                mShader.append("  vec2 t0 = varTex0.xy;\n");
-            }
-        }
-
-        mask = mTextureEnableMask;
-        texNum = 0;
-        while (mask) {
-            if (mask & 1) {
-                switch(mEnvModes[texNum]) {
-                case RS_TEX_ENV_MODE_NONE:
-                    rsAssert(0);
-                    break;
-                case RS_TEX_ENV_MODE_REPLACE:
-                    switch(mTextureFormats[texNum]) {
-                    case 1:
-                        mShader.append("  col.a = texture2D(uni_Tex0, t0).a;\n");
-                        break;
-                    case 2:
-                        mShader.append("  col.rgba = texture2D(uni_Tex0, t0).rgba;\n");
-                        break;
-                    case 3:
-                        mShader.append("  col.rgb = texture2D(uni_Tex0, t0).rgb;\n");
-                        break;
-                    case 4:
-                        mShader.append("  col.rgba = texture2D(uni_Tex0, t0).rgba;\n");
-                        break;
-                    }
-                    break;
-                case RS_TEX_ENV_MODE_MODULATE:
-                    switch(mTextureFormats[texNum]) {
-                    case 1:
-                        mShader.append("  col.a *= texture2D(uni_Tex0, t0).a;\n");
-                        break;
-                    case 2:
-                        mShader.append("  col.rgba *= texture2D(uni_Tex0, t0).rgba;\n");
-                        break;
-                    case 3:
-                        mShader.append("  col.rgb *= texture2D(uni_Tex0, t0).rgb;\n");
-                        break;
-                    case 4:
-                        mShader.append("  col.rgba *= texture2D(uni_Tex0, t0).rgba;\n");
-                        break;
-                    }
-                    break;
-                case RS_TEX_ENV_MODE_DECAL:
-                    mShader.append("  col = texture2D(uni_Tex0, t0);\n");
-                    break;
-                }
-
-            }
-            mask >>= 1;
-            texNum++;
-        }
-
-        //mShader.append("  col.a = 1.0;\n");
-        //mShader.append("  col.r = 0.5;\n");
-
-        mShader.append("  gl_FragColor = col;\n");
-        mShader.append("}\n");
+        LOGE("ProgramFragment::createShader cannot create program, shader code not defined");
+        rsAssert(0);
     }
 }
 
 void ProgramFragment::init(Context *rsc)
 {
-    mUniformCount = 2;
-    mUniformNames[0].setTo("uni_Tex0");
-    mUniformNames[1].setTo("uni_Tex1");
+    mUniformCount = 0;
+    if (mUserShader.size() > 0) {
+        for (uint32_t ct=0; ct < mConstantCount; ct++) {
+            initAddUserElement(mConstantTypes[ct]->getElement(), mUniformNames, &mUniformCount, RS_SHADER_UNI);
+        }
+    }
+    mTextureUniformIndexStart = mUniformCount;
+    char buf[256];
+    for (uint32_t ct=0; ct < mTextureCount; ct++) {
+        sprintf(buf, "UNI_Tex%i", ct);
+        mUniformNames[mUniformCount++].setTo(buf);
+    }
 
     createShader();
 }
 
+void ProgramFragment::serialize(OStream *stream) const
+{
+
+}
+
+ProgramFragment *ProgramFragment::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
+
 ProgramFragmentState::ProgramFragmentState()
 {
     mPF = NULL;
@@ -300,16 +186,39 @@
 
 }
 
-void ProgramFragmentState::init(Context *rsc, int32_t w, int32_t h)
+void ProgramFragmentState::init(Context *rsc)
 {
-    uint32_t tmp[5] = {
-        RS_TEX_ENV_MODE_NONE, 0,
-        RS_TEX_ENV_MODE_NONE, 0,
-        0
-    };
-    ProgramFragment *pf = new ProgramFragment(rsc, tmp, 5);
+    String8 shaderString(RS_SHADER_INTERNAL);
+    shaderString.append("varying lowp vec4 varColor;\n");
+    shaderString.append("varying vec2 varTex0;\n");
+    shaderString.append("void main() {\n");
+    shaderString.append("  lowp vec4 col = UNI_Color;\n");
+    shaderString.append("  gl_FragColor = col;\n");
+    shaderString.append("}\n");
+
+    const Element *colorElem = Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 4);
+    rsc->mStateElement.elementBuilderBegin();
+    rsc->mStateElement.elementBuilderAdd(colorElem, "Color", 1);
+    const Element *constInput = rsc->mStateElement.elementBuilderCreate(rsc);
+
+    Type *inputType = new Type(rsc);
+    inputType->setElement(constInput);
+    inputType->setDimX(1);
+    inputType->compute();
+
+    uint32_t tmp[4];
+    tmp[0] = RS_PROGRAM_PARAM_CONSTANT;
+    tmp[1] = (uint32_t)inputType;
+    tmp[2] = RS_PROGRAM_PARAM_TEXTURE_COUNT;
+    tmp[3] = 0;
+
+    Allocation *constAlloc = new Allocation(rsc, inputType);
+    ProgramFragment *pf = new ProgramFragment(rsc, shaderString.string(),
+                                              shaderString.length(), tmp, 4);
+    pf->bindAllocation(rsc, constAlloc, 0);
+    pf->setConstantColor(rsc, 1.0f, 1.0f, 1.0f, 1.0f);
+
     mDefault.set(pf);
-    pf->init(rsc);
 }
 
 void ProgramFragmentState::deinit(Context *rsc)
@@ -322,21 +231,13 @@
 namespace android {
 namespace renderscript {
 
-RsProgramFragment rsi_ProgramFragmentCreate(Context *rsc,
-                                            const uint32_t * params,
-                                            uint32_t paramLength)
-{
-    ProgramFragment *pf = new ProgramFragment(rsc, params, paramLength);
-    pf->incUserRef();
-    return pf;
-}
-
-RsProgramFragment rsi_ProgramFragmentCreate2(Context *rsc, const char * shaderText,
+RsProgramFragment rsi_ProgramFragmentCreate(Context *rsc, const char * shaderText,
                              uint32_t shaderLength, const uint32_t * params,
                              uint32_t paramLength)
 {
     ProgramFragment *pf = new ProgramFragment(rsc, shaderText, shaderLength, params, paramLength);
     pf->incUserRef();
+    //LOGE("rsi_ProgramFragmentCreate %p", pf);
     return pf;
 }
 
diff --git a/libs/rs/rsProgramFragment.h b/libs/rs/rsProgramFragment.h
index 9fa565d..1cf9ca7 100644
--- a/libs/rs/rsProgramFragment.h
+++ b/libs/rs/rsProgramFragment.h
@@ -28,26 +28,25 @@
 class ProgramFragment : public Program
 {
 public:
-    ProgramFragment(Context *, const uint32_t * params, uint32_t paramLength);
     ProgramFragment(Context *rsc, const char * shaderText,
                              uint32_t shaderLength, const uint32_t * params,
                              uint32_t paramLength);
     virtual ~ProgramFragment();
 
-    virtual void setupGL(const Context *, ProgramFragmentState *);
-    virtual void setupGL2(const Context *, ProgramFragmentState *, ShaderCache *sc);
+    virtual void setupGL2(Context *, ProgramFragmentState *, ShaderCache *sc);
 
     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(Context *, float, float, float, float);
 
 protected:
-    // Hacks to create a program for now
-    uint32_t mTextureFormats[MAX_TEXTURE];
-    uint32_t mTextureDimensions[MAX_TEXTURE];
-    RsTexEnvMode mEnvModes[MAX_TEXTURE];
-    uint32_t mTextureEnableMask;
-    bool mPointSpriteEnable;
+    float mConstantColor[4];
+    int32_t mTextureUniformIndexStart;
 };
 
 class ProgramFragmentState
@@ -57,10 +56,9 @@
     ~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];
     ObjectBaseRef<ProgramFragment> mDefault;
     Vector<ProgramFragment *> mPrograms;
 
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..62d060d 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,52 +52,47 @@
 void ProgramRaster::setLineWidth(float s)
 {
     mLineWidth = s;
+    mDirty = true;
 }
 
-void ProgramRaster::setPointSize(float s)
+void ProgramRaster::setCullMode(RsCullMode mode)
 {
-    mPointSize = s;
-}
-
-void ProgramRaster::setupGL(const Context *rsc, ProgramRasterState *state)
-{
-    if (state->mLast.get() == this) {
-        return;
-    }
-    state->mLast.set(this);
-
-    glPointSize(mPointSize);
-    if (mPointSmooth) {
-        glEnable(GL_POINT_SMOOTH);
-    } else {
-        glDisable(GL_POINT_SMOOTH);
-    }
-
-    glLineWidth(mLineWidth);
-    if (mLineSmooth) {
-        glEnable(GL_LINE_SMOOTH);
-    } else {
-        glDisable(GL_LINE_SMOOTH);
-    }
-
-    if (rsc->checkVersion1_1()) {
-        if (mPointSprite) {
-            glEnable(GL_POINT_SPRITE_OES);
-        } else {
-            glDisable(GL_POINT_SPRITE_OES);
-        }
-    }
+    mCull = mode;
+    mDirty = true;
 }
 
 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 +102,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 +118,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 +131,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..d5ed686 100644
--- a/libs/rs/rsProgramRaster.h
+++ b/libs/rs/rsProgramRaster.h
@@ -34,21 +34,20 @@
                   bool pointSprite);
     virtual ~ProgramRaster();
 
-    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 +55,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..3f90d7a
--- /dev/null
+++ b/libs/rs/rsProgramStore.cpp
@@ -0,0 +1,290 @@
+/*
+ * 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::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..95bcf3c
--- /dev/null
+++ b/libs/rs/rsProgramStore.h
@@ -0,0 +1,88 @@
+/*
+ * 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 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..a785262 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -14,28 +14,24 @@
  * 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;
 
 
-ProgramVertex::ProgramVertex(Context *rsc, bool texMat) :
-    Program(rsc)
-{
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
-    mTextureMatrixEnable = texMat;
-    mLightCount = 0;
-    init(rsc);
-}
-
 ProgramVertex::ProgramVertex(Context *rsc, const char * shaderText,
                              uint32_t shaderLength, const uint32_t * params,
                              uint32_t paramLength) :
@@ -43,69 +39,15 @@
 {
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
-    mTextureMatrixEnable = false;
-    mLightCount = 0;
 
     init(rsc);
 }
 
 ProgramVertex::~ProgramVertex()
 {
-}
-
-static void logMatrix(const char *txt, const float *f)
-{
-    LOGV("Matrix %s, %p", txt, f);
-    LOGV("%6.4f, %6.4f, %6.4f, %6.4f", f[0], f[4], f[8], f[12]);
-    LOGV("%6.4f, %6.4f, %6.4f, %6.4f", f[1], f[5], f[9], f[13]);
-    LOGV("%6.4f, %6.4f, %6.4f, %6.4f", f[2], f[6], f[10], f[14]);
-    LOGV("%6.4f, %6.4f, %6.4f, %6.4f", f[3], f[7], f[11], f[15]);
-}
-
-void ProgramVertex::setupGL(const Context *rsc, ProgramVertexState *state)
-{
-    if ((state->mLast.get() == this) && !mDirty) {
-        return;
+    if(mShaderID) {
+        mRSC->mShaderCache.cleanupVertex(mShaderID);
     }
-    state->mLast.set(this);
-
-    const float *f = static_cast<const float *>(mConstants[0]->getPtr());
-
-    glMatrixMode(GL_TEXTURE);
-    if (mTextureMatrixEnable) {
-        glLoadMatrixf(&f[RS_PROGRAM_VERTEX_TEXTURE_OFFSET]);
-    } else {
-        glLoadIdentity();
-    }
-
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
-    if (mLightCount) {
-        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);
-            l->setupGL(ct);
-        }
-        for (uint32_t ct = mLightCount; ct < MAX_LIGHTS; ct++) {
-            glDisable(GL_LIGHT0 + ct);
-        }
-    } else {
-        glDisable(GL_LIGHTING);
-    }
-
-    if (!f) {
-        LOGE("Must bind constants to vertex program");
-    }
-
-    glMatrixMode(GL_PROJECTION);
-    glLoadMatrixf(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET]);
-    glMatrixMode(GL_MODELVIEW);
-    glLoadMatrixf(&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET]);
-
-    mDirty = false;
 }
 
 void ProgramVertex::loadShader(Context *rsc) {
@@ -114,42 +56,19 @@
 
 void ProgramVertex::createShader()
 {
-    mShader.setTo("");
-
-    mShader.append("varying vec4 varColor;\n");
-    mShader.append("varying vec4 varTex0;\n");
-
     if (mUserShader.length() > 1) {
-        mShader.append("uniform mat4 ");
-        mShader.append(mUniformNames[0]);
-        mShader.append(";\n");
 
-        for (uint32_t ct=0; ct < mConstantCount; ct++) {
-            const Element *e = mConstantTypes[ct]->getElement();
-            for (uint32_t field=0; field < e->getFieldCount(); field++) {
-                const Element *f = e->getField(field);
-
-                // Cannot be complex
-                rsAssert(!f->getFieldCount());
-                switch(f->getComponent().getVectorSize()) {
-                case 1: mShader.append("uniform float UNI_"); break;
-                case 2: mShader.append("uniform vec2 UNI_"); break;
-                case 3: mShader.append("uniform vec3 UNI_"); break;
-                case 4: mShader.append("uniform vec4 UNI_"); break;
-                default:
-                    rsAssert(0);
-                }
-
-                mShader.append(e->getFieldName(field));
-                mShader.append(";\n");
-            }
-        }
-
+        appendUserConstants();
 
         for (uint32_t ct=0; ct < mInputCount; ct++) {
             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,149 +81,121 @@
                     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");
-
-        for (uint32_t ct=0; ct < mUniformCount; ct++) {
-            mShader.append("uniform mat4 ");
-            mShader.append(mUniformNames[ct]);
-            mShader.append(";\n");
-        }
-
-        mShader.append("void main() {\n");
-        mShader.append("  gl_Position = UNI_MVP * ATTRIB_LegacyPosition;\n");
-        mShader.append("  gl_PointSize = ATTRIB_LegacyPointSize;\n");
-
-        mShader.append("  varColor = ATTRIB_LegacyColor;\n");
-        if (mTextureMatrixEnable) {
-            mShader.append("  varTex0 = UNI_TexMatrix * ATTRIB_LegacyTexture;\n");
-        } else {
-            mShader.append("  varTex0 = ATTRIB_LegacyTexture;\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");
+        LOGE("ProgramFragment::createShader cannot create program, shader code not defined");
+        rsAssert(0);
     }
 }
 
-void ProgramVertex::setupGL2(const Context *rsc, ProgramVertexState *state, ShaderCache *sc)
+void ProgramVertex::setupGL2(Context *rsc, ProgramVertexState *state, ShaderCache *sc)
 {
     //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());
-
-    Matrix mvp;
-    mvp.load(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET]);
-    Matrix t;
-    t.load(&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET]);
-    mvp.multiply(&t);
-
-    glUniformMatrix4fv(sc->vtxUniformSlot(0), 1, GL_FALSE, mvp.m);
-    if (mTextureMatrixEnable) {
-        glUniformMatrix4fv(sc->vtxUniformSlot(1), 1, GL_FALSE,
-                           &f[RS_PROGRAM_VERTEX_TEXTURE_OFFSET]);
+    if(!isUserProgram()) {
+        if(mConstants[0].get() == NULL) {
+            LOGE("Unable to set fixed function emulation matrices because allocation is missing");
+            rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing");
+            return;
+        }
+        float *f = static_cast<float *>(mConstants[0]->getPtr());
+        Matrix mvp;
+        mvp.load(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET]);
+        Matrix t;
+        t.load(&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET]);
+        mvp.multiply(&t);
+        for(uint32_t i = 0; i < 16; i ++) {
+            f[RS_PROGRAM_VERTEX_MVP_OFFSET + i] = mvp.m[i];
+        }
     }
 
     rsc->checkError("ProgramVertex::setupGL2 begin uniforms");
-    uint32_t uidx = 1;
-    for (uint32_t ct=0; ct < mConstantCount; ct++) {
-        Allocation *alloc = mConstants[ct+1].get();
-        if (!alloc) {
-            continue;
-        }
-
-        const uint8_t *data = static_cast<const uint8_t *>(alloc->getPtr());
-        const Element *e = mConstantTypes[ct]->getElement();
-        for (uint32_t field=0; field < e->getFieldCount(); field++) {
-            const Element *f = e->getField(field);
-            uint32_t offset = e->getFieldOffsetBytes(field);
-            int32_t slot = sc->vtxUniformSlot(uidx);
-
-            const float *fd = reinterpret_cast<const float *>(&data[offset]);
-
-            //LOGE("Uniform  slot=%i, offset=%i, constant=%i, field=%i, uidx=%i", slot, offset, ct, field, uidx);
-            if (slot >= 0) {
-                switch(f->getComponent().getVectorSize()) {
-                case 1:
-                    //LOGE("Uniform 1 = %f", fd[0]);
-                    glUniform1fv(slot, 1, fd);
-                    break;
-                case 2:
-                    //LOGE("Uniform 2 = %f %f", fd[0], fd[1]);
-                    glUniform2fv(slot, 1, fd);
-                    break;
-                case 3:
-                    //LOGE("Uniform 3 = %f %f %f", fd[0], fd[1], fd[2]);
-                    glUniform3fv(slot, 1, fd);
-                    break;
-                case 4:
-                    //LOGE("Uniform 4 = %f %f %f %f", fd[0], fd[1], fd[2], fd[3]);
-                    glUniform4fv(slot, 1, fd);
-                    break;
-                default:
-                    rsAssert(0);
-                }
-            }
-            uidx ++;
-        }
-    }
-
-    for (uint32_t ct=0; ct < mConstantCount; ct++) {
-        uint32_t glSlot = sc->vtxUniformSlot(ct + 1);
-
-    }
+    setupUserConstants(rsc, sc, false);
 
     state->mLast.set(this);
     rsc->checkError("ProgramVertex::setupGL2");
 }
 
-void ProgramVertex::addLight(const Light *l)
+void ProgramVertex::setProjectionMatrix(Context *rsc, const rsc_Matrix *m) const
 {
-    if (mLightCount < MAX_LIGHTS) {
-        mLights[mLightCount].set(l);
-        mLightCount++;
+    if(isUserProgram()) {
+        LOGE("Attempting to set fixed function emulation matrix projection on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot set emulation matrix on user shader");
+        return;
     }
-}
-
-void ProgramVertex::setProjectionMatrix(const rsc_Matrix *m) const
-{
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to set fixed function emulation matrix projection because allocation is missing");
+        return;
+    }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
     memcpy(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET], m, sizeof(rsc_Matrix));
     mDirty = true;
 }
 
-void ProgramVertex::setModelviewMatrix(const rsc_Matrix *m) const
+void ProgramVertex::setModelviewMatrix(Context *rsc, const rsc_Matrix *m) const
 {
+    if(isUserProgram()) {
+        LOGE("Attempting to set fixed function emulation matrix modelview on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot set emulation matrix on user shader");
+        return;
+    }
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to set fixed function emulation matrix modelview because allocation is missing");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing");
+        return;
+    }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
     memcpy(&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET], m, sizeof(rsc_Matrix));
     mDirty = true;
 }
 
-void ProgramVertex::setTextureMatrix(const rsc_Matrix *m) const
+void ProgramVertex::setTextureMatrix(Context *rsc, const rsc_Matrix *m) const
 {
+    if(isUserProgram()) {
+        LOGE("Attempting to set fixed function emulation matrix texture on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot set emulation matrix on user shader");
+        return;
+    }
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to set fixed function emulation matrix texture because allocation is missing");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing");
+        return;
+    }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
     memcpy(&f[RS_PROGRAM_VERTEX_TEXTURE_OFFSET], m, sizeof(rsc_Matrix));
     mDirty = true;
 }
 
-void ProgramVertex::transformToScreen(const Context *rsc, float *v4out, const float *v3in) const
+void ProgramVertex::getProjectionMatrix(Context *rsc, rsc_Matrix *m) const
 {
+    if(isUserProgram()) {
+        LOGE("Attempting to get fixed function emulation matrix projection on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot get emulation matrix on user shader");
+        return;
+    }
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to get fixed function emulation matrix projection because allocation is missing");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing");
+        return;
+    }
+    float *f = static_cast<float *>(mConstants[0]->getPtr());
+    memcpy(m, &f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET], sizeof(rsc_Matrix));
+}
+
+void ProgramVertex::transformToScreen(Context *rsc, float *v4out, const float *v3in) const
+{
+    if(isUserProgram()) {
+        return;
+    }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
     Matrix mvp;
     mvp.loadMultiply((Matrix *)&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET],
@@ -312,45 +203,31 @@
     mvp.vectorMultiply(v4out, v3in);
 }
 
-void ProgramVertex::initAddUserElement(const Element *e, String8 *names, uint32_t *count, const char *prefix)
-{
-    rsAssert(e->getFieldCount());
-    for (uint32_t ct=0; ct < e->getFieldCount(); ct++) {
-        const Element *ce = e->getField(ct);
-        if (ce->getFieldCount()) {
-            initAddUserElement(ce, names, count, prefix);
-        } else {
-            String8 tmp(prefix);
-            tmp.append(e->getFieldName(ct));
-            names[*count].setTo(tmp.string());
-            (*count)++;
-        }
-    }
-}
-
-
 void ProgramVertex::init(Context *rsc)
 {
     mAttribCount = 0;
     if (mUserShader.size() > 0) {
         for (uint32_t ct=0; ct < mInputCount; ct++) {
-            initAddUserElement(mInputElements[ct].get(), mAttribNames, &mAttribCount, "ATTRIB_");
+            initAddUserElement(mInputElements[ct].get(), mAttribNames, &mAttribCount, RS_SHADER_ATTR);
         }
-
-        mUniformCount = 1;
-        mUniformNames[0].setTo("UNI_MVP");
+        mUniformCount = 0;
         for (uint32_t ct=0; ct < mConstantCount; ct++) {
-            initAddUserElement(mConstantTypes[ct]->getElement(), mUniformNames, &mUniformCount, "UNI_");
+            initAddUserElement(mConstantTypes[ct]->getElement(), mUniformNames, &mUniformCount, RS_SHADER_UNI);
         }
-    } else {
-        mUniformCount = 2;
-        mUniformNames[0].setTo("UNI_MVP");
-        mUniformNames[1].setTo("UNI_TexMatrix");
     }
-
     createShader();
 }
 
+void ProgramVertex::serialize(OStream *stream) const
+{
+
+}
+
+ProgramVertex *ProgramVertex::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
+
 
 ///////////////////////////////////////////////////////////////////////
 
@@ -362,44 +239,80 @@
 {
 }
 
-void ProgramVertexState::init(Context *rsc, int32_t w, int32_t h)
+void ProgramVertexState::init(Context *rsc)
 {
-    RsElement e = (RsElement) Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 1);
+    const Element *matrixElem = Element::create(rsc, RS_TYPE_MATRIX_4X4, RS_KIND_USER, false, 1);
+    const Element *f2Elem = Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 2);
+    const Element *f3Elem = Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 3);
+    const Element *f4Elem = Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 4);
 
-    rsi_TypeBegin(rsc, e);
-    rsi_TypeAdd(rsc, RS_DIMENSION_X, 48);
-    mAllocType.set((Type *)rsi_TypeCreate(rsc));
+    rsc->mStateElement.elementBuilderBegin();
+    rsc->mStateElement.elementBuilderAdd(matrixElem, "MV", 1);
+    rsc->mStateElement.elementBuilderAdd(matrixElem, "P", 1);
+    rsc->mStateElement.elementBuilderAdd(matrixElem, "TexMatrix", 1);
+    rsc->mStateElement.elementBuilderAdd(matrixElem, "MVP", 1);
+    const Element *constInput = rsc->mStateElement.elementBuilderCreate(rsc);
 
-    ProgramVertex *pv = new ProgramVertex(rsc, false);
-    Allocation *alloc = (Allocation *)rsi_AllocationCreateTyped(rsc, mAllocType.get());
+    rsc->mStateElement.elementBuilderBegin();
+    rsc->mStateElement.elementBuilderAdd(f4Elem, "position", 1);
+    rsc->mStateElement.elementBuilderAdd(f4Elem, "color", 1);
+    rsc->mStateElement.elementBuilderAdd(f3Elem, "normal", 1);
+    rsc->mStateElement.elementBuilderAdd(f2Elem, "texture0", 1);
+    const Element *attrElem = rsc->mStateElement.elementBuilderCreate(rsc);
+
+    Type *inputType = new Type(rsc);
+    inputType->setElement(constInput);
+    inputType->setDimX(1);
+    inputType->compute();
+
+    String8 shaderString(RS_SHADER_INTERNAL);
+    shaderString.append("varying vec4 varColor;\n");
+    shaderString.append("varying vec2 varTex0;\n");
+    shaderString.append("void main() {\n");
+    shaderString.append("  gl_Position = UNI_MVP * ATTRIB_position;\n");
+    shaderString.append("  gl_PointSize = 1.0;\n");
+    shaderString.append("  varColor = ATTRIB_color;\n");
+    shaderString.append("  varTex0 = ATTRIB_texture0;\n");
+    shaderString.append("}\n");
+
+    uint32_t tmp[6];
+    tmp[0] = RS_PROGRAM_PARAM_CONSTANT;
+    tmp[1] = (uint32_t)inputType;
+    tmp[2] = RS_PROGRAM_PARAM_INPUT;
+    tmp[3] = (uint32_t)attrElem;
+    tmp[4] = RS_PROGRAM_PARAM_TEXTURE_COUNT;
+    tmp[5] = 0;
+
+    ProgramVertex *pv = new ProgramVertex(rsc, shaderString.string(),
+                                          shaderString.length(), tmp, 6);
+    Allocation *alloc = new Allocation(rsc, inputType);
+    pv->bindAllocation(rsc, alloc, 0);
+
     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);
 
-    updateSize(rsc, w, h);
 }
 
-void ProgramVertexState::updateSize(Context *rsc, int32_t w, int32_t h)
+void ProgramVertexState::updateSize(Context *rsc)
 {
+    float *f = static_cast<float *>(mDefaultAlloc->getPtr());
+
     Matrix m;
-    m.loadOrtho(0,w, h,0, -1,1);
-    mDefaultAlloc->subData(RS_PROGRAM_VERTEX_PROJECTION_OFFSET, 16, &m.m[0], 16*4);
+    m.loadOrtho(0,rsc->getWidth(), rsc->getHeight(),0, -1,1);
+    memcpy(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET], m.m, sizeof(m));
+    memcpy(&f[RS_PROGRAM_VERTEX_MVP_OFFSET], m.m, sizeof(m));
 
     m.loadIdentity();
-    mDefaultAlloc->subData(RS_PROGRAM_VERTEX_MODELVIEW_OFFSET, 16, &m.m[0], 16*4);
+    memcpy(&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET], m.m, sizeof(m));
+    memcpy(&f[RS_PROGRAM_VERTEX_TEXTURE_OFFSET], m.m, sizeof(m));
 }
 
 void ProgramVertexState::deinit(Context *rsc)
 {
     mDefaultAlloc.clear();
     mDefault.clear();
-    mAllocType.clear();
     mLast.clear();
 }
 
@@ -407,15 +320,7 @@
 namespace android {
 namespace renderscript {
 
-
-RsProgramVertex rsi_ProgramVertexCreate(Context *rsc, bool texMat)
-{
-    ProgramVertex *pv = new ProgramVertex(rsc, texMat);
-    pv->incUserRef();
-    return pv;
-}
-
-RsProgramVertex rsi_ProgramVertexCreate2(Context *rsc, const char * shaderText,
+RsProgramVertex rsi_ProgramVertexCreate(Context *rsc, const char * shaderText,
                              uint32_t shaderLength, const uint32_t * params,
                              uint32_t paramLength)
 {
diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h
index 28554cc..355df2b 100644
--- a/libs/rs/rsProgramVertex.h
+++ b/libs/rs/rsProgramVertex.h
@@ -28,40 +28,26 @@
 class ProgramVertex : public Program
 {
 public:
-    const static uint32_t MAX_LIGHTS = 8;
-
     ProgramVertex(Context *,const char * shaderText, uint32_t shaderLength,
                   const uint32_t * params, uint32_t paramLength);
-    ProgramVertex(Context *, bool texMat);
     virtual ~ProgramVertex();
 
-    virtual void setupGL(const Context *rsc, ProgramVertexState *state);
-    virtual void setupGL2(const Context *rsc, ProgramVertexState *state, ShaderCache *sc);
+    virtual void setupGL2(Context *rsc, ProgramVertexState *state, ShaderCache *sc);
 
+    void setProjectionMatrix(Context *, const rsc_Matrix *) const;
+    void getProjectionMatrix(Context *, rsc_Matrix *) const;
+    void setModelviewMatrix(Context *, const rsc_Matrix *) const;
+    void setTextureMatrix(Context *, const rsc_Matrix *) const;
 
-    void setTextureMatrixEnable(bool e) {mTextureMatrixEnable = e;}
-    void addLight(const Light *);
-
-    void setProjectionMatrix(const rsc_Matrix *) const;
-    void setModelviewMatrix(const rsc_Matrix *) const;
-    void setTextureMatrix(const rsc_Matrix *) const;
-
-    void transformToScreen(const Context *, float *v4out, const float *v3in) const;
+    void transformToScreen(Context *, float *v4out, const float *v3in) const;
 
     virtual void createShader();
     virtual void loadShader(Context *);
     virtual void init(Context *);
 
-
-protected:
-    uint32_t mLightCount;
-    ObjectBaseRef<const Light> mLights[MAX_LIGHTS];
-
-    // Hacks to create a program for now
-    bool mTextureMatrixEnable;
-
-private:
-    void initAddUserElement(const Element *e, String8 *names, uint32_t *count, const char *prefix);
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_PROGRAM_VERTEX; }
+    static ProgramVertex *createFromStream(Context *rsc, IStream *stream);
 };
 
 
@@ -71,18 +57,13 @@
     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..180d78e 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"
 
 
@@ -38,7 +44,8 @@
                  RsSamplerValue minFilter,
                  RsSamplerValue wrapS,
                  RsSamplerValue wrapT,
-                 RsSamplerValue wrapR) : ObjectBase(rsc)
+                 RsSamplerValue wrapR,
+                 float aniso) : ObjectBase(rsc)
 {
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
@@ -47,13 +54,14 @@
     mWrapS = wrapS;
     mWrapT = wrapT;
     mWrapR = wrapR;
+    mAniso = aniso;
 }
 
 Sampler::~Sampler()
 {
 }
 
-void Sampler::setupGL(const Context *rsc, bool npot)
+void Sampler::setupGL(const Context *rsc, const Allocation *tex)
 {
     GLenum trans[] = {
         GL_NEAREST, //RS_SAMPLER_NEAREST,
@@ -61,25 +69,38 @@
         GL_LINEAR_MIPMAP_LINEAR, //RS_SAMPLER_LINEAR_MIP_LINEAR,
         GL_REPEAT, //RS_SAMPLER_WRAP,
         GL_CLAMP_TO_EDGE, //RS_SAMPLER_CLAMP
-
     };
 
-    bool forceNonMip = false;
-    if (!rsc->ext_OES_texture_npot() && npot) {
-        forceNonMip = true;
-    }
+    GLenum transNP[] = {
+        GL_NEAREST, //RS_SAMPLER_NEAREST,
+        GL_LINEAR, //RS_SAMPLER_LINEAR,
+        GL_LINEAR, //RS_SAMPLER_LINEAR_MIP_LINEAR,
+        GL_CLAMP_TO_EDGE, //RS_SAMPLER_WRAP,
+        GL_CLAMP_TO_EDGE, //RS_SAMPLER_CLAMP
+    };
 
-    if ((mMinFilter == RS_SAMPLER_LINEAR_MIP_LINEAR) && forceNonMip) {
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    if (!rsc->ext_OES_texture_npot() && tex->getType()->getIsNp2()) {
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, transNP[mMinFilter]);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, transNP[mMagFilter]);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, transNP[mWrapS]);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, transNP[mWrapT]);
     } else {
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, trans[mMinFilter]);
+        if (tex->getHasGraphicsMipmaps()) {
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, trans[mMinFilter]);
+        } else {
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, transNP[mMinFilter]);
+        }
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, trans[mMagFilter]);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, trans[mWrapS]);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, trans[mWrapT]);
     }
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, trans[mMagFilter]);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, trans[mWrapS]);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, trans[mWrapT]);
 
+    float anisoValue = rsMin(rsc->ext_texture_max_aniso(), mAniso);
+    if(rsc->ext_texture_max_aniso() > 1.0f) {
+        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoValue);
+    }
 
-    rsc->checkError("ProgramFragment::setupGL2 tex env");
+    rsc->checkError("Sampler::setupGL2 tex env");
 }
 
 void Sampler::bindToContext(SamplerState *ss, uint32_t slot)
@@ -94,6 +115,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()
 {
@@ -122,6 +154,7 @@
     ss->mWrapS = RS_SAMPLER_WRAP;
     ss->mWrapT = RS_SAMPLER_WRAP;
     ss->mWrapR = RS_SAMPLER_WRAP;
+    ss->mAniso = 1.0f;
 }
 
 void rsi_SamplerSet(Context *rsc, RsSamplerParam param, RsSamplerValue value)
@@ -144,21 +177,37 @@
     case RS_SAMPLER_WRAP_R:
         ss->mWrapR = value;
         break;
+    default:
+        LOGE("Attempting to set invalid value on sampler");
+        break;
     }
+}
 
+void rsi_SamplerSet2(Context *rsc, RsSamplerParam param, float value)
+{
+    SamplerState * ss = &rsc->mStateSampler;
+
+    switch(param) {
+    case RS_SAMPLER_ANISO:
+        ss->mAniso = value;
+        break;
+    default:
+        LOGE("Attempting to set invalid value on sampler");
+        break;
+    }
 }
 
 RsSampler rsi_SamplerCreate(Context *rsc)
 {
     SamplerState * ss = &rsc->mStateSampler;
 
-
     Sampler * s = new Sampler(rsc,
                               ss->mMagFilter,
                               ss->mMinFilter,
                               ss->mWrapS,
                               ss->mWrapT,
-                              ss->mWrapR);
+                              ss->mWrapR,
+                              ss->mAniso);
     s->incUserRef();
     return s;
 }
diff --git a/libs/rs/rsSampler.h b/libs/rs/rsSampler.h
index 0506081..4946355 100644
--- a/libs/rs/rsSampler.h
+++ b/libs/rs/rsSampler.h
@@ -36,22 +36,28 @@
             RsSamplerValue minFilter,
             RsSamplerValue wrapS,
             RsSamplerValue wrapT,
-            RsSamplerValue wrapR);
+            RsSamplerValue wrapR,
+            float aniso = 1.0f);
 
     virtual ~Sampler();
 
     void bind(Allocation *);
-    void setupGL(const Context *, bool npot);
+    void setupGL(const Context *, const Allocation *);
 
     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;
     RsSamplerValue mWrapS;
     RsSamplerValue mWrapT;
     RsSamplerValue mWrapR;
+    float mAniso;
 
     int32_t mBoundSlot;
 
@@ -70,6 +76,7 @@
     RsSamplerValue mWrapS;
     RsSamplerValue mWrapT;
     RsSamplerValue mWrapR;
+    float mAniso;
 
 
     ObjectBaseRef<Sampler> mSamplers[RS_MAX_SAMPLER_SLOT];
diff --git a/libs/rs/rsScript.cpp b/libs/rs/rsScript.cpp
index a33933b..43bb09e 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,57 @@
     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_ScriptSetVarD(Context *rsc, RsScript vs, uint32_t slot, double 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..0a20344 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,43 @@
     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;
+
+        bool mIsThreadable;
     };
     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..c6418be 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -17,9 +17,9 @@
 #include "rsContext.h"
 #include "rsScriptC.h"
 #include "rsMatrix.h"
-
-#include "acc/acc.h"
+#include "../../compile/libbcc/include/bcc/bcc.h"
 #include "utils/Timers.h"
+#include "utils/StopWatch.h"
 
 #include <GLES/gl.h>
 #include <GLES/glext.h>
@@ -37,40 +37,84 @@
 {
     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() && !mTypes[ct].get()) {
+            mTypes[ct].set(mSlots[ct]->getType());
+        }
+
+        if (!mTypes[ct].get())
+            continue;
+        void *ptr = NULL;
+        if (mSlots[ct].get()) {
+            ptr = mSlots[ct]->getPtr();
+        }
+        void **dest = ((void ***)mEnviroment.mFieldAddress)[ct];
+
+        if (rsc->props.mLogScripts) {
+            LOGV("%p ScriptC::setupScript slot=%i  dst=%p  src=%p  type=%p", rsc, ct, dest, ptr, mSlots[ct]->getType());
+
+            //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,51 +127,267 @@
     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);
+
+    if (rsc->props.mLogScripts) {
+        LOGV("%p ScriptC::run invoking root,  ptr %p", rsc, mProgram.mRoot);
+    }
+
+    ret = mProgram.mRoot();
+
+    if (rsc->props.mLogScripts) {
+        LOGV("%p ScriptC::run invoking complete, ret=%i", rsc, ret);
+    }
+
+    setTLS(oldTLS);
     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);
+        //LOGE("usr ptr in %p,  out %p", mtls->ptrIn, mtls->ptrOut);
+        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) && mEnviroment.mIsThreadable && (mtls.dimY > 1)) {
+
+        //LOGE("launch 1");
+        rsc->launchThreads(wc_xy, &mtls);
+    } else {
+        //LOGE("launch 3");
+        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);
+
+    if (rsc->props.mLogScripts) {
+        LOGV("%p ScriptC::Invoke invoking slot %i,  ptr %p", rsc, slot, mEnviroment.mInvokeFunctions[slot]);
+    }
+    ((void (*)(const void *, uint32_t))
+        mEnviroment.mInvokeFunctions[slot])(data, len);
+    if (rsc->props.mLogScripts) {
+        LOGV("%p ScriptC::Invoke complete", rsc);
+    }
+
+    setTLS(oldTLS);
+}
+
 ScriptCState::ScriptCState()
 {
-    mScript = NULL;
-    clear();
+    mScript.clear();
 }
 
 ScriptCState::~ScriptCState()
 {
-    delete mScript;
-    mScript = NULL;
+    mScript.clear();
 }
 
-void ScriptCState::clear()
+void ScriptCState::init(Context *rsc)
 {
+    clear(rsc);
+}
+
+void ScriptCState::clear(Context *rsc)
+{
+    rsAssert(rsc);
     for (uint32_t ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
         mConstantBufferTypes[ct].clear();
-        mSlotNames[ct].setTo("");
-        mInvokableNames[ct].setTo("");
         mSlotWritable[ct] = false;
     }
 
-    delete mScript;
-    mScript = new ScriptC(NULL);
-
-    mInt32Defines.clear();
-    mFloatDefines.clear();
+    mScript.clear();
+    mScript.set(new ScriptC(rsc));
 }
 
-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;
+    ScriptC *s = (ScriptC *)pContext;
+    sym = ScriptCState::lookupSymbol(name);
+    if (sym) {
+        return sym->mPtr;
+    }
+    sym = ScriptCState::lookupSymbolCL(name);
+    if (sym) {
+        return sym->mPtr;
+    }
+    s->mEnviroment.mIsThreadable = false;
+    sym = ScriptCState::lookupSymbolGL(name);
     if (sym) {
         return sym->mPtr;
     }
@@ -137,65 +397,52 @@
 
 void ScriptCState::runCompiler(Context *rsc, ScriptC *s)
 {
-    s->mAccScript = accCreateScript();
-    String8 tmp;
-
-    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;
+    LOGV("%p ScriptCState::runCompiler ", rsc);
+    {
+        StopWatch compileTimer("RenderScript compile time");
+        s->mBccScript = bccCreateScript();
+        s->mEnviroment.mIsThreadable = true;
+        bccScriptBitcode(s->mBccScript, s->mEnviroment.mScriptText, s->mEnviroment.mScriptTextLength);
+        bccRegisterSymbolCallback(s->mBccScript, symbolLookup, s);
+        bccCompileScript(s->mBccScript);
+        bccGetScriptLabel(s->mBccScript, "root", (BCCvoid**) &s->mProgram.mRoot);
+        bccGetScriptLabel(s->mBccScript, "init", (BCCvoid**) &s->mProgram.mInit);
     }
+    LOGV("%p ScriptCState::runCompiler root %p,  init %p", rsc, 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);
     }
 
     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 +455,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 +466,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 +477,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 +488,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 +499,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 {
@@ -386,14 +507,7 @@
 void rsi_ScriptCBegin(Context * rsc)
 {
     ScriptCState *ss = &rsc->mScriptC;
-    ss->clear();
-}
-
-void rsi_ScriptCSetScript(Context * rsc, void *vp)
-{
-    rsAssert(0);
-    //ScriptCState *ss = &rsc->mScriptC;
-    //ss->mProgram.mScript = reinterpret_cast<ScriptC::RunScript_t>(vp);
+    ss->clear(rsc);
 }
 
 void rsi_ScriptCSetText(Context *rsc, const char *text, uint32_t len)
@@ -412,32 +526,19 @@
 {
     ScriptCState *ss = &rsc->mScriptC;
 
-    ScriptC *s = ss->mScript;
-    ss->mScript = NULL;
+    ObjectBaseRef<ScriptC> s = ss->mScript.get();
+    ss->mScript.clear();
 
-    ss->runCompiler(rsc, s);
+    ss->runCompiler(rsc, s.get());
     s->incUserRef();
     s->setContext(rsc);
     for (int ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
         s->mTypes[ct].set(ss->mConstantBufferTypes[ct].get());
-        s->mSlotNames[ct] = ss->mSlotNames[ct];
         s->mSlotWritable[ct] = ss->mSlotWritable[ct];
     }
 
-    ss->clear();
-    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);
+    ss->clear(rsc);
+    return s.get();
 }
 
 }
diff --git a/libs/rs/rsScriptC.h b/libs/rs/rsScriptC.h
index 35abadf..7ec80aa 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
@@ -64,30 +79,26 @@
     ScriptCState();
     ~ScriptCState();
 
-    ScriptC *mScript;
+    ObjectBaseRef<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 init(Context *rsc);
+
+    void clear(Context *rsc);
     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..e0de867 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -17,18 +17,9 @@
 #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 +30,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 +73,7 @@
     return 0.2215f * (y * fabsf(y) - y) + y;
 }
 
+
 static float SC_randf(float max)
 {
     float r = (float)rand();
@@ -335,104 +86,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 +178,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 +201,338 @@
 //
 //////////////////////////////////////////////////////////////////////////////
 
-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);
+    CHECK_OBJ(a);
+    //LOGE("SC_allocGetDimX a=%p  type=%p", a, a->getType());
+    return a->getType()->getDimX();
+}
+
+static uint32_t SC_allocGetDimY(RsAllocation va)
+{
+    const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
+    return a->getType()->getDimY();
+}
+
+static uint32_t SC_allocGetDimZ(RsAllocation va)
+{
+    const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
+    return a->getType()->getDimZ();
+}
+
+static uint32_t SC_allocGetDimLOD(RsAllocation va)
+{
+    const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
+    return a->getType()->getDimLOD();
+}
+
+static uint32_t SC_allocGetDimFaces(RsAllocation va)
+{
+    const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
+    return a->getType()->getDimFaces();
+}
+
+static const void * SC_getElementAtX(RsAllocation va, uint32_t x)
+{
+    const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
+    const Type *t = a->getType();
+    CHECK_OBJ(t);
+    const uint8_t *p = (const uint8_t *)a->getPtr();
+    return &p[t->getElementSizeBytes() * x];
+}
+
+static const void * SC_getElementAtXY(RsAllocation va, uint32_t x, uint32_t y)
+{
+    const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
+    const Type *t = a->getType();
+    CHECK_OBJ(t);
+    const uint8_t *p = (const uint8_t *)a->getPtr();
+    return &p[t->getElementSizeBytes() * (x + y*t->getDimX())];
+}
+
+static const void * SC_getElementAtXYZ(RsAllocation va, uint32_t x, uint32_t y, uint32_t z)
+{
+    const Allocation *a = static_cast<const Allocation *>(va);
+    CHECK_OBJ(a);
+    const Type *t = a->getType();
+    CHECK_OBJ(t);
+    const uint8_t *p = (const uint8_t *)a->getPtr();
+    return &p[t->getElementSizeBytes() * (x + y*t->getDimX())];
+}
+
+static void SC_setObject(void **vdst, void * vsrc) {
+    //LOGE("SC_setObject  %p,%p  %p", vdst, *vdst, vsrc);
+    if (vsrc) {
+        CHECK_OBJ(vsrc);
+        static_cast<ObjectBase *>(vsrc)->incSysRef();
     }
-}
-
-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;
+    if (vdst[0]) {
+        CHECK_OBJ(vdst[0]);
+        static_cast<ObjectBase *>(vdst[0])->decSysRef();
     }
-
-    rgb[0] = red;
-    rgb[1] = green;
-    rgb[2] = blue;
+    *vdst = vsrc;
+    //LOGE("SC_setObject *");
 }
-
-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)
-{
-    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);
+static void SC_clearObject(void **vdst) {
+    //LOGE("SC_clearObject  %p,%p", vdst, *vdst);
+    if (vdst[0]) {
+        CHECK_OBJ(vdst[0]);
+        static_cast<ObjectBase *>(vdst[0])->decSysRef();
     }
+    *vdst = NULL;
+    //LOGE("SC_clearObject *");
+}
+static bool SC_isObject(RsAllocation vsrc) {
+    return vsrc != NULL;
 }
 
-static void SC_uploadToTexture(RsAllocation va, uint32_t baseMipLevel)
+
+
+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, float f1, float f2) {
+    LOGE("%s {%f, %f}", s, f1, f2);
+}
+static void SC_debugFv3(const char *s, float f1, float f2, float f3) {
+    LOGE("%s {%f, %f, %f}", s, f1, f2, f3);
+}
+static void SC_debugFv4(const char *s, float f1, float f2, float f3, float f4) {
+    LOGE("%s {%f, %f, %f, %f}", s, f1, f2, f3, f4);
+}
+static void SC_debugFM4v4(const char *s, const float *f) {
+    LOGE("%s {%f, %f, %f, %f", s, f[0], f[4], f[8], f[12]);
+    LOGE("%s  %f, %f, %f, %f", s, f[1], f[5], f[9], f[13]);
+    LOGE("%s  %f, %f, %f, %f", s, f[2], f[6], f[10], f[14]);
+    LOGE("%s  %f, %f, %f, %f}", s, f[3], f[7], f[11], f[15]);
+}
+static void SC_debugFM3v3(const char *s, const float *f) {
+    LOGE("%s {%f, %f, %f", s, f[0], f[3], f[6]);
+    LOGE("%s  %f, %f, %f", s, f[1], f[4], f[7]);
+    LOGE("%s  %f, %f, %f}",s, f[2], f[5], f[8]);
+}
+static void SC_debugFM2v2(const char *s, const float *f) {
+    LOGE("%s {%f, %f", s, f[0], f[2]);
+    LOGE("%s  %f, %f}",s, f[1], f[3]);
+}
+
+static void SC_debugI32(const char *s, int32_t i) {
+    LOGE("%s %i  0x%x", s, i, i);
+}
+static void SC_debugU32(const char *s, uint32_t i) {
+    LOGE("%s %i  0x%x", s, i, i);
+}
+
+static void SC_debugP(const char *s, const void *p) {
+    LOGE("%s %p", s, p);
+}
+
+static uint32_t SC_toClient2(int cmdID, void *data, int len)
 {
     GET_TLS();
-    rsi_AllocationUploadToTexture(rsc, va, false, baseMipLevel);
+    //LOGE("SC_toClient %i %i %i", cmdID, len);
+    return rsc->sendMessageToClient(data, cmdID, len, false);
 }
 
-static void SC_uploadToBufferObject(RsAllocation va)
+static uint32_t SC_toClient(int cmdID)
 {
     GET_TLS();
-    rsi_AllocationUploadToBufferObject(rsc, va);
+    //LOGE("SC_toClient %i", cmdID);
+    return rsc->sendMessageToClient(NULL, cmdID, 0, false);
 }
 
-static void SC_syncToGL(RsAllocation va)
+static uint32_t SC_toClientBlocking2(int cmdID, void *data, int len)
 {
     GET_TLS();
-    Allocation *a = static_cast<Allocation *>(va);
-
+    //LOGE("SC_toClientBlocking %i %i", cmdID, len);
+    return rsc->sendMessageToClient(data, cmdID, len, true);
 }
 
-static void SC_ClearColor(float r, float g, float b, float a)
-{
-    //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;
-}
-
-static void SC_debugF(const char *s, float f)
-{
-    LOGE("%s %f", s, f);
-}
-
-static void SC_debugHexF(const char *s, float f)
-{
-    LOGE("%s 0x%x", s, *((int *) (&f)));
-}
-
-static void SC_debugI32(const char *s, int32_t i)
-{
-    LOGE("%s %i", s, i);
-}
-
-static void SC_debugHexI32(const char *s, int32_t i)
-{
-    LOGE("%s 0x%x", s, i);
-}
-
-static uint32_t SC_getWidth()
+static uint32_t SC_toClientBlocking(int cmdID)
 {
     GET_TLS();
-    return rsc->getWidth();
+    //LOGE("SC_toClientBlocking %i", cmdID);
+    return rsc->sendMessageToClient(NULL, cmdID, 0, true);
 }
 
-static uint32_t SC_getHeight()
+int SC_divsi3(int a, int b)
+{
+    return a / b;
+}
+
+int SC_getAllocation(const void *ptr)
 {
     GET_TLS();
-    return rsc->getHeight();
+    const Allocation *alloc = sc->ptrToAllocation(ptr);
+    return (int)alloc;
 }
 
-static uint32_t SC_colorFloatRGBAtoUNorm8(float r, float g, float b, float a)
+void SC_allocationMarkDirty(RsAllocation 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;
+    Allocation *alloc = static_cast<Allocation *>(a);
+    alloc->sendDirty();
 }
 
-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)
+void SC_ForEach(RsScript vs,
+                RsAllocation vin,
+                RsAllocation vout,
+                const void *usr)
 {
     GET_TLS();
-    return rsc->sendMessageToClient(data, cmdID, len, waitForSpace != 0);
+    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);
 }
 
-static void SC_scriptCall(int scriptID)
+void SC_ForEach2(RsScript vs,
+                RsAllocation vin,
+                RsAllocation vout,
+                const void *usr,
+                const RsScriptCall *call)
 {
     GET_TLS();
-    rsc->runScript((Script *)scriptID, 0);
+    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 },
+
+    { "_Z11rsSetObjectP10rs_elementS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP10rs_element", (void *)&SC_clearObject },
+    { "_Z10rsIsObject10rs_element", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP7rs_typeS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP7rs_type", (void *)&SC_clearObject },
+    { "_Z10rsIsObject7rs_type", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP13rs_allocationS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP13rs_allocation", (void *)&SC_clearObject },
+    { "_Z10rsIsObject13rs_allocation", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP10rs_samplerS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP10rs_sampler", (void *)&SC_clearObject },
+    { "_Z10rsIsObject10rs_sampler", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP9rs_scriptS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP9rs_script", (void *)&SC_clearObject },
+    { "_Z10rsIsObject9rs_script", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP7rs_meshS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP7rs_mesh", (void *)&SC_clearObject },
+    { "_Z10rsIsObject7rs_mesh", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP19rs_program_fragmentS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP19rs_program_fragment", (void *)&SC_clearObject },
+    { "_Z10rsIsObject19rs_program_fragment", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP17rs_program_vertexS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP17rs_program_vertex", (void *)&SC_clearObject },
+    { "_Z10rsIsObject17rs_program_vertex", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP17rs_program_rasterS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP17rs_program_raster", (void *)&SC_clearObject },
+    { "_Z10rsIsObject17rs_program_raster", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP16rs_program_storeS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP16rs_program_store", (void *)&SC_clearObject },
+    { "_Z10rsIsObject16rs_program_store", (void *)&SC_isObject },
+
+    { "_Z11rsSetObjectP7rs_fontS_", (void *)&SC_setObject },
+    { "_Z13rsClearObjectP7rs_font", (void *)&SC_clearObject },
+    { "_Z10rsIsObject7rs_font", (void *)&SC_isObject },
+
+
+    { "_Z21rsAllocationMarkDirty13rs_allocation", (void *)&SC_allocationMarkDirty },
+
+
+    // Debug
+    { "_Z7rsDebugPKcf", (void *)&SC_debugF },
+    { "_Z7rsDebugPKcff", (void *)&SC_debugFv2 },
+    { "_Z7rsDebugPKcfff", (void *)&SC_debugFv3 },
+    { "_Z7rsDebugPKcffff", (void *)&SC_debugFv4 },
+    { "_Z7rsDebugPKcPK12rs_matrix4x4", (void *)&SC_debugFM4v4 },
+    { "_Z7rsDebugPKcPK12rs_matrix3x3", (void *)&SC_debugFM3v3 },
+    { "_Z7rsDebugPKcPK12rs_matrix2x2", (void *)&SC_debugFM2v2 },
+    { "_Z7rsDebugPKci", (void *)&SC_debugI32 },
+    { "_Z7rsDebugPKcj", (void *)&SC_debugU32 },
+    { "_Z7rsDebugPKcPKv", (void *)&SC_debugP },
+
+    // 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 +548,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..b991cab
--- /dev/null
+++ b/libs/rs/rsScriptC_LibGL.cpp
@@ -0,0 +1,505 @@
+/*
+ * 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 "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)
+{
+    CHECK_OBJ_OR_NULL(va);
+    CHECK_OBJ(vpf);
+    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)
+{
+    CHECK_OBJ_OR_NULL(vs);
+    CHECK_OBJ(vpf);
+    GET_TLS();
+    rsi_ProgramBindSampler(rsc,
+                           static_cast<ProgramFragment *>(vpf),
+                           slot,
+                           static_cast<Sampler *>(vs));
+
+}
+
+static void SC_bindProgramStore(RsProgramStore pfs)
+{
+    CHECK_OBJ_OR_NULL(pfs);
+    GET_TLS();
+    rsi_ContextBindProgramStore(rsc, pfs);
+}
+
+static void SC_bindProgramFragment(RsProgramFragment pf)
+{
+    CHECK_OBJ_OR_NULL(pf);
+    GET_TLS();
+    rsi_ContextBindProgramFragment(rsc, pf);
+}
+
+static void SC_bindProgramVertex(RsProgramVertex pv)
+{
+    CHECK_OBJ_OR_NULL(pv);
+    GET_TLS();
+    rsi_ContextBindProgramVertex(rsc, pv);
+}
+
+static void SC_bindProgramRaster(RsProgramRaster pv)
+{
+    CHECK_OBJ_OR_NULL(pv);
+    GET_TLS();
+    rsi_ContextBindProgramRaster(rsc, pv);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// VP
+//////////////////////////////////////////////////////////////////////////////
+
+static void SC_vpLoadProjectionMatrix(const rsc_Matrix *m)
+{
+    GET_TLS();
+    rsc->getVertex()->setProjectionMatrix(rsc, m);
+}
+
+static void SC_vpLoadModelMatrix(const rsc_Matrix *m)
+{
+    GET_TLS();
+    rsc->getVertex()->setModelviewMatrix(rsc, m);
+}
+
+static void SC_vpLoadTextureMatrix(const rsc_Matrix *m)
+{
+    GET_TLS();
+    rsc->getVertex()->setTextureMatrix(rsc, m);
+}
+
+
+static void SC_pfConstantColor(RsProgramFragment vpf, float r, float g, float b, float a)
+{
+    GET_TLS();
+    CHECK_OBJ(vpf);
+    ProgramFragment *pf = static_cast<ProgramFragment *>(vpf);
+    pf->setConstantColor(rsc, r, g, b, a);
+}
+
+static void SC_vpGetProjectionMatrix(rsc_Matrix *m)
+{
+    GET_TLS();
+    rsc->getVertex()->getProjectionMatrix(rsc, m);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// 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, "ATTRIB_position");
+    va.add(GL_FLOAT, 2, 8, false, (uint32_t)tex, "ATTRIB_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)
+{
+    CHECK_OBJ(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)
+{
+    CHECK_OBJ(vsm);
+    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)
+{
+    CHECK_OBJ(vsm);
+    GET_TLS();
+    Mesh *sm = static_cast<Mesh *>(vsm);
+    if (!rsc->setupCheck()) {
+        return;
+    }
+    sm->renderPrimitiveRange(rsc, primIndex, start, len);
+}
+
+static void SC_meshComputeBoundingBox(RsMesh vsm, float *minX, float *minY, float *minZ,
+                                                     float *maxX, float *maxY, float *maxZ)
+{
+    CHECK_OBJ(vsm);
+    GET_TLS();
+    Mesh *sm = static_cast<Mesh *>(vsm);
+    sm->computeBBox();
+    *minX = sm->mBBoxMin[0];
+    *minY = sm->mBBoxMin[1];
+    *minZ = sm->mBBoxMin[2];
+    *maxX = sm->mBBoxMax[0];
+    *maxY = sm->mBBoxMax[1];
+    *maxZ = sm->mBBoxMax[2];
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//////////////////////////////////////////////////////////////////////////////
+
+
+static void SC_color(float r, float g, float b, float a)
+{
+    GET_TLS();
+    ProgramFragment *pf = (ProgramFragment *)rsc->getFragment();
+    pf->setConstantColor(rsc, r, g, b, a);
+}
+
+static void SC_uploadToTexture2(RsAllocation va, uint32_t baseMipLevel)
+{
+    CHECK_OBJ(va);
+    GET_TLS();
+    rsi_AllocationUploadToTexture(rsc, va, false, baseMipLevel);
+}
+static void SC_uploadToTexture(RsAllocation va)
+{
+    CHECK_OBJ(va);
+    GET_TLS();
+    rsi_AllocationUploadToTexture(rsc, va, false, 0);
+}
+
+static void SC_uploadToBufferObject(RsAllocation va)
+{
+    CHECK_OBJ(va);
+    GET_TLS();
+    rsi_AllocationUploadToBufferObject(rsc, va);
+}
+
+static void SC_ClearColor(float r, float g, float b, float a)
+{
+    GET_TLS();
+    rsc->setupProgramStore();
+
+    glClearColor(r, g, b, a);
+    glClear(GL_COLOR_BUFFER_BIT);
+}
+
+static void SC_ClearDepth(float v)
+{
+    GET_TLS();
+    rsc->setupProgramStore();
+
+    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)
+{
+    CHECK_OBJ(va);
+    GET_TLS();
+    Allocation *alloc = static_cast<Allocation *>(va);
+    const char *text = (const char *)alloc->getPtr();
+    size_t allocSize = alloc->getType()->getSizeBytes();
+    rsc->mStateFont.renderText(text, allocSize, x, y);
+}
+
+static void SC_DrawText(const char *text, int x, int y)
+{
+    GET_TLS();
+    size_t textLen = strlen(text);
+    rsc->mStateFont.renderText(text, textLen, x, y);
+}
+
+static void SC_setMetrics(Font::Rect *metrics,
+                          int32_t *left, int32_t *right,
+                          int32_t *top, int32_t *bottom)
+{
+    if(left) {
+        *left = metrics->left;
+    }
+    if(right) {
+        *right = metrics->right;
+    }
+    if(top) {
+        *top = metrics->top;
+    }
+    if(bottom) {
+        *bottom = metrics->bottom;
+    }
+}
+
+static void SC_MeasureTextAlloc(RsAllocation va,
+                                int32_t *left, int32_t *right,
+                                int32_t *top, int32_t *bottom)
+{
+    CHECK_OBJ(va);
+    GET_TLS();
+    Allocation *alloc = static_cast<Allocation *>(va);
+    const char *text = (const char *)alloc->getPtr();
+    size_t textLen = alloc->getType()->getSizeBytes();
+    Font::Rect metrics;
+    rsc->mStateFont.measureText(text, textLen, &metrics);
+    SC_setMetrics(&metrics, left, right, top, bottom);
+}
+
+static void SC_MeasureText(const char *text,
+                           int32_t *left, int32_t *right,
+                           int32_t *top, int32_t *bottom)
+{
+    GET_TLS();
+    size_t textLen = strlen(text);
+    Font::Rect metrics;
+    rsc->mStateFont.measureText(text, textLen, &metrics);
+    SC_setMetrics(&metrics, left, right, top, bottom);
+}
+
+static void SC_BindFont(RsFont font)
+{
+    CHECK_OBJ(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 },
+
+    { "_Z35rsgProgramVertexGetProjectionMatrixP12rs_matrix4x4", (void *)&SC_vpGetProjectionMatrix },
+
+    { "_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 },
+    { "_Z25rsgMeshComputeBoundingBox7rs_meshPfS0_S0_S0_S0_S0_", (void *)&SC_meshComputeBoundingBox },
+
+    { "_Z13rsgClearColorffff", (void *)&SC_ClearColor },
+    { "_Z13rsgClearDepthf", (void *)&SC_ClearDepth },
+
+    { "_Z11rsgDrawTextPKcii", (void *)&SC_DrawText },
+    { "_Z11rsgDrawText13rs_allocationii", (void *)&SC_DrawTextAlloc },
+    { "_Z14rsgMeasureTextPKcPiS1_S1_S1_", (void *)&SC_MeasureText },
+    { "_Z14rsgMeasureText13rs_allocationPiS0_S0_S0_", (void *)&SC_MeasureTextAlloc },
+
+    { "_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..45f6207 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;
@@ -25,20 +29,15 @@
 
 ShaderCache::ShaderCache()
 {
-    mEntryCount = 0;
-    mEntryAllocationCount = 16;
-    mEntries = (entry_t *)calloc(mEntryAllocationCount, sizeof(entry_t));
+    mEntries.setCapacity(16);
 }
 
 ShaderCache::~ShaderCache()
 {
-    for (uint32_t ct=0; ct < mEntryCount; ct++) {
-        glDeleteProgram(mEntries[ct].program);
+    for (uint32_t ct=0; ct < mEntries.size(); ct++) {
+        glDeleteProgram(mEntries[ct]->program);
+        free(mEntries[ct]);
     }
-
-    mEntryCount = 0;
-    mEntryAllocationCount = 0;
-    free(mEntries);
 }
 
 bool ShaderCache::lookup(Context *rsc, ProgramVertex *vtx, ProgramFragment *frag)
@@ -49,61 +48,46 @@
     if (!frag->getShaderID()) {
         frag->loadShader(rsc);
     }
+
+    // Don't try to cache if shaders failed to load
+    if(!vtx->getShaderID() || !frag->getShaderID()) {
+        return false;
+    }
     //LOGV("ShaderCache lookup  vtx %i, frag %i", vtx->getShaderID(), frag->getShaderID());
+    uint32_t entryCount = mEntries.size();
+    for(uint32_t ct = 0; ct < entryCount; ct ++) {
+        if ((mEntries[ct]->vtx == vtx->getShaderID()) &&
+            (mEntries[ct]->frag == frag->getShaderID())) {
 
-    for (uint32_t ct=0; ct < mEntryCount; ct++) {
-        if ((mEntries[ct].vtx == vtx->getShaderID()) &&
-            (mEntries[ct].frag == frag->getShaderID())) {
-
-            //LOGV("SC using program %i", mEntries[ct].program);
-            glUseProgram(mEntries[ct].program);
-            mCurrent = &mEntries[ct];
+            //LOGV("SC using program %i", mEntries[ct]->program);
+            glUseProgram(mEntries[ct]->program);
+            mCurrent = mEntries[ct];
             //LOGV("ShaderCache hit, using %i", ct);
             rsc->checkError("ShaderCache::lookup (hit)");
             return true;
         }
     }
-    // Not in cache, add it.
 
-    if (mEntryAllocationCount == mEntryCount) {
-        // Out of space, make some.
-        mEntryAllocationCount *= 2;
-        entry_t *e = (entry_t *)calloc(mEntryAllocationCount, sizeof(entry_t));
-        if (!e) {
-            LOGE("Out of memory for ShaderCache::lookup");
-            return false;
-        }
-        memcpy(e, mEntries, sizeof(entry_t) * mEntryCount);
-        free(mEntries);
-        mEntries = e;
-    }
-
-    //LOGV("ShaderCache miss, using %i", mEntryCount);
+    //LOGV("ShaderCache miss");
     //LOGE("e0 %x", glGetError());
-
-    entry_t *e = &mEntries[mEntryCount];
+    entry_t *e = (entry_t *)malloc(sizeof(entry_t));
+    mEntries.push(e);
     mCurrent = e;
     e->vtx = vtx->getShaderID();
     e->frag = frag->getShaderID();
     e->program = glCreateProgram();
-    e->mUserVertexProgram = vtx->isUserProgram();
-    if (mEntries[mEntryCount].program) {
+    e->vtxAttrCount = vtx->getAttribCount();
+    if (e->program) {
         GLuint pgm = e->program;
         glAttachShader(pgm, vtx->getShaderID());
         //LOGE("e1 %x", glGetError());
         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());
@@ -126,14 +110,15 @@
             rsc->setError(RS_ERROR_BAD_SHADER, "Error linking GL Programs");
             return false;
         }
-        if (vtx->isUserProgram()) {
-            for (uint32_t ct=0; ct < vtx->getAttribCount(); ct++) {
-                e->mVtxAttribSlots[ct] = glGetAttribLocation(pgm, vtx->getAttribName(ct));
-                if (rsc->props.mLogShaders) {
-                    LOGV("vtx A %i, %s = %d\n", ct, vtx->getAttribName(ct).string(), e->mVtxAttribSlots[ct]);
-                }
+
+        for (uint32_t ct=0; ct < e->vtxAttrCount; ct++) {
+            e->mVtxAttribSlots[ct] = glGetAttribLocation(pgm, vtx->getAttribName(ct));
+            e->mVtxAttribNames[ct] = vtx->getAttribName(ct).string();
+            if (rsc->props.mLogShaders) {
+                LOGV("vtx A %i, %s = %d\n", ct, vtx->getAttribName(ct).string(), e->mVtxAttribSlots[ct]);
             }
         }
+
         for (uint32_t ct=0; ct < vtx->getUniformCount(); ct++) {
             e->mVtxUniformSlots[ct] = glGetUniformLocation(pgm, vtx->getUniformName(ct));
             if (rsc->props.mLogShaders) {
@@ -151,17 +136,47 @@
     e->mIsValid = true;
     //LOGV("SC made program %i", e->program);
     glUseProgram(e->program);
-    mEntryCount++;
     rsc->checkError("ShaderCache::lookup (miss)");
     return true;
 }
 
+int32_t ShaderCache::vtxAttribSlot(const String8 &attrName) const {
+    for (uint32_t ct=0; ct < mCurrent->vtxAttrCount; ct++) {
+        if(attrName == mCurrent->mVtxAttribNames[ct]) {
+            return mCurrent->mVtxAttribSlots[ct];
+        }
+    }
+    return -1;
+}
+
 void ShaderCache::cleanupVertex(uint32_t id)
 {
+    int32_t numEntries = (int32_t)mEntries.size();
+    for(int32_t ct = 0; ct < numEntries; ct ++) {
+        if (mEntries[ct]->vtx == id) {
+            glDeleteProgram(mEntries[ct]->program);
+
+            free(mEntries[ct]);
+            mEntries.removeAt(ct);
+            numEntries = (int32_t)mEntries.size();
+            ct --;
+        }
+    }
 }
 
 void ShaderCache::cleanupFragment(uint32_t id)
 {
+    int32_t numEntries = (int32_t)mEntries.size();
+    for(int32_t ct = 0; ct < numEntries; ct ++) {
+        if (mEntries[ct]->frag == id) {
+            glDeleteProgram(mEntries[ct]->program);
+
+            free(mEntries[ct]);
+            mEntries.removeAt(ct);
+            numEntries = (int32_t)mEntries.size();
+            ct --;
+        }
+    }
 }
 
 void ShaderCache::cleanupAll()
diff --git a/libs/rs/rsShaderCache.h b/libs/rs/rsShaderCache.h
index df99ccc..35ff95b 100644
--- a/libs/rs/rsShaderCache.h
+++ b/libs/rs/rsShaderCache.h
@@ -40,29 +40,30 @@
 
     void cleanupAll();
 
-    int32_t vtxAttribSlot(uint32_t a) const {return mCurrent->mVtxAttribSlots[a];}
+    int32_t vtxAttribSlot(const String8 &attrName) const;
     int32_t vtxUniformSlot(uint32_t a) const {return mCurrent->mVtxUniformSlots[a];}
     int32_t fragAttribSlot(uint32_t a) const {return mCurrent->mFragAttribSlots[a];}
     int32_t fragUniformSlot(uint32_t a) const {return mCurrent->mFragUniformSlots[a];}
-    bool isUserVertexProgram() const {return mCurrent->mUserVertexProgram;}
 
 protected:
     typedef struct {
         uint32_t vtx;
         uint32_t frag;
         uint32_t program;
+        uint32_t vtxAttrCount;
+        const char* mVtxAttribNames[Program::MAX_ATTRIBS];
         int32_t mVtxAttribSlots[Program::MAX_ATTRIBS];
         int32_t mVtxUniformSlots[Program::MAX_UNIFORMS];
         int32_t mFragAttribSlots[Program::MAX_ATTRIBS];
         int32_t mFragUniformSlots[Program::MAX_UNIFORMS];
-        bool mUserVertexProgram;
         bool mIsValid;
     } entry_t;
-    entry_t *mEntries;
+    //entry_t *mEntries;
+    Vector<entry_t*> mEntries;
     entry_t *mCurrent;
 
-    uint32_t mEntryCount;
-    uint32_t mEntryAllocationCount;
+    /*uint32_t mEntryCount;
+    uint32_t mEntryAllocationCount;*/
 
 };
 
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..8cdb48a 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];
     }
 
@@ -139,135 +146,35 @@
 void Type::makeGLComponents()
 {
     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;
+        if(getElement()->getFieldName(ct)[0] == '#') {
+            continue;
+        }
 
-        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;
+        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();
+        String8 tmp(RS_SHADER_ATTR);
+        tmp.append(getElement()->getFieldName(ct));
+        mAttribs[userNum].name.setTo(tmp.string());
+        userNum ++;
 
-        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;
+        if(userNum == RS_MAX_ATTRIBS) {
+            return;
         }
     }
 }
 
+
 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 +190,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();
@@ -301,6 +259,73 @@
     return false;
 }
 
+bool Type::isEqual(const Type *other) const {
+    if(other == NULL) {
+        return false;
+    }
+    if (other->getElement()->isEqual(getElement()) &&
+        other->getDimX() == mDimX &&
+        other->getDimY() == mDimY &&
+        other->getDimZ() == mDimZ &&
+        other->getDimLOD() == mDimLOD &&
+        other->getDimFaces() == mFaces) {
+        return true;
+    }
+    return false;
+}
+
+Type * Type::cloneAndResize1D(Context *rsc, uint32_t dimX) const
+{
+    TypeState * stc = &rsc->mStateType;
+    for (uint32_t ct=0; ct < stc->mTypes.size(); ct++) {
+        Type *t = stc->mTypes[ct];
+        if (t->getElement() != mElement.get()) continue;
+        if (t->getDimX() != dimX) continue;
+        if (t->getDimY() != mDimY) continue;
+        if (t->getDimZ() != mDimZ) continue;
+        if (t->getDimLOD() != mDimLOD) continue;
+        if (t->getDimFaces() != mFaces) continue;
+        t->incUserRef();
+        return t;
+    }
+
+    Type *nt = new Type(rsc);
+    nt->mElement.set(mElement);
+    nt->mDimX = dimX;
+    nt->mDimY = mDimY;
+    nt->mDimZ = mDimZ;
+    nt->mDimLOD = mDimLOD;
+    nt->mFaces = mFaces;
+    nt->compute();
+    return nt;
+}
+
+Type * Type::cloneAndResize2D(Context *rsc, uint32_t dimX, uint32_t dimY) const
+{
+    TypeState * stc = &rsc->mStateType;
+    for (uint32_t ct=0; ct < stc->mTypes.size(); ct++) {
+        Type *t = stc->mTypes[ct];
+        if (t->getElement() != mElement.get()) continue;
+        if (t->getDimX() != dimX) continue;
+        if (t->getDimY() != dimY) continue;
+        if (t->getDimZ() != mDimZ) continue;
+        if (t->getDimLOD() != mDimLOD) continue;
+        if (t->getDimFaces() != mFaces) continue;
+        t->incUserRef();
+        return t;
+    }
+
+    Type *nt = new Type(rsc);
+    nt->mElement.set(mElement);
+    nt->mDimX = dimX;
+    nt->mDimY = dimY;
+    nt->mDimZ = mDimZ;
+    nt->mDimLOD = mDimLOD;
+    nt->mFaces = mFaces;
+    nt->compute();
+    return nt;
+}
+
 
 //////////////////////////////////////////////////
 //
@@ -349,8 +374,6 @@
         break;
     }
 
-
-    int32_t arrayNum = dim - RS_DIMENSION_ARRAY_0;
     if ((dim < 0) || (dim > RS_DIMENSION_MAX)) {
         LOGE("rsTypeAdd: Bad dimension");
         //error
@@ -391,6 +414,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..b5548c0 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -71,9 +71,16 @@
     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);
+
+    bool isEqual(const Type *other) const;
+
+    Type * cloneAndResize1D(Context *rsc, uint32_t dimX) const;
+    Type * cloneAndResize2D(Context *rsc, uint32_t dimX, uint32_t dimY) const;
 
 protected:
     struct LOD {
@@ -112,15 +119,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..4d50124 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,72 @@
     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,
+    if(idx == 0) {
+        LOGV("Starting vertex attribute binding");
+    }
+    LOGV("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 (sc->isUserVertexProgram()) {
-            slot = sc->vtxAttribSlot(ct);
-        } else {
-            if (mAttribs[ct].kind == RS_KIND_USER) {
-                continue;
-            }
-            slot = sc->vtxAttribSlot(mAttribs[ct].kind);
+        int32_t slot = sc->vtxAttribSlot(mAttribs[ct].name);
+        if(rsc->props.mLogShadersAttr) {
+            logAttrib(ct, slot);
         }
-
-        //logAttrib(ct, slot);
+        if(slot < 0) {
+            continue;
+        }
         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..dea7d41 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,17 +52,25 @@
 
 
     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 setupGL(const Context *rsc, class VertexArrayState *) const;
+    void add(const Attrib &, uint32_t stride);
+    void add(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset, const char *name);
+
     void setupGL2(const Context *rsc, class VertexArrayState *, ShaderCache *) const;
     void logAttrib(uint32_t idx, uint32_t slot) const;
 
 protected:
     void clear(uint32_t index);
     uint32_t mActiveBuffer;
+    const uint8_t * mActivePointer;
     uint32_t mCount;
 
     Attrib mAttribs[RS_MAX_ATTRIBS];
@@ -80,7 +88,7 @@
 
 }
 }
-#endif //ANDROID_LIGHT_H
+#endif //ANDROID_VERTEX_ARRAY_H
 
 
 
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..99fc166
--- /dev/null
+++ b/libs/rs/scriptc/rs_core.rsh
@@ -0,0 +1,892 @@
+#ifndef __RS_CORE_RSH__
+#define __RS_CORE_RSH__
+
+static void __attribute__((overloadable)) rsDebug(const char *s, float2 v) {
+    rsDebug(s, v.x, v.y);
+}
+static void __attribute__((overloadable)) rsDebug(const char *s, float3 v) {
+    rsDebug(s, v.x, v.y, v.z);
+}
+static void __attribute__((overloadable)) rsDebug(const char *s, float4 v) {
+    rsDebug(s, v.x, v.y, v.z, v.w);
+}
+
+static uchar4 __attribute__((overloadable)) rsPackColorTo8888(float r, float g, float b)
+{
+    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 void __attribute__((overloadable))
+rsMatrixLoadPerspective(rs_matrix4x4* m, float fovy, float aspect, float near, float far) {
+    float top = near * tan((float) (fovy * M_PI / 360.0f));
+    float bottom = -top;
+    float left = bottom * aspect;
+    float right = top * aspect;
+    rsMatrixLoadFrustum(m, left, right, bottom, top, near, far);
+}
+
+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;
+}
+
+/////////////////////////////////////////////////////
+// quaternion ops
+/////////////////////////////////////////////////////
+
+static void __attribute__((overloadable))
+rsQuaternionSet(rs_quaternion *q, float w, float x, float y, float z) {
+    q->w = w;
+    q->x = x;
+    q->y = y;
+    q->z = z;
+}
+
+static void __attribute__((overloadable))
+rsQuaternionSet(rs_quaternion *q, const rs_quaternion *rhs) {
+    q->w = rhs->w;
+    q->x = rhs->x;
+    q->y = rhs->y;
+    q->z = rhs->z;
+}
+
+static void __attribute__((overloadable))
+rsQuaternionMultiply(rs_quaternion *q, float s) {
+    q->w *= s;
+    q->x *= s;
+    q->y *= s;
+    q->z *= s;
+}
+
+static void __attribute__((overloadable))
+rsQuaternionMultiply(rs_quaternion *q, const rs_quaternion *rhs) {
+    q->w = -q->x*rhs->x - q->y*rhs->y - q->z*rhs->z + q->w*rhs->w;
+    q->x =  q->x*rhs->w + q->y*rhs->z - q->z*rhs->y + q->w*rhs->x;
+    q->y = -q->x*rhs->z + q->y*rhs->w + q->z*rhs->z + q->w*rhs->y;
+    q->z =  q->x*rhs->y - q->y*rhs->x + q->z*rhs->w + q->w*rhs->z;
+}
+
+static void
+rsQuaternionAdd(rs_quaternion *q, const rs_quaternion *rhs) {
+    q->w *= rhs->w;
+    q->x *= rhs->x;
+    q->y *= rhs->y;
+    q->z *= rhs->z;
+}
+
+static void
+rsQuaternionLoadRotateUnit(rs_quaternion *q, float rot, float x, float y, float z) {
+    rot *= (float)(M_PI / 180.0f) * 0.5f;
+    float c = cos(rot);
+    float s = sin(rot);
+
+    q->w = c;
+    q->x = x * s;
+    q->y = y * s;
+    q->z = z * s;
+}
+
+static void
+rsQuaternionLoadRotate(rs_quaternion *q, float rot, float x, float y, float z) {
+    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;
+    }
+    rsQuaternionLoadRotateUnit(q, rot, x, y, z);
+}
+
+static void
+rsQuaternionConjugate(rs_quaternion *q) {
+    q->x = -q->x;
+    q->y = -q->y;
+    q->z = -q->z;
+}
+
+static float
+rsQuaternionDot(const rs_quaternion *q0, const rs_quaternion *q1) {
+    return q0->w*q1->w + q0->x*q1->x + q0->y*q1->y + q0->z*q1->z;
+}
+
+static void
+rsQuaternionNormalize(rs_quaternion *q) {
+    const float len = rsQuaternionDot(q, q);
+    if (len != 1) {
+        const float recipLen = 1.f / sqrt(len);
+        rsQuaternionMultiply(q, recipLen);
+    }
+}
+
+static void
+rsQuaternionSlerp(rs_quaternion *q, const rs_quaternion *q0, const rs_quaternion *q1, float t) {
+    if(t <= 0.0f) {
+        rsQuaternionSet(q, q0);
+        return;
+    }
+    if(t >= 1.0f) {
+        rsQuaternionSet(q, q1);
+        return;
+    }
+
+    rs_quaternion tempq0, tempq1;
+    rsQuaternionSet(&tempq0, q0);
+    rsQuaternionSet(&tempq1, q1);
+
+    float angle = rsQuaternionDot(q0, q1);
+    if(angle < 0) {
+        rsQuaternionMultiply(&tempq0, -1.0f);
+        angle *= -1.0f;
+    }
+
+    float scale, invScale;
+    if (angle + 1.0f > 0.05f) {
+        if (1.0f - angle >= 0.05f) {
+            float theta = acos(angle);
+            float invSinTheta = 1.0f / sin(theta);
+            scale = sin(theta * (1.0f - t)) * invSinTheta;
+            invScale = sin(theta * t) * invSinTheta;
+        }
+        else {
+            scale = 1.0f - t;
+            invScale = t;
+        }
+    }
+    else {
+        rsQuaternionSet(&tempq1, tempq0.z, -tempq0.y, tempq0.x, -tempq0.w);
+        scale = sin(M_PI * (0.5f - t));
+        invScale = sin(M_PI * t);
+    }
+
+    rsQuaternionSet(q, tempq0.w*scale + tempq1.w*invScale, tempq0.x*scale + tempq1.x*invScale,
+                        tempq0.y*scale + tempq1.y*invScale, tempq0.z*scale + tempq1.z*invScale);
+}
+
+static void rsQuaternionGetMatrixUnit(rs_matrix4x4 *m, const rs_quaternion *q) {
+    float x2 = 2.0f * q->x * q->x;
+    float y2 = 2.0f * q->y * q->y;
+    float z2 = 2.0f * q->z * q->z;
+    float xy = 2.0f * q->x * q->y;
+    float wz = 2.0f * q->w * q->z;
+    float xz = 2.0f * q->x * q->z;
+    float wy = 2.0f * q->w * q->y;
+    float wx = 2.0f * q->w * q->x;
+    float yz = 2.0f * q->y * q->z;
+
+    m->m[0] = 1.0f - y2 - z2;
+    m->m[1] = xy - wz;
+    m->m[2] = xz + wy;
+    m->m[3] = 0.0f;
+
+    m->m[4] = xy + wz;
+    m->m[5] = 1.0f - x2 - z2;
+    m->m[6] = yz - wx;
+    m->m[7] = 0.0f;
+
+    m->m[8] = xz - wy;
+    m->m[9] = yz - wx;
+    m->m[10] = 1.0f - x2 - y2;
+    m->m[11] = 0.0f;
+
+    m->m[12] = 0.0f;
+    m->m[13] = 0.0f;
+    m->m[14] = 0.0f;
+    m->m[15] = 1.0f;
+}
+
+/////////////////////////////////////////////////////
+// utility funcs
+/////////////////////////////////////////////////////
+void __attribute__((overloadable))
+rsExtractFrustumPlanes(const rs_matrix4x4 *modelViewProj,
+                         float4 *left, float4 *right,
+                         float4 *top, float4 *bottom,
+                         float4 *near, float4 *far) {
+    // x y z w = a b c d in the plane equation
+    left->x = modelViewProj->m[3] + modelViewProj->m[0];
+    left->y = modelViewProj->m[7] + modelViewProj->m[4];
+    left->z = modelViewProj->m[11] + modelViewProj->m[8];
+    left->w = modelViewProj->m[15] + modelViewProj->m[12];
+
+    right->x = modelViewProj->m[3] - modelViewProj->m[0];
+    right->y = modelViewProj->m[7] - modelViewProj->m[4];
+    right->z = modelViewProj->m[11] - modelViewProj->m[8];
+    right->w = modelViewProj->m[15] - modelViewProj->m[12];
+
+    top->x = modelViewProj->m[3] - modelViewProj->m[1];
+    top->y = modelViewProj->m[7] - modelViewProj->m[5];
+    top->z = modelViewProj->m[11] - modelViewProj->m[9];
+    top->w = modelViewProj->m[15] - modelViewProj->m[13];
+
+    bottom->x = modelViewProj->m[3] + modelViewProj->m[1];
+    bottom->y = modelViewProj->m[7] + modelViewProj->m[5];
+    bottom->z = modelViewProj->m[11] + modelViewProj->m[9];
+    bottom->w = modelViewProj->m[15] + modelViewProj->m[13];
+
+    near->x = modelViewProj->m[3] + modelViewProj->m[2];
+    near->y = modelViewProj->m[7] + modelViewProj->m[6];
+    near->z = modelViewProj->m[11] + modelViewProj->m[10];
+    near->w = modelViewProj->m[15] + modelViewProj->m[14];
+
+    far->x = modelViewProj->m[3] - modelViewProj->m[2];
+    far->y = modelViewProj->m[7] - modelViewProj->m[6];
+    far->z = modelViewProj->m[11] - modelViewProj->m[10];
+    far->w = modelViewProj->m[15] - modelViewProj->m[14];
+
+    float len = length(left->xyz);
+    *left /= len;
+    len = length(right->xyz);
+    *right /= len;
+    len = length(top->xyz);
+    *top /= len;
+    len = length(bottom->xyz);
+    *bottom /= len;
+    len = length(near->xyz);
+    *near /= len;
+    len = length(far->xyz);
+    *far /= len;
+}
+
+bool __attribute__((overloadable))
+rsIsSphereInFrustum(float4 *sphere,
+                      float4 *left, float4 *right,
+                      float4 *top, float4 *bottom,
+                      float4 *near, float4 *far) {
+
+    float distToCenter = dot(left->xyz, sphere->xyz) + left->w;
+    if(distToCenter < -sphere->w) {
+        return false;
+    }
+    distToCenter = dot(right->xyz, sphere->xyz) + right->w;
+    if(distToCenter < -sphere->w) {
+        return false;
+    }
+    distToCenter = dot(top->xyz, sphere->xyz) + top->w;
+    if(distToCenter < -sphere->w) {
+        return false;
+    }
+    distToCenter = dot(bottom->xyz, sphere->xyz) + bottom->w;
+    if(distToCenter < -sphere->w) {
+        return false;
+    }
+    distToCenter = dot(near->xyz, sphere->xyz) + near->w;
+    if(distToCenter < -sphere->w) {
+        return false;
+    }
+    distToCenter = dot(far->xyz, sphere->xyz) + far->w;
+    if(distToCenter < -sphere->w) {
+        return false;
+    }
+    return true;
+}
+
+
+/////////////////////////////////////////////////////
+// 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..ac6f8cc 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -1,65 +1,112 @@
+#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))
+    rsgProgramVertexGetProjectionMatrix(rs_matrix4x4 *);
 
-extern void bindSampler(rs_program_fragment, int slot, rs_sampler);
-extern void bindSampler(rs_program_fragment, int slot, rs_allocation);
+extern void __attribute__((overloadable))
+    rsgProgramFragmentConstantColor(rs_program_fragment, float, float, float, float);
 
-extern void vpLoadModelMatrix(const float *);
-extern void vpLoadTextureMatrix(const float *);
+extern uint __attribute__((overloadable))
+    rsgGetWidth(void);
+extern uint __attribute__((overloadable))
+    rsgGetHeight(void);
 
+extern void __attribute__((overloadable))
+    rsgUploadToTexture(rs_allocation);
+extern void __attribute__((overloadable))
+    rsgUploadToTexture(rs_allocation, uint mipLevel);
+extern void __attribute__((overloadable))
+    rsgUploadToBufferObject(rs_allocation);
 
-// 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))
+    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);
 
+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);
+// Returns the bounding box of the text relative to (0, 0)
+// Any of left, right, top, bottom could be NULL
+extern void __attribute__((overloadable))
+    rsgMeasureText(const char *, int *left, int *right, int *top, int *bottom);
+extern void __attribute__((overloadable))
+    rsgMeasureText(rs_allocation, int *left, int *right, int *top, int *bottom);
+
+extern void __attribute__((overloadable))
+    rsgMeshComputeBoundingBox(rs_mesh mesh, float *minX, float *minY, float *minZ,
+                                                float *maxX, float *maxY, float *maxZ);
+void __attribute__((overloadable))
+rsgMeshComputeBoundingBox(rs_mesh mesh, float3 *bBoxMin, float3 *bBoxMax) {
+    float x1, y1, z1, x2, y2, z2;
+    rsgMeshComputeBoundingBox(mesh, &x1, &y1, &z1, &x2, &y2, &z2);
+    bBoxMin->x = x1;
+    bBoxMin->y = y1;
+    bBoxMin->z = z1;
+    bBoxMax->x = x2;
+    bBoxMax->y = y2;
+    bBoxMax->z = z2;
+}
+
+///////////////////////////////////////////////////////
 // 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..5720b05 100644
--- a/libs/rs/scriptc/rs_math.rsh
+++ b/libs/rs/scriptc/rs_math.rsh
@@ -1,287 +1,222 @@
-// 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);
+// Debugging, print to the LOG a description string and a value.
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float, float);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float, float, float);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float, float, float, float);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, const rs_matrix4x4 *);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, const rs_matrix3x3 *);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, const rs_matrix2x2 *);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, int);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, uint);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, const void *);
+#define RS_DEBUG(a) rsDebug(#a, a)
+#define RS_DEBUG_MARKER rsDebug(__FILE__, __LINE__)
 
-extern float __attribute__((overloadable)) acos(float);
-extern float2 __attribute__((overloadable)) acos(float2);
-extern float3 __attribute__((overloadable)) acos(float3);
-extern float4 __attribute__((overloadable)) acos(float4);
-extern float8 __attribute__((overloadable)) acos(float8);
-extern float16 __attribute__((overloadable)) acos(float16);
 
-extern 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);
+#include "rs_cl.rsh"
+#include "rs_core.rsh"
 
-extern float __attribute__((overloadable)) atan(float);
-extern float2 __attribute__((overloadable)) atan(float2);
-extern float3 __attribute__((overloadable)) atan(float3);
-extern float4 __attribute__((overloadable)) atan(float4);
-extern float8 __attribute__((overloadable)) atan(float8);
-extern float16 __attribute__((overloadable)) atan(float16);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_element *dst, rs_element src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_type *dst, rs_type src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_allocation *dst, rs_allocation src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_sampler *dst, rs_sampler src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_script *dst, rs_script src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_mesh *dst, rs_mesh src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_program_fragment *dst, rs_program_fragment src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_program_vertex *dst, rs_program_vertex src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_program_raster *dst, rs_program_raster src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_program_store *dst, rs_program_store src);
+extern void __attribute__((overloadable))
+    rsSetObject(rs_font *dst, rs_font src);
 
-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 void __attribute__((overloadable))
+    rsClearObject(rs_element *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_type *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_allocation *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_sampler *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_script *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_mesh *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_program_fragment *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_program_vertex *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_program_raster *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_program_store *dst);
+extern void __attribute__((overloadable))
+    rsClearObject(rs_font *dst);
 
-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);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_element);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_type);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_allocation);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_sampler);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_script);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_mesh);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_program_fragment);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_program_vertex);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_program_raster);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_program_store);
+extern bool __attribute__((overloadable))
+    rsIsObject(rs_font);
 
 
 
+// Allocations
+
+// Return the rs_allocation associated with a bound data
+// pointer.
+extern rs_allocation __attribute__((overloadable))
+    rsGetAllocation(const void *);
+
+// Mark the allocation dirty and notify those using it
+extern void __attribute__((overloadable))
+    rsAllocationMarkDirty(rs_allocation);
+
+// 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);
+
+// 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);
+
+// 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);
+
+// Return the current system clock in milliseconds
+extern int64_t __attribute__((overloadable))
+    rsUptimeMillis(void);
+
+// 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);
+
+// 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
+enum rs_for_each_strategy {
+    RS_FOR_EACH_STRATEGY_SERIAL,
+    RS_FOR_EACH_STRATEGY_DONT_CARE,
+    RS_FOR_EACH_STRATEGY_DST_LINEAR,
+    RS_FOR_EACH_STRATEGY_TILE_SMALL,
+    RS_FOR_EACH_STRATEGY_TILE_MEDIUM,
+    RS_FOR_EACH_STRATEGY_TILE_LARGE
+};
 
-// Int ops
+typedef struct rs_script_call {
+    enum rs_for_each_strategy strategy;
+    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 int __attribute__((overloadable)) abs(int);
-extern int2 __attribute__((overloadable)) abs(int2);
-extern int3 __attribute__((overloadable)) abs(int3);
-extern int4 __attribute__((overloadable)) abs(int4);
-extern int8 __attribute__((overloadable)) abs(int8);
-extern int16 __attribute__((overloadable)) abs(int16);
+extern 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 *);
 
-
-/*
-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);
-*/
-
+#endif
diff --git a/libs/rs/scriptc/rs_types.rsh b/libs/rs/scriptc/rs_types.rsh
index 4198a74..dd42972 100644
--- a/libs/rs/scriptc/rs_types.rsh
+++ b/libs/rs/scriptc/rs_types.rsh
@@ -2,70 +2,73 @@
 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 { const int* const p; } __attribute__((packed, aligned(4))) rs_element;
+typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_type;
+typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_allocation;
+typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_sampler;
+typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_script;
+typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_mesh;
+typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_program_fragment;
+typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_program_vertex;
+typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_program_raster;
+typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_program_store;
+typedef struct { const int* const 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;
+
+typedef float4 rs_quaternion;
+
+#define RS_PACKED __attribute__((packed, aligned(4)))
 
diff --git a/libs/surfaceflinger_client/SharedBufferStack.cpp b/libs/surfaceflinger_client/SharedBufferStack.cpp
index 4bc5d9e..8f583f06 100644
--- a/libs/surfaceflinger_client/SharedBufferStack.cpp
+++ b/libs/surfaceflinger_client/SharedBufferStack.cpp
@@ -380,11 +380,6 @@
 {
     SharedBufferStack& stack( *mSharedStack );
 
-    if (stack.head == tail && stack.available == mNumBuffers) {
-        LOGW("dequeue: tail=%d, head=%d, avail=%d, queued=%d",
-                tail, stack.head, stack.available, stack.queued);
-    }
-
     RWLock::AutoRLock _rd(mLock);
 
     const nsecs_t dequeueTime = systemTime(SYSTEM_TIME_THREAD);
diff --git a/libs/surfaceflinger_client/Surface.cpp b/libs/surfaceflinger_client/Surface.cpp
index d44aab9..ebb0cc9 100644
--- a/libs/surfaceflinger_client/Surface.cpp
+++ b/libs/surfaceflinger_client/Surface.cpp
@@ -364,6 +364,13 @@
         height   = surface->mHeight;
         format   = surface->mFormat;
         flags    = surface->mFlags;
+    } else if (surface != 0 && surface->mSurface != 0) {
+        LOGW("Parceling invalid surface with non-NULL ISurface as NULL: "
+             "mSurface = %p, mIdentity = %d, mWidth = %d, mHeight = %d, "
+             "mFormat = %d, mFlags = 0x%08x, mInitCheck = %d",
+             surface->mSurface.get(), surface->mIdentity, surface->mWidth,
+             surface->mHeight, surface->mFormat, surface->mFlags,
+             surface->mInitCheck);
     }
     parcel->writeStrongBinder(sur!=0 ? sur->asBinder() : NULL);
     parcel->writeInt32(identity);
@@ -438,6 +445,9 @@
             mSharedBufferClient = new SharedBufferClient(
                     mClient.getSharedClient(), token, 2, mIdentity);
             mInitCheck = mClient.getSharedClient()->validate(token);
+        } else {
+            LOGW("Not initializing the shared buffer client because token = %d",
+                    token);
         }
     }
 }
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index aa54f82..a6f5a1b 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -2476,6 +2476,70 @@
     }
 }
 
+bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel,
+        const sp<InputChannel>& toChannel) {
+#if DEBUG_FOCUS
+    LOGD("transferTouchFocus: fromChannel=%s, toChannel=%s",
+            fromChannel->getName().string(), toChannel->getName().string());
+#endif
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        const InputWindow* fromWindow = getWindowLocked(fromChannel);
+        const InputWindow* toWindow = getWindowLocked(toChannel);
+        if (! fromWindow || ! toWindow) {
+#if DEBUG_FOCUS
+            LOGD("Cannot transfer focus because from or to window not found.");
+#endif
+            return false;
+        }
+        if (fromWindow == toWindow) {
+#if DEBUG_FOCUS
+            LOGD("Trivial transfer to same window.");
+#endif
+            return true;
+        }
+
+        bool found = false;
+        for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+            const TouchedWindow& touchedWindow = mTouchState.windows[i];
+            if (touchedWindow.window == fromWindow) {
+                int32_t oldTargetFlags = touchedWindow.targetFlags;
+                BitSet32 pointerIds = touchedWindow.pointerIds;
+
+                mTouchState.windows.removeAt(i);
+
+                int32_t newTargetFlags = 0;
+                if (oldTargetFlags & InputTarget::FLAG_FOREGROUND) {
+                    newTargetFlags |= InputTarget::FLAG_FOREGROUND;
+                    if (toWindow->layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH) {
+                        newTargetFlags |= InputTarget::FLAG_SPLIT;
+                    }
+                }
+                mTouchState.addOrUpdateWindow(toWindow, newTargetFlags, pointerIds);
+
+                found = true;
+                break;
+            }
+        }
+
+        if (! found) {
+#if DEBUG_FOCUS
+            LOGD("Focus transfer failed because from window did not have focus.");
+#endif
+            return false;
+        }
+
+#if DEBUG_FOCUS
+        logDispatchStateLocked();
+#endif
+    } // release lock
+
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+    return true;
+}
+
 void InputDispatcher::logDispatchStateLocked() {
     String8 dump;
     dumpDispatchStateLocked(dump);
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index 8345cc3..91e7df3 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/AudioService.java b/media/java/android/media/AudioService.java
index 41d2cc5..6aa1ae6 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -16,18 +16,19 @@
 
 package android.media;
 
-import java.util.NoSuchElementException;
 import android.app.ActivityManagerNative;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.media.MediaPlayer.OnCompletionListener;
@@ -40,6 +41,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.provider.Settings.System;
 import android.telephony.PhoneStateListener;
@@ -47,7 +49,6 @@
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.VolumePanel;
-import android.os.SystemProperties;
 
 import com.android.internal.telephony.ITelephony;
 
@@ -58,6 +59,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.Stack;
 
@@ -258,8 +260,8 @@
     // BluetoothHeadset API to control SCO connection
     private BluetoothHeadset mBluetoothHeadset;
 
-    // Bluetooth headset connection state
-    private boolean mBluetoothHeadsetConnected;
+    // Bluetooth headset device
+    private BluetoothDevice mBluetoothHeadsetDevice;
 
     ///////////////////////////////////////////////////////////////////////////
     // Construction
@@ -294,17 +296,20 @@
         AudioSystem.setErrorCallback(mAudioSystemCallback);
         loadSoundEffects();
 
-        mBluetoothHeadsetConnected = false;
-        mBluetoothHeadset = new BluetoothHeadset(context,
-                                                 mBluetoothHeadsetServiceListener);
+        mBluetoothHeadsetDevice = null;
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+                                    BluetoothProfile.HEADSET);
+        }
 
         // Register for device connection intent broadcasts.
         IntentFilter intentFilter =
                 new IntentFilter(Intent.ACTION_HEADSET_PLUG);
-        intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
-        intentFilter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
-        intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
+        intentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+        intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+        intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
         context.registerReceiver(mReceiver, intentFilter);
 
         // Register for media button intent broadcasts.
@@ -1000,7 +1005,7 @@
 
         public void incCount() {
             synchronized(mScoClients) {
-                requestScoState(BluetoothHeadset.AUDIO_STATE_CONNECTED);
+                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED);
                 if (mStartcount == 0) {
                     try {
                         mCb.linkToDeath(this, 0);
@@ -1026,7 +1031,7 @@
                             Log.w(TAG, "decCount() going to 0 but not registered to binder");
                         }
                     }
-                    requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
+                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                 }
             }
         }
@@ -1042,7 +1047,7 @@
                 }
                 mStartcount = 0;
                 if (stopSco) {
-                    requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
+                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                 }
             }
         }
@@ -1068,12 +1073,12 @@
 
         private void requestScoState(int state) {
             if (totalCount() == 0 &&
-                mBluetoothHeadsetConnected &&
+                mBluetoothHeadsetDevice != null &&
                 AudioService.this.mMode == AudioSystem.MODE_NORMAL) {
-                if (state == BluetoothHeadset.AUDIO_STATE_CONNECTED) {
-                    mBluetoothHeadset.startVoiceRecognition();
+                if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+                    mBluetoothHeadset.startVoiceRecognition(mBluetoothHeadsetDevice);
                 } else {
-                    mBluetoothHeadset.stopVoiceRecognition();
+                    mBluetoothHeadset.stopVoiceRecognition(mBluetoothHeadsetDevice);
                 }
             }
         }
@@ -1103,23 +1108,27 @@
         }
     }
 
-    private BluetoothHeadset.ServiceListener mBluetoothHeadsetServiceListener =
-        new BluetoothHeadset.ServiceListener() {
-        public void onServiceConnected() {
-            if (mBluetoothHeadset != null) {
-                BluetoothDevice device = mBluetoothHeadset.getCurrentHeadset();
-                if (mBluetoothHeadset.getState(device) == BluetoothHeadset.STATE_CONNECTED) {
-                    mBluetoothHeadsetConnected = true;
-                }
+    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+        new BluetoothProfile.ServiceListener() {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mBluetoothHeadset = (BluetoothHeadset) proxy;
+            Set<BluetoothDevice> deviceSet = mBluetoothHeadset.getConnectedDevices();
+            if (deviceSet.size() > 0) {
+                BluetoothDevice[] devices =
+                    deviceSet.toArray(new BluetoothDevice[deviceSet.size()]);
+                mBluetoothHeadsetDevice = devices[0];
+            } else {
+                mBluetoothHeadsetDevice = null;
             }
         }
-        public void onServiceDisconnected() {
+        public void onServiceDisconnected(int profile) {
             if (mBluetoothHeadset != null) {
-                BluetoothDevice device = mBluetoothHeadset.getCurrentHeadset();
-                if (mBluetoothHeadset.getState(device) == BluetoothHeadset.STATE_DISCONNECTED) {
-                    mBluetoothHeadsetConnected = false;
+                Set<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
+                if (devices.size() == 0) {
+                    mBluetoothHeadsetDevice = null;
                     clearAllScoClients();
                 }
+                mBluetoothHeadset = null;
             }
         }
     };
@@ -1813,18 +1822,18 @@
                         config = AudioSystem.FORCE_NONE;
                 }
                 AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
-            } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE,
-                                               BluetoothA2dp.STATE_DISCONNECTED);
+            } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+                                               BluetoothProfile.STATE_DISCONNECTED);
                 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                 String address = btDevice.getAddress();
-                boolean isConnected = (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) &&
-                                       ((String)mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)).equals(address));
+                boolean isConnected =
+                    (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) &&
+                     mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP).equals(address));
 
-                if (isConnected &&
-                    state != BluetoothA2dp.STATE_CONNECTED && state != BluetoothA2dp.STATE_PLAYING) {
+                if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
                     if (btDevice.isBluetoothDock()) {
-                        if (state == BluetoothA2dp.STATE_DISCONNECTED) {
+                        if (state == BluetoothProfile.STATE_DISCONNECTED) {
                             // introduction of a delay for transient disconnections of docks when
                             // power is rapidly turned off/on, this message will be canceled if
                             // we reconnect the dock under a preset delay
@@ -1834,9 +1843,7 @@
                     } else {
                         makeA2dpDeviceUnavailableNow(address);
                     }
-                } else if (!isConnected &&
-                             (state == BluetoothA2dp.STATE_CONNECTED ||
-                              state == BluetoothA2dp.STATE_PLAYING)) {
+                } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
                     if (btDevice.isBluetoothDock()) {
                         // this could be a reconnection after a transient disconnection
                         cancelA2dpDeviceTimeout();
@@ -1851,9 +1858,9 @@
                     }
                     makeA2dpDeviceAvailable(address);
                 }
-            } else if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                                               BluetoothHeadset.STATE_ERROR);
+            } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+                                               BluetoothProfile.STATE_DISCONNECTED);
                 int device = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
                 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                 String address = null;
@@ -1874,21 +1881,21 @@
                 }
 
                 boolean isConnected = (mConnectedDevices.containsKey(device) &&
-                                       ((String)mConnectedDevices.get(device)).equals(address));
+                                       mConnectedDevices.get(device).equals(address));
 
-                if (isConnected && state != BluetoothHeadset.STATE_CONNECTED) {
+                if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
                     AudioSystem.setDeviceConnectionState(device,
                                                          AudioSystem.DEVICE_STATE_UNAVAILABLE,
                                                          address);
                     mConnectedDevices.remove(device);
-                    mBluetoothHeadsetConnected = false;
+                    mBluetoothHeadsetDevice = null;
                     clearAllScoClients();
-                } else if (!isConnected && state == BluetoothHeadset.STATE_CONNECTED) {
+                } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
                     AudioSystem.setDeviceConnectionState(device,
                                                          AudioSystem.DEVICE_STATE_AVAILABLE,
                                                          address);
                     mConnectedDevices.put(new Integer(device), address);
-                    mBluetoothHeadsetConnected = true;
+                    mBluetoothHeadsetDevice = btDevice;
                 }
             } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
                 int state = intent.getIntExtra("state", 0);
@@ -1922,15 +1929,14 @@
                     }
                 }
             } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothHeadset.EXTRA_AUDIO_STATE,
-                                               BluetoothHeadset.STATE_ERROR);
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
                 synchronized (mScoClients) {
                     if (!mScoClients.isEmpty()) {
                         switch (state) {
-                        case BluetoothHeadset.AUDIO_STATE_CONNECTED:
+                        case BluetoothHeadset.STATE_AUDIO_CONNECTED:
                             state = AudioManager.SCO_AUDIO_STATE_CONNECTED;
                             break;
-                        case BluetoothHeadset.AUDIO_STATE_DISCONNECTED:
+                        case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
                             state = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
                             break;
                         default:
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
index a27df57..3e9429d 100644
--- a/media/java/android/media/CamcorderProfile.java
+++ b/media/java/android/media/CamcorderProfile.java
@@ -39,22 +39,80 @@
  */
 public class CamcorderProfile
 {
+    // Do not change these values/ordinals without updating their counterpart
+    // in include/media/MediaProfiles.h!
+
     /**
-     * The output from camcorder recording sessions can have different quality levels.
-     *
-     * Currently, we define two quality levels: high quality and low quality.
-     * A camcorder recording session with high quality level usually has higher output bit
-     * rate, better video and/or audio recording quality, larger video frame
-     * resolution and higher audio sampling rate, etc, than those with low quality
-     * level.
-     *
-     * Do not change these values/ordinals without updating their counterpart
-     * in include/media/MediaProfiles.h!
+     * Quality level corresponding to the lowest available resolution.
      */
     public static final int QUALITY_LOW  = 0;
+
+    /**
+     * Quality level corresponding to the highest available resolution.
+     */
     public static final int QUALITY_HIGH = 1;
 
     /**
+     * Quality level corresponding to the qcif (176 x 144) resolution.
+     */
+    public static final int QUALITY_QCIF = 2;
+
+    /**
+     * Quality level corresponding to the cif (352 x 288) resolution.
+     */
+    public static final int QUALITY_CIF = 3;
+
+    /**
+     * Quality level corresponding to the 480p (720 x 480) resolution.
+     */
+    public static final int QUALITY_480P = 4;
+
+    /**
+     * Quality level corresponding to the 720p (1280 x 720) resolution.
+     */
+    public static final int QUALITY_720P = 5;
+
+    /**
+     * Quality level corresponding to the 1080p (1920 x 1088) resolution.
+     */
+    public static final int QUALITY_1080P = 6;
+
+    /**
+     * Time lapse quality level corresponding to the lowest available resolution.
+     */
+    public static final int QUALITY_TIME_LAPSE_LOW  = 1000;
+
+    /**
+     * Time lapse quality level corresponding to the highest available resolution.
+     */
+    public static final int QUALITY_TIME_LAPSE_HIGH = 1001;
+
+    /**
+     * Time lapse quality level corresponding to the qcif (176 x 144) resolution.
+     */
+    public static final int QUALITY_TIME_LAPSE_QCIF = 1002;
+
+    /**
+     * Time lapse quality level corresponding to the cif (352 x 288) resolution.
+     */
+    public static final int QUALITY_TIME_LAPSE_CIF = 1003;
+
+    /**
+     * Time lapse quality level corresponding to the 480p (720 x 480) resolution.
+     */
+    public static final int QUALITY_TIME_LAPSE_480P = 1004;
+
+    /**
+     * Time lapse quality level corresponding to the 720p (1280 x 720) resolution.
+     */
+    public static final int QUALITY_TIME_LAPSE_720P = 1005;
+
+    /**
+     * Time lapse quality level corresponding to the 1080p (1920 x 1088) resolution.
+     */
+    public static final int QUALITY_TIME_LAPSE_1080P = 1006;
+
+    /**
      * Default recording duration in seconds before the session is terminated.
      * This is useful for applications like MMS has limited file size requirement.
      */
@@ -122,25 +180,79 @@
      * Returns the camcorder profile for the default camera at the given
      * quality level.
      * @param quality the target quality level for the camcorder profile
+     * @see #get(int, int)
      */
     public static CamcorderProfile get(int quality) {
-        return get(0, quality);
+        return get(android.hardware.Camera.CAMERA_ID_DEFAULT, quality);
     }
 
     /**
      * Returns the camcorder profile for the given camera at the given
      * quality level.
+     *
+     * Quality levels QUALITY_LOW, QUALITY_HIGH are guaranteed to be supported, while
+     * other levels may or may not be supported. The supported levels can be checked using
+     * {@link #hasProfile(int, int)}.
+     * QUALITY_LOW refers to the lowest quality available, while QUALITY_HIGH refers to
+     * the highest quality available.
+     * QUALITY_LOW/QUALITY_HIGH have to match one of qcif, cif, 480p, 720p, or 1080p.
+     * E.g. if the device supports 480p, 720p, and 1080p, then low is 480p and high is
+     * 1080p.
+     *
+     * The same is true for time lapse quality levels, i.e. QUALITY_TIME_LAPSE_LOW,
+     * QUALITY_TIME_LAPSE_HIGH are guaranteed to be supported and have to match one of
+     * qcif, cif, 480p, 720p, or 1080p.
+     *
+     * A camcorder recording session with higher quality level usually has higher output
+     * bit rate, better video and/or audio recording quality, larger video frame
+     * resolution and higher audio sampling rate, etc, than those with lower quality
+     * level.
+     *
      * @param cameraId the id for the camera
-     * @param quality the target quality level for the camcorder profile
+     * @param quality the target quality level for the camcorder profile.
+     * @see #QUALITY_LOW
+     * @see #QUALITY_HIGH
+     * @see #QUALITY_QCIF
+     * @see #QUALITY_CIF
+     * @see #QUALITY_480P
+     * @see #QUALITY_720P
+     * @see #QUALITY_1080P
+     * @see #QUALITY_TIME_LAPSE_LOW
+     * @see #QUALITY_TIME_LAPSE_HIGH
+     * @see #QUALITY_TIME_LAPSE_QCIF
+     * @see #QUALITY_TIME_LAPSE_CIF
+     * @see #QUALITY_TIME_LAPSE_480P
+     * @see #QUALITY_TIME_LAPSE_720P
+     * @see #QUALITY_TIME_LAPSE_1080P
      */
     public static CamcorderProfile get(int cameraId, int quality) {
-        if (quality < QUALITY_LOW || quality > QUALITY_HIGH) {
+        if (!((quality >= QUALITY_LOW && quality <= QUALITY_1080P) ||
+                (quality >= QUALITY_TIME_LAPSE_LOW && quality <= QUALITY_TIME_LAPSE_1080P))) {
             String errMessage = "Unsupported quality level: " + quality;
             throw new IllegalArgumentException(errMessage);
         }
         return native_get_camcorder_profile(cameraId, quality);
     }
 
+    /**
+     * Returns true if camcorder profile exists for the default camera at
+     * the given quality level.
+     * @param quality the target quality level for the camcorder profile
+     */
+    public static boolean hasProfile(int quality) {
+        return hasProfile(android.hardware.Camera.CAMERA_ID_DEFAULT, quality);
+    }
+
+    /**
+     * Returns true if camcorder profile exists for the given camera at
+     * the given quality level.
+     * @param cameraId the id for the camera
+     * @param quality the target quality level for the camcorder profile
+     */
+    public static boolean hasProfile(int cameraId, int quality) {
+        return native_has_camcorder_profile(cameraId, quality);
+    }
+
     static {
         System.loadLibrary("media_jni");
         native_init();
@@ -178,4 +290,6 @@
     private static native final void native_init();
     private static native final CamcorderProfile native_get_camcorder_profile(
             int cameraId, int quality);
+    private static native final boolean native_has_camcorder_profile(
+            int cameraId, int quality);
 }
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 6e527d9..66a93f04 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -34,8 +34,6 @@
  * {@hide}
  */
 public class MediaFile {
-    // comma separated list of all file extensions supported by the media scanner
-    public final static String sFileExtensions;
 
     // Audio file types
     public static final int FILE_TYPE_MP3     = 1;
@@ -84,6 +82,17 @@
     public static final int FILE_TYPE_WPL     = 43;
     private static final int FIRST_PLAYLIST_FILE_TYPE = FILE_TYPE_M3U;
     private static final int LAST_PLAYLIST_FILE_TYPE = FILE_TYPE_WPL;
+
+    // Other popular file types
+    public static final int FILE_TYPE_TEXT          = 100;
+    public static final int FILE_TYPE_HTML          = 101;
+    public static final int FILE_TYPE_PDF           = 102;
+    public static final int FILE_TYPE_XML           = 103;
+    public static final int FILE_TYPE_MS_WORD       = 104;
+    public static final int FILE_TYPE_MS_EXCEL      = 105;
+    public static final int FILE_TYPE_MS_POWERPOINT = 106;
+    public static final int FILE_TYPE_FLAC          = 107;
+    public static final int FILE_TYPE_ZIP           = 108;
     
     static class MediaFileType {
     
@@ -96,15 +105,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) {
@@ -115,28 +141,18 @@
         return false;
     }
 
-    private static boolean isWMVEnabled() {
-        List<VideoDecoder> decoders = DecoderCapabilities.getVideoDecoders();
-        for (VideoDecoder decoder: decoders) {
-            if (decoder == VideoDecoder.VIDEO_DECODER_WMV) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     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", MtpConstants.FORMAT_MP3);
+        addFileType("M4A", FILE_TYPE_M4A, "audio/mp4", MtpConstants.FORMAT_MPEG);
+        addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav", MtpConstants.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", MtpConstants.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", MtpConstants.FORMAT_OGG);
+        addFileType("OGA", FILE_TYPE_OGG, "application/ogg", MtpConstants.FORMAT_OGG);
+        addFileType("AAC", FILE_TYPE_AAC, "audio/aac", MtpConstants.FORMAT_AAC);
         addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska");
  
         addFileType("MID", FILE_TYPE_MID, "audio/midi");
@@ -148,78 +164,118 @@
         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", MtpConstants.FORMAT_MPEG);
+        addFileType("MP4", FILE_TYPE_MP4, "video/mp4", MtpConstants.FORMAT_MPEG);
+        addFileType("M4V", FILE_TYPE_M4V, "video/mp4", MtpConstants.FORMAT_MPEG);
+        addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp",  MtpConstants.FORMAT_3GP_CONTAINER);
+        addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp", MtpConstants.FORMAT_3GP_CONTAINER);
+        addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2", MtpConstants.FORMAT_3GP_CONTAINER);
+        addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2", MtpConstants.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("ASF", FILE_TYPE_ASF, "video/x-ms-asf");
-        }
+        addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", MtpConstants.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", MtpConstants.FORMAT_EXIF_JPEG);
+        addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg", MtpConstants.FORMAT_EXIF_JPEG);
+        addFileType("GIF", FILE_TYPE_GIF, "image/gif", MtpConstants.FORMAT_GIF);
+        addFileType("PNG", FILE_TYPE_PNG, "image/png", MtpConstants.FORMAT_PNG);
+        addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp", MtpConstants.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", MtpConstants.FORMAT_M3U_PLAYLIST);
+        addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls", MtpConstants.FORMAT_PLS_PLAYLIST);
+        addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl", MtpConstants.FORMAT_WPL_PLAYLIST);
 
-        // compute file extensions list for native Media Scanner
-        StringBuilder builder = new StringBuilder();
-        Iterator<String> iterator = sFileTypeMap.keySet().iterator();
-        
-        while (iterator.hasNext()) {
-            if (builder.length() > 0) {
-                builder.append(',');
-            }
-            builder.append(iterator.next());
-        } 
-        sFileExtensions = builder.toString();
+        addFileType("TXT", FILE_TYPE_TEXT, "text/plain", MtpConstants.FORMAT_TEXT);
+        addFileType("HTM", FILE_TYPE_HTML, "text/html", MtpConstants.FORMAT_HTML);
+        addFileType("HTML", FILE_TYPE_HTML, "text/html", MtpConstants.FORMAT_HTML);
+        addFileType("PDF", FILE_TYPE_PDF, "application/pdf");
+        addFileType("DOC", FILE_TYPE_MS_WORD, "application/msword", MtpConstants.FORMAT_MS_WORD_DOCUMENT);
+        addFileType("XLS", FILE_TYPE_MS_EXCEL, "application/vnd.ms-excel", MtpConstants.FORMAT_MS_EXCEL_SPREADSHEET);
+        addFileType("PPT", FILE_TYPE_MS_POWERPOINT, "application/mspowerpoint", MtpConstants.FORMAT_MS_POWERPOINT_PRESENTATION);
+        addFileType("FLAC", FILE_TYPE_FLAC, "audio/flac", MtpConstants.FORMAT_FLAC);
+        addFileType("ZIP", FILE_TYPE_ZIP, "application/zip");
     }
-    
+
     public static boolean isAudioFileType(int fileType) {
         return ((fileType >= FIRST_AUDIO_FILE_TYPE &&
                 fileType <= LAST_AUDIO_FILE_TYPE) ||
                 (fileType >= FIRST_MIDI_FILE_TYPE &&
                 fileType <= LAST_MIDI_FILE_TYPE));
     }
-    
+
     public static boolean isVideoFileType(int fileType) {
         return (fileType >= FIRST_VIDEO_FILE_TYPE &&
                 fileType <= LAST_VIDEO_FILE_TYPE);
     }
-    
+
     public static boolean isImageFileType(int fileType) {
         return (fileType >= FIRST_IMAGE_FILE_TYPE &&
                 fileType <= LAST_IMAGE_FILE_TYPE);
     }
-    
+
     public static boolean isPlayListFileType(int fileType) {
         return (fileType >= FIRST_PLAYLIST_FILE_TYPE &&
                 fileType <= LAST_PLAYLIST_FILE_TYPE);
     }
-    
+
     public static MediaFileType getFileType(String path) {
         int lastDot = path.lastIndexOf(".");
         if (lastDot < 0)
             return null;
         return sFileTypeMap.get(path.substring(lastDot + 1).toUpperCase());
     }
-    
+
+    // generates a title based on file name
+    public static String getFileTitle(String path) {
+        // extract file name after last slash
+        int lastSlash = path.lastIndexOf('/');
+        if (lastSlash >= 0) {
+            lastSlash++;
+            if (lastSlash < path.length()) {
+                path = path.substring(lastSlash);
+            }
+        }
+        // truncate the file extension (if any)
+        int lastDot = path.lastIndexOf('.');
+        if (lastDot > 0) {
+            path = path.substring(0, lastDot);
+        }
+        return path;
+    }
+
     public static int getFileTypeForMimeType(String mimeType) {
         Integer value = sMimeTypeMap.get(mimeType);
         return (value == null ? 0 : value.intValue());
     }
 
+    public static String getMimeTypeForFile(String path) {
+        MediaFileType mediaFileType = getFileType(path);
+        return (mediaFileType == null ? null : mediaFileType.mimeType);
+    }
+
+    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 MtpConstants.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..dd19450 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -72,6 +72,9 @@
 
     private String mPath;
     private FileDescriptor mFd;
+    private boolean mPrepareAuxiliaryFile = false;
+    private String mPathAux;
+    private FileDescriptor mFdAux;
     private EventHandler mEventHandler;
     private OnErrorListener mOnErrorListener;
     private OnInfoListener mOnInfoListener;
@@ -274,11 +277,37 @@
         setVideoFrameRate(profile.videoFrameRate);
         setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
         setVideoEncodingBitRate(profile.videoBitRate);
-        setAudioEncodingBitRate(profile.audioBitRate);
-        setAudioChannels(profile.audioChannels);
-        setAudioSamplingRate(profile.audioSampleRate);
         setVideoEncoder(profile.videoCodec);
-        setAudioEncoder(profile.audioCodec);
+        if (profile.quality >= CamcorderProfile.QUALITY_TIME_LAPSE_LOW &&
+             profile.quality <= CamcorderProfile.QUALITY_TIME_LAPSE_1080P) {
+            // Enable time lapse. Also don't set audio for time lapse.
+            setParameter(String.format("time-lapse-enable=1"));
+        } else {
+            setAudioEncodingBitRate(profile.audioBitRate);
+            setAudioChannels(profile.audioChannels);
+            setAudioSamplingRate(profile.audioSampleRate);
+            setAudioEncoder(profile.audioCodec);
+        }
+    }
+
+    /**
+     * Set video frame capture rate. This can be used to set a different video frame capture
+     * rate than the recorded video's playback rate. Currently this works only for time lapse mode.
+     *
+     * @param fps Rate at which frames should be captured in frames per second.
+     * The fps can go as low as desired. However the fastest fps will be limited by the hardware.
+     * For resolutions that can be captured by the video camera, the fastest fps can be computed using
+     * {@link android.hardware.Camera.Parameters#getPreviewFpsRange(int[])}. For higher
+     * resolutions the fastest fps may be more restrictive.
+     * Note that the recorder cannot guarantee that frames will be captured at the
+     * given rate due to camera/encoder limitations. However it tries to be as close as
+     * possible.
+     */
+    public void setCaptureRate(double fps) {
+        double timeBetweenFrameCapture = 1 / fps;
+        int timeBetweenFrameCaptureMs = (int) (1000 * timeBetweenFrameCapture);
+        setParameter(String.format("time-between-time-lapse-frame-capture=%d",
+                    timeBetweenFrameCaptureMs));
     }
 
     /**
@@ -448,6 +477,97 @@
     }
 
     /**
+     * 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));
+    }
+
+    /**
+     * Sets the auxiliary time lapse video's resolution and bitrate.
+     *
+     * The auxiliary video's resolution and bitrate are determined by the CamcorderProfile
+     * quality level {@link android.media.CamcorderProfile#QUALITY_HIGH}.
+     */
+    private void setAuxVideoParameters() {
+        CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
+        setParameter(String.format("video-aux-param-width=%d", profile.videoFrameWidth));
+        setParameter(String.format("video-aux-param-height=%d", profile.videoFrameHeight));
+        setParameter(String.format("video-aux-param-encoding-bitrate=%d", profile.videoBitRate));
+    }
+
+    /**
+     * Pass in the file descriptor for the auxiliary time lapse video. Call this before
+     * prepare().
+     *
+     * Sets file descriptor and parameters for auxiliary time lapse video. Time lapse mode
+     * can capture video (using the still camera) at resolutions higher than that can be
+     * played back on the device. This function or
+     * {@link #setAuxiliaryOutputFile(String)} enable capture of a smaller video in
+     * parallel with the main time lapse video, which can be used to play back on the
+     * device. The smaller video is created by downsampling the main video. This call is
+     * optional and does not have to be called if parallel capture of a downsampled video
+     * is not desired.
+     *
+     * Note that while the main video resolution and bitrate is determined from the
+     * CamcorderProfile in {@link #setProfile(CamcorderProfile)}, the auxiliary video's
+     * resolution and bitrate are determined by the CamcorderProfile quality level
+     * {@link android.media.CamcorderProfile#QUALITY_HIGH}. All other encoding parameters
+     * remain the same for the main video and the auxiliary video.
+     *
+     * E.g. if the device supports the time lapse profile quality level
+     * {@link android.media.CamcorderProfile#QUALITY_TIME_LAPSE_1080P} but can playback at
+     * most 480p, the application might want to capture an auxiliary video of resolution
+     * 480p using this call.
+     *
+     * @param fd an open file descriptor to be written into.
+     */
+    public void setAuxiliaryOutputFile(FileDescriptor fd)
+    {
+        mPrepareAuxiliaryFile = true;
+        mPathAux = null;
+        mFdAux = fd;
+        setAuxVideoParameters();
+    }
+
+    /**
+     * Pass in the file path for the auxiliary time lapse video. Call this before
+     * prepare().
+     *
+     * Sets file path and parameters for auxiliary time lapse video. Time lapse mode can
+     * capture video (using the still camera) at resolutions higher than that can be
+     * played back on the device. This function or
+     * {@link #setAuxiliaryOutputFile(FileDescriptor)} enable capture of a smaller
+     * video in parallel with the main time lapse video, which can be used to play back on
+     * the device. The smaller video is created by downsampling the main video. This call
+     * is optional and does not have to be called if parallel capture of a downsampled
+     * video is not desired.
+     *
+     * Note that while the main video resolution and bitrate is determined from the
+     * CamcorderProfile in {@link #setProfile(CamcorderProfile)}, the auxiliary video's
+     * resolution and bitrate are determined by the CamcorderProfile quality level
+     * {@link android.media.CamcorderProfile#QUALITY_HIGH}. All other encoding parameters
+     * remain the same for the main video and the auxiliary video.
+     *
+     * E.g. if the device supports the time lapse profile quality level
+     * {@link android.media.CamcorderProfile#QUALITY_TIME_LAPSE_1080P} but can playback at
+     * most 480p, the application might want to capture an auxiliary video of resolution
+     * 480p using this call.
+     *
+     * @param path The pathname to use.
+     */
+    public void setAuxiliaryOutputFile(String path)
+    {
+        mPrepareAuxiliaryFile = true;
+        mFdAux = null;
+        mPathAux = path;
+        setAuxVideoParameters();
+    }
+
+    /**
      * Pass in the file descriptor of the file to be written. Call this after
      * setOutputFormat() but before prepare().
      *
@@ -478,6 +598,8 @@
     // native implementation
     private native void _setOutputFile(FileDescriptor fd, long offset, long length)
         throws IllegalStateException, IOException;
+    private native void _setOutputFileAux(FileDescriptor fd)
+        throws IllegalStateException, IOException;
     private native void _prepare() throws IllegalStateException, IOException;
 
     /**
@@ -503,6 +625,22 @@
         } else {
             throw new IOException("No valid output file");
         }
+
+        if (mPrepareAuxiliaryFile) {
+            if (mPathAux != null) {
+                FileOutputStream fos = new FileOutputStream(mPathAux);
+                try {
+                    _setOutputFileAux(fos.getFD());
+                } finally {
+                    fos.close();
+                }
+            } else if (mFdAux != null) {
+                _setOutputFileAux(mFdAux);
+            } else {
+                throw new IOException("No valid output file");
+            }
+        }
+
         _prepare();
     }
 
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 3333268..bc5f9fa 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -34,6 +34,7 @@
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.provider.MediaStore.Audio;
+import android.provider.MediaStore.Files;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
 import android.provider.MediaStore.Audio.Genres;
@@ -109,41 +110,28 @@
 
     private final static String TAG = "MediaScanner";
 
-    private static final String[] AUDIO_PROJECTION = new String[] {
-            Audio.Media._ID, // 0
-            Audio.Media.DATA, // 1
-            Audio.Media.DATE_MODIFIED, // 2
+    private static final String[] FILES_PRESCAN_PROJECTION = new String[] {
+            Files.FileColumns._ID, // 0
+            Files.FileColumns.DATA, // 1
+            Files.FileColumns.FORMAT, // 2
+            Files.FileColumns.DATE_MODIFIED, // 3
     };
 
-    private static final int ID_AUDIO_COLUMN_INDEX = 0;
-    private static final int PATH_AUDIO_COLUMN_INDEX = 1;
-    private static final int DATE_MODIFIED_AUDIO_COLUMN_INDEX = 2;
+    private static final int FILES_PRESCAN_ID_COLUMN_INDEX = 0;
+    private static final int FILES_PRESCAN_PATH_COLUMN_INDEX = 1;
+    private static final int FILES_PRESCAN_FORMAT_COLUMN_INDEX = 2;
+    private static final int FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX = 3;
 
-    private static final String[] VIDEO_PROJECTION = new String[] {
-            Video.Media._ID, // 0
-            Video.Media.DATA, // 1
-            Video.Media.DATE_MODIFIED, // 2
+    private static final String[] MEDIA_PRESCAN_PROJECTION = new String[] {
+            MediaStore.MediaColumns._ID, // 0
+            MediaStore.MediaColumns.DATA, // 1
+            MediaStore.MediaColumns.DATE_MODIFIED, // 2
     };
 
-    private static final int ID_VIDEO_COLUMN_INDEX = 0;
-    private static final int PATH_VIDEO_COLUMN_INDEX = 1;
-    private static final int DATE_MODIFIED_VIDEO_COLUMN_INDEX = 2;
+    private static final int MEDIA_PRESCAN_ID_COLUMN_INDEX = 0;
+    private static final int MEDIA_PRESCAN_PATH_COLUMN_INDEX = 1;
+    private static final int MEDIA_PRESCAN_DATE_MODIFIED_COLUMN_INDEX = 2;
 
-    private static final String[] IMAGES_PROJECTION = new String[] {
-            Images.Media._ID, // 0
-            Images.Media.DATA, // 1
-            Images.Media.DATE_MODIFIED, // 2
-    };
-
-    private static final int ID_IMAGES_COLUMN_INDEX = 0;
-    private static final int PATH_IMAGES_COLUMN_INDEX = 1;
-    private static final int DATE_MODIFIED_IMAGES_COLUMN_INDEX = 2;
-
-    private static final String[] PLAYLISTS_PROJECTION = new String[] {
-            Audio.Playlists._ID, // 0
-            Audio.Playlists.DATA, // 1
-            Audio.Playlists.DATE_MODIFIED, // 2
-    };
 
     private static final String[] PLAYLIST_MEMBERS_PROJECTION = new String[] {
             Audio.Playlists.Members.PLAYLIST_ID, // 0
@@ -304,7 +292,9 @@
     private Uri mThumbsUri;
     private Uri mGenresUri;
     private Uri mPlaylistsUri;
+    private Uri mFilesUri;
     private boolean mProcessPlaylists, mProcessGenres;
+    private int mMtpObjectHandle;
 
     // used when scanning the image database so we know whether we have to prune
     // old thumbnail files
@@ -339,21 +329,23 @@
         long mRowId;
         String mPath;
         long mLastModified;
+        int mFormat;
         boolean mSeenInFileSystem;
         boolean mLastModifiedChanged;
 
-        FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified) {
+        FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified, int format) {
             mTableUri = tableUri;
             mRowId = rowId;
             mPath = path;
             mLastModified = lastModified;
+            mFormat = format;
             mSeenInFileSystem = false;
             mLastModifiedChanged = false;
         }
 
         @Override
         public String toString() {
-            return mPath;
+            return mPath + " mTableUri: " + mTableUri + " mRowId: " + mRowId;
         }
     }
 
@@ -431,6 +423,9 @@
             }
 
             mMimeType = null;
+            mFileType = 0;
+            mFileSize = fileSize;
+
             // try mimeType first, if it is specified
             if (mimeType != null) {
                 mFileType = MediaFile.getFileTypeForMimeType(mimeType);
@@ -438,7 +433,6 @@
                     mMimeType = mimeType;
                 }
             }
-            mFileSize = fileSize;
 
             // if mimeType was not specified, compute file type based on file extension.
             if (mMimeType == null) {
@@ -455,7 +449,17 @@
             }
             FileCacheEntry entry = mFileCache.get(key);
             if (entry == null) {
-                entry = new FileCacheEntry(null, 0, path, 0);
+                Uri tableUri;
+                if (MediaFile.isVideoFileType(mFileType)) {
+                    tableUri = mVideoUri;
+                } else if (MediaFile.isImageFileType(mFileType)) {
+                    tableUri = mImagesUri;
+                } else if (MediaFile.isAudioFileType(mFileType)) {
+                    tableUri = mAudioUri;
+                } else {
+                    tableUri = mFilesUri;
+                }
+                entry = new FileCacheEntry(tableUri, 0, path, 0, 0);
                 mFileCache.put(key, entry);
             }
             entry.mSeenInFileSystem = true;
@@ -500,7 +504,8 @@
             doScanFile(path, mimeType, lastModified, fileSize, false);
         }
 
-        public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {
+        public Uri doScanFile(String path, String mimeType, long lastModified,
+                long fileSize, boolean scanAlways) {
             Uri result = null;
 //            long t1 = System.currentTimeMillis();
             try {
@@ -515,7 +520,9 @@
                     boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) ||
                         (!ringtones && !notifications && !alarms && !podcasts);
 
-                    if (!MediaFile.isImageFileType(mFileType)) {
+                    // we only extract metadata for audio and video files
+                    if (MediaFile.isAudioFileType(mFileType)
+                            || MediaFile.isVideoFileType(mFileType)) {
                         processFile(path, mimeType, this);
                     }
 
@@ -555,7 +562,8 @@
                 mTitle = value;
             } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) {
                 mArtist = value.trim();
-            } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")) {
+            } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")
+                    || name.equalsIgnoreCase("band") || name.startsWith("band;")) {
                 mAlbumArtist = value.trim();
             } else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) {
                 mAlbum = value.trim();
@@ -654,21 +662,6 @@
                 boolean alarms, boolean music, boolean podcasts)
                 throws RemoteException {
             // update database
-            Uri tableUri;
-            boolean isAudio = MediaFile.isAudioFileType(mFileType);
-            boolean isVideo = MediaFile.isVideoFileType(mFileType);
-            boolean isImage = MediaFile.isImageFileType(mFileType);
-            if (isVideo) {
-                tableUri = mVideoUri;
-            } else if (isImage) {
-                tableUri = mImagesUri;
-            } else if (isAudio) {
-                tableUri = mAudioUri;
-            } else {
-                // don't add file to database if not audio, video or image
-                return null;
-            }
-            entry.mTableUri = tableUri;
 
              // use album artist if artist is missing
             if (mArtist == null || mArtist.length() == 0) {
@@ -678,20 +671,7 @@
             ContentValues values = toValues();
             String title = values.getAsString(MediaStore.MediaColumns.TITLE);
             if (title == null || TextUtils.isEmpty(title.trim())) {
-                title = values.getAsString(MediaStore.MediaColumns.DATA);
-                // extract file name after last slash
-                int lastSlash = title.lastIndexOf('/');
-                if (lastSlash >= 0) {
-                    lastSlash++;
-                    if (lastSlash < title.length()) {
-                        title = title.substring(lastSlash);
-                    }
-                }
-                // truncate the file extension (if any)
-                int lastDot = title.lastIndexOf('.');
-                if (lastDot > 0) {
-                    title = title.substring(0, lastDot);
-                }
+                title = MediaFile.getFileTitle(values.getAsString(MediaStore.MediaColumns.DATA));
                 values.put(MediaStore.MediaColumns.TITLE, title);
             }
             String album = values.getAsString(Audio.Media.ALBUM);
@@ -715,7 +695,7 @@
                 }
             }
             long rowId = entry.mRowId;
-            if (isAudio && rowId == 0) {
+            if (MediaFile.isAudioFileType(mFileType) && (rowId == 0 || mMtpObjectHandle != 0)) {
                 // Only set these for new entries. For existing entries, they
                 // may have been modified later, and we want to keep the current
                 // values so that custom ringtones still show up in the ringtone
@@ -768,8 +748,15 @@
                 }
             }
 
+            Uri tableUri = entry.mTableUri;
             Uri result = null;
             if (rowId == 0) {
+                if (mMtpObjectHandle != 0) {
+                    values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
+                }
+                if (tableUri == mFilesUri) {
+                    values.put(Files.FileColumns.FORMAT, MediaFile.getFormatCode(entry.mPath, mMimeType));
+                }
                 // new file, insert it
                 result = mMediaProvider.insert(tableUri, values);
                 if (result != null) {
@@ -885,7 +872,7 @@
 
     }; // end of anonymous MediaScannerClient instance
 
-    private void prescan(String filePath) throws RemoteException {
+    private void prescan(String filePath, boolean prescanFiles) throws RemoteException {
         Cursor c = null;
         String where = null;
         String[] selectionArgs = null;
@@ -901,138 +888,43 @@
             mPlayLists.clear();
         }
 
+        if (filePath != null) {
+            // query for only one file
+            where = Files.FileColumns.DATA + "=?";
+            selectionArgs = new String[] { filePath };
+        }
+
         // Build the list of files from the content provider
         try {
-            // Read existing files from the audio table
-            if (filePath != null) {
-                where = MediaStore.Audio.Media.DATA + "=?";
-                selectionArgs = new String[] { filePath };
-            }
-            c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where, selectionArgs, null);
+            if (prescanFiles) {
+                // First read existing files from the files table
 
-            if (c != null) {
-                try {
-                    while (c.moveToNext()) {
-                        long rowId = c.getLong(ID_AUDIO_COLUMN_INDEX);
-                        String path = c.getString(PATH_AUDIO_COLUMN_INDEX);
-                        long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX);
-
-                        // Only consider entries with absolute path names.
-                        // This allows storing URIs in the database without the
-                        // media scanner removing them.
-                        if (path.startsWith("/")) {
-                            String key = path;
-                            if (mCaseInsensitivePaths) {
-                                key = path.toLowerCase();
-                            }
-                            mFileCache.put(key, new FileCacheEntry(mAudioUri, rowId, path,
-                                    lastModified));
-                        }
-                    }
-                } finally {
-                    c.close();
-                    c = null;
-                }
-            }
-
-            // Read existing files from the video table
-            if (filePath != null) {
-                where = MediaStore.Video.Media.DATA + "=?";
-            } else {
-                where = null;
-            }
-            c = mMediaProvider.query(mVideoUri, VIDEO_PROJECTION, where, selectionArgs, null);
-
-            if (c != null) {
-                try {
-                    while (c.moveToNext()) {
-                        long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX);
-                        String path = c.getString(PATH_VIDEO_COLUMN_INDEX);
-                        long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX);
-
-                        // Only consider entries with absolute path names.
-                        // This allows storing URIs in the database without the
-                        // media scanner removing them.
-                        if (path.startsWith("/")) {
-                            String key = path;
-                            if (mCaseInsensitivePaths) {
-                                key = path.toLowerCase();
-                            }
-                            mFileCache.put(key, new FileCacheEntry(mVideoUri, rowId, path,
-                                    lastModified));
-                        }
-                    }
-                } finally {
-                    c.close();
-                    c = null;
-                }
-            }
-
-            // Read existing files from the images table
-            if (filePath != null) {
-                where = MediaStore.Images.Media.DATA + "=?";
-            } else {
-                where = null;
-            }
-            mOriginalCount = 0;
-            c = mMediaProvider.query(mImagesUri, IMAGES_PROJECTION, where, selectionArgs, null);
-
-            if (c != null) {
-                try {
-                    mOriginalCount = c.getCount();
-                    while (c.moveToNext()) {
-                        long rowId = c.getLong(ID_IMAGES_COLUMN_INDEX);
-                        String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
-                       long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX);
-
-                       // Only consider entries with absolute path names.
-                       // This allows storing URIs in the database without the
-                       // media scanner removing them.
-                       if (path.startsWith("/")) {
-                           String key = path;
-                           if (mCaseInsensitivePaths) {
-                               key = path.toLowerCase();
-                           }
-                           mFileCache.put(key, new FileCacheEntry(mImagesUri, rowId, path,
-                                   lastModified));
-                       }
-                    }
-                } finally {
-                    c.close();
-                    c = null;
-                }
-            }
-
-            if (mProcessPlaylists) {
-                // Read existing files from the playlists table
-                if (filePath != null) {
-                    where = MediaStore.Audio.Playlists.DATA + "=?";
-                } else {
-                    where = null;
-                }
-                c = mMediaProvider.query(mPlaylistsUri, PLAYLISTS_PROJECTION, where, selectionArgs, null);
+                c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
+                        where, selectionArgs, null);
 
                 if (c != null) {
-                    try {
-                        while (c.moveToNext()) {
-                            String path = c.getString(PATH_PLAYLISTS_COLUMN_INDEX);
+                    while (c.moveToNext()) {
+                        long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
+                        String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
+                        int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
+                        long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
 
-                            if (path != null && path.length() > 0) {
-                                long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX);
-                                long lastModified = c.getLong(DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX);
-
-                                String key = path;
-                                if (mCaseInsensitivePaths) {
-                                    key = path.toLowerCase();
-                                }
-                                mFileCache.put(key, new FileCacheEntry(mPlaylistsUri, rowId, path,
-                                        lastModified));
+                        // Only consider entries with absolute path names.
+                        // This allows storing URIs in the database without the
+                        // media scanner removing them.
+                        if (path.startsWith("/")) {
+                            String key = path;
+                            if (mCaseInsensitivePaths) {
+                                key = path.toLowerCase();
                             }
+
+                            FileCacheEntry entry = new FileCacheEntry(mFilesUri, rowId, path,
+                                    lastModified, format);
+                            mFileCache.put(key, entry);
                         }
-                    } finally {
-                        c.close();
-                        c = null;
                     }
+                    c.close();
+                    c = null;
                 }
             }
         }
@@ -1107,12 +999,14 @@
             // remove database entries for files that no longer exist.
             boolean fileMissing = false;
 
-            if (!entry.mSeenInFileSystem) {
-                if (inScanDirectory(path, directories)) {
+            if (!entry.mSeenInFileSystem && !MtpConstants.isAbstractObject(entry.mFormat)) {
+                if (entry.mFormat != MtpConstants.FORMAT_ASSOCIATION &&
+                        inScanDirectory(path, directories)) {
                     // we didn't see this file in the scan directory.
                     fileMissing = true;
                 } else {
-                    // the file is outside of our scan directory,
+                    // the file actually a directory or other abstract object
+                    // or is outside of our scan directory,
                     // so we need to check for file existence here.
                     File testFile = new File(path);
                     if (!testFile.exists()) {
@@ -1132,9 +1026,11 @@
                     ContentValues values = new ContentValues();
                     values.put(MediaStore.Audio.Playlists.DATA, "");
                     values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, 0);
-                    mMediaProvider.update(ContentUris.withAppendedId(mPlaylistsUri, entry.mRowId), values, null, null);
+                    mMediaProvider.update(ContentUris.withAppendedId(mPlaylistsUri, entry.mRowId),
+                            values, null, null);
                 } else {
-                    mMediaProvider.delete(ContentUris.withAppendedId(entry.mTableUri, entry.mRowId), null, null);
+                    mMediaProvider.delete(ContentUris.withAppendedId(mFilesUri, entry.mRowId),
+                            null, null);
                     iterator.remove();
                 }
             }
@@ -1162,6 +1058,7 @@
         mVideoUri = Video.Media.getContentUri(volumeName);
         mImagesUri = Images.Media.getContentUri(volumeName);
         mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
+        mFilesUri = Files.getContentUri(volumeName);
 
         if (!volumeName.equals("internal")) {
             // we only support playlists on external media
@@ -1170,9 +1067,12 @@
             mGenreCache = new HashMap<String, Uri>();
             mGenresUri = Genres.getContentUri(volumeName);
             mPlaylistsUri = Playlists.getContentUri(volumeName);
-            // assuming external storage is FAT (case insensitive), except on the simulator.
-            if ( Process.supportsProcesses()) {
-                mCaseInsensitivePaths = true;
+
+            mCaseInsensitivePaths = !mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_caseSensitiveExternalStorage);
+            if (!Process.supportsProcesses()) {
+                // Simulator uses host file system, so it should be case sensitive.
+                mCaseInsensitivePaths = false;
             }
         }
     }
@@ -1181,11 +1081,11 @@
         try {
             long start = System.currentTimeMillis();
             initialize(volumeName);
-            prescan(null);
+            prescan(null, true);
             long prescan = System.currentTimeMillis();
 
             for (int i = 0; i < directories.length; i++) {
-                processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
+                processDirectory(directories[i], mClient);
             }
             long scan = System.currentTimeMillis();
             postscan(directories);
@@ -1212,7 +1112,7 @@
     public Uri scanSingleFile(String path, String volumeName, String mimeType) {
         try {
             initialize(volumeName);
-            prescan(path);
+            prescan(path, true);
 
             File file = new File(path);
 
@@ -1227,6 +1127,36 @@
         }
     }
 
+    public void scanMtpFile(String path, String volumeName, int objectHandle, int format) {
+        MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
+        int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
+
+        if (!MediaFile.isAudioFileType(fileType) && !MediaFile.isVideoFileType(fileType) &&
+            !MediaFile.isImageFileType(fileType) && !MediaFile.isPlayListFileType(fileType)) {
+            // nothing to do
+            return;
+        }
+
+        mMtpObjectHandle = objectHandle;
+        try {
+            initialize(volumeName);
+            // MTP will create a file entry for us so we don't want to do it in prescan
+            prescan(path, false);
+
+            File file = new File(path);
+
+            // lastModified is in milliseconds on Files.
+            long lastModifiedSeconds = file.lastModified() / 1000;
+
+            // always scan the file, so we can return the content://media Uri for existing files
+            mClient.doScanFile(path, mediaFileType.mimeType, lastModifiedSeconds, file.length(), true);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
+        } finally {
+            mMtpObjectHandle = 0;
+        }
+    }
+
     // returns the number of matching file/directory names, starting from the right
     private int matchPaths(String path1, String path2) {
         int result = 0;
@@ -1506,7 +1436,7 @@
         }
     }
 
-    private native void processDirectory(String path, String extensions, MediaScannerClient client);
+    private native void processDirectory(String path, MediaScannerClient client);
     private native void processFile(String path, String mimeType, MediaScannerClient client);
     public native void setLocale(String locale);
 
diff --git a/media/java/android/media/MtpClient.java b/media/java/android/media/MtpClient.java
new file mode 100644
index 0000000..98da1f6
--- /dev/null
+++ b/media/java/android/media/MtpClient.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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() throws Throwable {
+        try {
+            native_finalize();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    public boolean start() {
+        return native_start();
+    }
+
+    public void stop() {
+        native_stop();
+    }
+
+    public boolean deleteObject(int deviceID, long objectID) {
+        return native_delete_object(deviceID, objectID);
+    }
+
+    public long getParent(int deviceID, long objectID) {
+        return native_get_parent(deviceID, objectID);
+    }
+
+    public long getStorageID(int deviceID, long 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, long 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, long objectID);
+    private native long native_get_parent(int deviceID, long objectID);
+    private native long native_get_storage_id(int deviceID, long objectID);
+    private native ParcelFileDescriptor native_open_file(int deviceID, long objectID);
+}
diff --git a/media/java/android/media/MtpConstants.java b/media/java/android/media/MtpConstants.java
new file mode 100644
index 0000000..a7d33ce
--- /dev/null
+++ b/media/java/android/media/MtpConstants.java
@@ -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.
+ */
+
+package android.media;
+
+/**
+ * {@hide}
+ */
+public final class MtpConstants {
+
+// MTP Response Codes
+    public static final int RESPONSE_UNDEFINED = 0x2000;
+    public static final int RESPONSE_OK = 0x2001;
+    public static final int RESPONSE_GENERAL_ERROR = 0x2002;
+    public static final int RESPONSE_SESSION_NOT_OPEN = 0x2003;
+    public static final int RESPONSE_INVALID_TRANSACTION_ID = 0x2004;
+    public static final int RESPONSE_OPERATION_NOT_SUPPORTED = 0x2005;
+    public static final int RESPONSE_PARAMETER_NOT_SUPPORTED = 0x2006;
+    public static final int RESPONSE_INCOMPLETE_TRANSFER = 0x2007;
+    public static final int RESPONSE_INVALID_STORAGE_ID = 0x2008;
+    public static final int RESPONSE_INVALID_OBJECT_HANDLE = 0x2009;
+    public static final int RESPONSE_DEVICE_PROP_NOT_SUPPORTED = 0x200A;
+    public static final int RESPONSE_INVALID_OBJECT_FORMAT_CODE = 0x200B;
+    public static final int RESPONSE_STORAGE_FULL = 0x200C;
+    public static final int RESPONSE_OBJECT_WRITE_PROTECTED = 0x200D;
+    public static final int RESPONSE_STORE_READ_ONLY = 0x200E;
+    public static final int RESPONSE_ACCESS_DENIED = 0x200F;
+    public static final int RESPONSE_NO_THUMBNAIL_PRESENT = 0x2010;
+    public static final int RESPONSE_SELF_TEST_FAILED = 0x2011;
+    public static final int RESPONSE_PARTIAL_DELETION = 0x2012;
+    public static final int RESPONSE_STORE_NOT_AVAILABLE = 0x2013;
+    public static final int RESPONSE_SPECIFICATION_BY_FORMAT_UNSUPPORTED = 0x2014;
+    public static final int RESPONSE_NO_VALID_OBJECT_INFO = 0x2015;
+    public static final int RESPONSE_INVALID_CODE_FORMAT = 0x2016;
+    public static final int RESPONSE_UNKNOWN_VENDOR_CODE = 0x2017;
+    public static final int RESPONSE_CAPTURE_ALREADY_TERMINATED = 0x2018;
+    public static final int RESPONSE_DEVICE_BUSY = 0x2019;
+    public static final int RESPONSE_INVALID_PARENT_OBJECT = 0x201A;
+    public static final int RESPONSE_INVALID_DEVICE_PROP_FORMAT = 0x201B;
+    public static final int RESPONSE_INVALID_DEVICE_PROP_VALUE = 0x201C;
+    public static final int RESPONSE_INVALID_PARAMETER = 0x201D;
+    public static final int RESPONSE_SESSION_ALREADY_OPEN = 0x201E;
+    public static final int RESPONSE_TRANSACTION_CANCELLED = 0x201F;
+    public static final int RESPONSE_SPECIFICATION_OF_DESTINATION_UNSUPPORTED = 0x2020;
+    public static final int RESPONSE_INVALID_OBJECT_PROP_CODE = 0xA801;
+    public static final int RESPONSE_INVALID_OBJECT_PROP_FORMAT = 0xA802;
+    public static final int RESPONSE_INVALID_OBJECT_PROP_VALUE = 0xA803;
+    public static final int RESPONSE_INVALID_OBJECT_REFERENCE = 0xA804;
+    public static final int RESPONSE_GROUP_NOT_SUPPORTED = 0xA805;
+    public static final int RESPONSE_INVALID_DATASET = 0xA806;
+    public static final int RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED = 0xA807;
+    public static final int RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED = 0xA808;
+    public static final int RESPONSE_OBJECT_TOO_LARGE = 0xA809;
+    public static final int RESPONSE_OBJECT_PROP_NOT_SUPPORTED = 0xA80A;
+
+    // MTP format codes
+    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;
+
+    public static boolean isAbstractObject(int format) {
+        switch (format) {
+            case FORMAT_ABSTRACT_MULTIMEDIA_ALBUM:
+            case FORMAT_ABSTRACT_IMAGE_ALBUM:
+            case FORMAT_ABSTRACT_AUDIO_ALBUM:
+            case FORMAT_ABSTRACT_VIDEO_ALBUM:
+            case FORMAT_ABSTRACT_AV_PLAYLIST:
+            case FORMAT_ABSTRACT_CONTACT_GROUP:
+            case FORMAT_ABSTRACT_MESSAGE_FOLDER:
+            case FORMAT_ABSTRACT_CHAPTERED_PRODUCTION:
+            case FORMAT_ABSTRACT_AUDIO_PLAYLIST:
+            case FORMAT_ABSTRACT_VIDEO_PLAYLIST:
+            case FORMAT_ABSTRACT_MEDIACAST:
+            case FORMAT_ABSTRACT_DOCUMENT:
+            case FORMAT_ABSTRACT_MESSSAGE:
+            case FORMAT_ABSTRACT_CONTACT:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    // MTP object properties
+    public static final int PROPERTY_STORAGE_ID = 0xDC01;
+    public static final int PROPERTY_OBJECT_FORMAT = 0xDC02;
+    public static final int PROPERTY_PROTECTION_STATUS = 0xDC03;
+    public static final int PROPERTY_OBJECT_SIZE = 0xDC04;
+    public static final int PROPERTY_ASSOCIATION_TYPE = 0xDC05;
+    public static final int PROPERTY_ASSOCIATION_DESC = 0xDC06;
+    public static final int PROPERTY_OBJECT_FILE_NAME = 0xDC07;
+    public static final int PROPERTY_DATE_CREATED = 0xDC08;
+    public static final int PROPERTY_DATE_MODIFIED = 0xDC09;
+    public static final int PROPERTY_KEYWORDS = 0xDC0A;
+    public static final int PROPERTY_PARENT_OBJECT = 0xDC0B;
+    public static final int PROPERTY_ALLOWED_FOLDER_CONTENTS = 0xDC0C;
+    public static final int PROPERTY_HIDDEN = 0xDC0D;
+    public static final int PROPERTY_SYSTEM_OBJECT = 0xDC0E;
+    public static final int PROPERTY_PERSISTENT_UID = 0xDC41;
+    public static final int PROPERTY_SYNC_ID = 0xDC42;
+    public static final int PROPERTY_PROPERTY_BAG = 0xDC43;
+    public static final int PROPERTY_NAME = 0xDC44;
+    public static final int PROPERTY_CREATED_BY = 0xDC45;
+    public static final int PROPERTY_ARTIST = 0xDC46;
+    public static final int PROPERTY_DATE_AUTHORED = 0xDC47;
+    public static final int PROPERTY_DESCRIPTION = 0xDC48;
+    public static final int PROPERTY_URL_REFERENCE = 0xDC49;
+    public static final int PROPERTY_LANGUAGE_LOCALE = 0xDC4A;
+    public static final int PROPERTY_COPYRIGHT_INFORMATION = 0xDC4B;
+    public static final int PROPERTY_SOURCE = 0xDC4C;
+    public static final int PROPERTY_ORIGIN_LOCATION = 0xDC4D;
+    public static final int PROPERTY_DATE_ADDED = 0xDC4E;
+    public static final int PROPERTY_NON_CONSUMABLE = 0xDC4F;
+    public static final int PROPERTY_CORRUPT_UNPLAYABLE = 0xDC50;
+    public static final int PROPERTY_PRODUCER_SERIAL_NUMBER = 0xDC51;
+    public static final int PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT = 0xDC81;
+    public static final int PROPERTY_REPRESENTATIVE_SAMPLE_SIZE = 0xDC82;
+    public static final int PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT = 0xDC83;
+    public static final int PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH = 0xDC84;
+    public static final int PROPERTY_REPRESENTATIVE_SAMPLE_DURATION = 0xDC85;
+    public static final int PROPERTY_REPRESENTATIVE_SAMPLE_DATA = 0xDC86;
+    public static final int PROPERTY_WIDTH = 0xDC87;
+    public static final int PROPERTY_HEIGHT = 0xDC88;
+    public static final int PROPERTY_DURATION = 0xDC89;
+    public static final int PROPERTY_RATING = 0xDC8A;
+    public static final int PROPERTY_TRACK = 0xDC8B;
+    public static final int PROPERTY_GENRE = 0xDC8C;
+    public static final int PROPERTY_CREDITS = 0xDC8D;
+    public static final int PROPERTY_LYRICS = 0xDC8E;
+    public static final int PROPERTY_SUBSCRIPTION_CONTENT_ID = 0xDC8F;
+    public static final int PROPERTY_PRODUCED_BY = 0xDC90;
+    public static final int PROPERTY_USE_COUNT = 0xDC91;
+    public static final int PROPERTY_SKIP_COUNT = 0xDC92;
+    public static final int PROPERTY_LAST_ACCESSED = 0xDC93;
+    public static final int PROPERTY_PARENTAL_RATING = 0xDC94;
+    public static final int PROPERTY_META_GENRE = 0xDC95;
+    public static final int PROPERTY_COMPOSER = 0xDC96;
+    public static final int PROPERTY_EFFECTIVE_RATING = 0xDC97;
+    public static final int PROPERTY_SUBTITLE = 0xDC98;
+    public static final int PROPERTY_ORIGINAL_RELEASE_DATE = 0xDC99;
+    public static final int PROPERTY_ALBUM_NAME = 0xDC9A;
+    public static final int PROPERTY_ALBUM_ARTIST = 0xDC9B;
+    public static final int PROPERTY_MOOD = 0xDC9C;
+    public static final int PROPERTY_DRM_STATUS = 0xDC9D;
+    public static final int PROPERTY_SUB_DESCRIPTION = 0xDC9E;
+    public static final int PROPERTY_IS_CROPPED = 0xDCD1;
+    public static final int PROPERTY_IS_COLOUR_CORRECTED = 0xDCD2;
+    public static final int PROPERTY_IMAGE_BIT_DEPTH = 0xDCD3;
+    public static final int PROPERTY_F_NUMBER = 0xDCD4;
+    public static final int PROPERTY_EXPOSURE_TIME = 0xDCD5;
+    public static final int PROPERTY_EXPOSURE_INDEX = 0xDCD6;
+    public static final int PROPERTY_TOTAL_BITRATE = 0xDE91;
+    public static final int PROPERTY_BITRATE_TYPE = 0xDE92;
+    public static final int PROPERTY_SAMPLE_RATE = 0xDE93;
+    public static final int PROPERTY_NUMBER_OF_CHANNELS = 0xDE94;
+    public static final int PROPERTY_AUDIO_BIT_DEPTH = 0xDE95;
+    public static final int PROPERTY_SCAN_TYPE = 0xDE97;
+    public static final int PROPERTY_AUDIO_WAVE_CODEC = 0xDE99;
+    public static final int PROPERTY_AUDIO_BITRATE = 0xDE9A;
+    public static final int PROPERTY_VIDEO_FOURCC_CODEC = 0xDE9B;
+    public static final int PROPERTY_VIDEO_BITRATE = 0xDE9C;
+    public static final int PROPERTY_FRAMES_PER_THOUSAND_SECONDS = 0xDE9D;
+    public static final int PROPERTY_KEYFRAME_DISTANCE = 0xDE9E;
+    public static final int PROPERTY_BUFFER_SIZE = 0xDE9F;
+    public static final int PROPERTY_ENCODING_QUALITY = 0xDEA0;
+    public static final int PROPERTY_ENCODING_PROFILE = 0xDEA1;
+    public static final int PROPERTY_DISPLAY_NAME = 0xDCE0;
+    public static final int PROPERTY_BODY_TEXT = 0xDCE1;
+    public static final int PROPERTY_SUBJECT = 0xDCE2;
+    public static final int PROPERTY_PRIORITY = 0xDCE3;
+    public static final int PROPERTY_GIVEN_NAME = 0xDD00;
+    public static final int PROPERTY_MIDDLE_NAMES = 0xDD01;
+    public static final int PROPERTY_FAMILY_NAME = 0xDD02;
+    public static final int PROPERTY_PREFIX = 0xDD03;
+    public static final int PROPERTY_SUFFIX = 0xDD04;
+    public static final int PROPERTY_PHONETIC_GIVEN_NAME = 0xDD05;
+    public static final int PROPERTY_PHONETIC_FAMILY_NAME = 0xDD06;
+    public static final int PROPERTY_EMAIL_PRIMARY = 0xDD07;
+    public static final int PROPERTY_EMAIL_PERSONAL_1 = 0xDD08;
+    public static final int PROPERTY_EMAIL_PERSONAL_2 = 0xDD09;
+    public static final int PROPERTY_EMAIL_BUSINESS_1 = 0xDD0A;
+    public static final int PROPERTY_EMAIL_BUSINESS_2 = 0xDD0B;
+    public static final int PROPERTY_EMAIL_OTHERS = 0xDD0C;
+    public static final int PROPERTY_PHONE_NUMBER_PRIMARY = 0xDD0D;
+    public static final int PROPERTY_PHONE_NUMBER_PERSONAL = 0xDD0E;
+    public static final int PROPERTY_PHONE_NUMBER_PERSONAL_2 = 0xDD0F;
+    public static final int PROPERTY_PHONE_NUMBER_BUSINESS = 0xDD10;
+    public static final int PROPERTY_PHONE_NUMBER_BUSINESS_2 = 0xDD11;
+    public static final int PROPERTY_PHONE_NUMBER_MOBILE= 0xDD12;
+    public static final int PROPERTY_PHONE_NUMBER_MOBILE_2 = 0xDD13;
+    public static final int PROPERTY_FAX_NUMBER_PRIMARY = 0xDD14;
+    public static final int PROPERTY_FAX_NUMBER_PERSONAL= 0xDD15;
+    public static final int PROPERTY_FAX_NUMBER_BUSINESS= 0xDD16;
+    public static final int PROPERTY_PAGER_NUMBER = 0xDD17;
+    public static final int PROPERTY_PHONE_NUMBER_OTHERS= 0xDD18;
+    public static final int PROPERTY_PRIMARY_WEB_ADDRESS= 0xDD19;
+    public static final int PROPERTY_PERSONAL_WEB_ADDRESS = 0xDD1A;
+    public static final int PROPERTY_BUSINESS_WEB_ADDRESS = 0xDD1B;
+    public static final int PROPERTY_INSTANT_MESSANGER_ADDRESS = 0xDD1C;
+    public static final int PROPERTY_INSTANT_MESSANGER_ADDRESS_2 = 0xDD1D;
+    public static final int PROPERTY_INSTANT_MESSANGER_ADDRESS_3 = 0xDD1E;
+    public static final int PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL = 0xDD1F;
+    public static final int PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1 = 0xDD20;
+    public static final int PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2 = 0xDD21;
+    public static final int PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY = 0xDD22;
+    public static final int PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION = 0xDD23;
+    public static final int PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE = 0xDD24;
+    public static final int PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY = 0xDD25;
+    public static final int PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL = 0xDD26;
+    public static final int PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1 = 0xDD27;
+    public static final int PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2 = 0xDD28;
+    public static final int PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY = 0xDD29;
+    public static final int PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION = 0xDD2A;
+    public static final int PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE = 0xDD2B;
+    public static final int PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY = 0xDD2C;
+    public static final int PROPERTY_POSTAL_ADDRESS_OTHER_FULL = 0xDD2D;
+    public static final int PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1 = 0xDD2E;
+    public static final int PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2 = 0xDD2F;
+    public static final int PROPERTY_POSTAL_ADDRESS_OTHER_CITY = 0xDD30;
+    public static final int PROPERTY_POSTAL_ADDRESS_OTHER_REGION = 0xDD31;
+    public static final int PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE = 0xDD32;
+    public static final int PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY = 0xDD33;
+    public static final int PROPERTY_ORGANIZATION_NAME = 0xDD34;
+    public static final int PROPERTY_PHONETIC_ORGANIZATION_NAME = 0xDD35;
+    public static final int PROPERTY_ROLE = 0xDD36;
+    public static final int PROPERTY_BIRTHDATE = 0xDD37;
+    public static final int PROPERTY_MESSAGE_TO = 0xDD40;
+    public static final int PROPERTY_MESSAGE_CC = 0xDD41;
+    public static final int PROPERTY_MESSAGE_BCC = 0xDD42;
+    public static final int PROPERTY_MESSAGE_READ = 0xDD43;
+    public static final int PROPERTY_MESSAGE_RECEIVED_TIME = 0xDD44;
+    public static final int PROPERTY_MESSAGE_SENDER = 0xDD45;
+    public static final int PROPERTY_ACTIVITY_BEGIN_TIME = 0xDD50;
+    public static final int PROPERTY_ACTIVITY_END_TIME = 0xDD51;
+    public static final int PROPERTY_ACTIVITY_LOCATION = 0xDD52;
+    public static final int PROPERTY_ACTIVITY_REQUIRED_ATTENDEES = 0xDD54;
+    public static final int PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES = 0xDD55;
+    public static final int PROPERTY_ACTIVITY_RESOURCES = 0xDD56;
+    public static final int PROPERTY_ACTIVITY_ACCEPTED = 0xDD57;
+    public static final int PROPERTY_ACTIVITY_TENTATIVE = 0xDD58;
+    public static final int PROPERTY_ACTIVITY_DECLINED = 0xDD59;
+    public static final int PROPERTY_ACTIVITY_REMAINDER_TIME = 0xDD5A;
+    public static final int PROPERTY_ACTIVITY_OWNER = 0xDD5B;
+    public static final int PROPERTY_ACTIVITY_STATUS = 0xDD5C;
+    public static final int PROPERTY_OWNER = 0xDD5D;
+    public static final int PROPERTY_EDITOR = 0xDD5E;
+    public static final int PROPERTY_WEBMASTER = 0xDD5F;
+    public static final int PROPERTY_URL_SOURCE = 0xDD60;
+    public static final int PROPERTY_URL_DESTINATION = 0xDD61;
+    public static final int PROPERTY_TIME_BOOKMARK = 0xDD62;
+    public static final int PROPERTY_OBJECT_BOOKMARK = 0xDD63;
+    public static final int PROPERTY_BYTE_BOOKMARK = 0xDD64;
+    public static final int PROPERTY_LAST_BUILD_DATE = 0xDD70;
+    public static final int PROPERTY_TIME_TO_LIVE = 0xDD71;
+    public static final int PROPERTY_MEDIA_GUID = 0xDD72;
+
+    // MTP device properties
+    public static final int DEVICE_PROPERTY_UNDEFINED = 0x5000;
+    public static final int DEVICE_PROPERTY_BATTERY_LEVEL = 0x5001;
+    public static final int DEVICE_PROPERTY_FUNCTIONAL_MODE = 0x5002;
+    public static final int DEVICE_PROPERTY_IMAGE_SIZE = 0x5003;
+    public static final int DEVICE_PROPERTY_COMPRESSION_SETTING = 0x5004;
+    public static final int DEVICE_PROPERTY_WHITE_BALANCE = 0x5005;
+    public static final int DEVICE_PROPERTY_RGB_GAIN = 0x5006;
+    public static final int DEVICE_PROPERTY_F_NUMBER = 0x5007;
+    public static final int DEVICE_PROPERTY_FOCAL_LENGTH = 0x5008;
+    public static final int DEVICE_PROPERTY_FOCUS_DISTANCE = 0x5009;
+    public static final int DEVICE_PROPERTY_FOCUS_MODE = 0x500A;
+    public static final int DEVICE_PROPERTY_EXPOSURE_METERING_MODE = 0x500B;
+    public static final int DEVICE_PROPERTY_FLASH_MODE = 0x500C;
+    public static final int DEVICE_PROPERTY_EXPOSURE_TIME = 0x500D;
+    public static final int DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE = 0x500E;
+    public static final int DEVICE_PROPERTY_EXPOSURE_INDEX = 0x500F;
+    public static final int DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION = 0x5010;
+    public static final int DEVICE_PROPERTY_DATETIME = 0x5011;
+    public static final int DEVICE_PROPERTY_CAPTURE_DELAY = 0x5012;
+    public static final int DEVICE_PROPERTY_STILL_CAPTURE_MODE = 0x5013;
+    public static final int DEVICE_PROPERTY_CONTRAST = 0x5014;
+    public static final int DEVICE_PROPERTY_SHARPNESS = 0x5015;
+    public static final int DEVICE_PROPERTY_DIGITAL_ZOOM = 0x5016;
+    public static final int DEVICE_PROPERTY_EFFECT_MODE = 0x5017;
+    public static final int DEVICE_PROPERTY_BURST_NUMBER= 0x5018;
+    public static final int DEVICE_PROPERTY_BURST_INTERVAL = 0x5019;
+    public static final int DEVICE_PROPERTY_TIMELAPSE_NUMBER = 0x501A;
+    public static final int DEVICE_PROPERTY_TIMELAPSE_INTERVAL = 0x501B;
+    public static final int DEVICE_PROPERTY_FOCUS_METERING_MODE = 0x501C;
+    public static final int DEVICE_PROPERTY_UPLOAD_URL = 0x501D;
+    public static final int DEVICE_PROPERTY_ARTIST = 0x501E;
+    public static final int DEVICE_PROPERTY_COPYRIGHT_INFO = 0x501F;
+    public static final int DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER = 0xD401;
+    public static final int DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME = 0xD402;
+    public static final int DEVICE_PROPERTY_VOLUME = 0xD403;
+    public static final int DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED = 0xD404;
+    public static final int DEVICE_PROPERTY_DEVICE_ICON = 0xD405;
+    public static final int DEVICE_PROPERTY_PLAYBACK_RATE = 0xD410;
+    public static final int DEVICE_PROPERTY_PLAYBACK_OBJECT = 0xD411;
+    public static final int DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX = 0xD412;
+    public static final int DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO = 0xD406;
+    public static final int DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE = 0xD407;
+
+
+    /**
+     * 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/media/java/android/media/MtpCursor.java b/media/java/android/media/MtpCursor.java
new file mode 100644
index 0000000..ff8799a
--- /dev/null
+++ b/media/java/android/media/MtpCursor.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 queryType */
+    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;
+
+    /** The names of the columns in the projection */
+    private String[] mColumns;
+
+    /** The number of rows in the cursor */
+    private int mCount = NO_COUNT;
+
+
+    public MtpCursor(MtpClient client, int queryType, int deviceID, long storageID, long objectID,
+            String[] projection) {
+        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() {
+        try {
+            native_finalize();
+        } finally {
+            super.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, long storageID, long 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..630d7112e
--- /dev/null
+++ b/media/java/android/media/MtpDatabase.java
@@ -0,0 +1,824 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.MediaStore.Audio;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.Mtp;
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+public class MtpDatabase {
+
+    private static final String TAG = "MtpDatabase";
+
+    private final Context mContext;
+    private final IContentProvider mMediaProvider;
+    private final String mVolumeName;
+    private final Uri mObjectsUri;
+
+    // true if the database has been modified in the current MTP session
+    private boolean mDatabaseModified;
+
+    // database for writable MTP device properties
+    private SQLiteDatabase mDevicePropDb;
+    private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
+
+    // FIXME - this should be passed in via the constructor
+    private final int mStorageID = 0x00010001;
+
+    private static final String[] ID_PROJECTION = new String[] {
+            Files.FileColumns._ID, // 0
+    };
+    private static final String[] PATH_SIZE_PROJECTION = new String[] {
+            Files.FileColumns._ID, // 0
+            Files.FileColumns.DATA, // 1
+            Files.FileColumns.SIZE, // 2
+    };
+    private static final String[] OBJECT_INFO_PROJECTION = new String[] {
+            Files.FileColumns._ID, // 0
+            Files.FileColumns.DATA, // 1
+            Files.FileColumns.FORMAT, // 2
+            Files.FileColumns.PARENT, // 3
+            Files.FileColumns.SIZE, // 4
+            Files.FileColumns.DATE_MODIFIED, // 5
+    };
+    private static final String ID_WHERE = Files.FileColumns._ID + "=?";
+    private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
+    private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
+    private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND "
+                                            + Files.FileColumns.FORMAT + "=?";
+
+    private static final String[] DEVICE_PROPERTY_PROJECTION = new String[] { "_id", "value" };
+    private  static final String DEVICE_PROPERTY_WHERE = "code=?";
+
+    private final MediaScanner mMediaScanner;
+
+    static {
+        System.loadLibrary("media_jni");
+    }
+
+    public MtpDatabase(Context context, String volumeName) {
+        native_setup();
+
+        mContext = context;
+        mMediaProvider = context.getContentResolver().acquireProvider("media");
+        mVolumeName = volumeName;
+        mObjectsUri = Files.getMtpObjectsUri(volumeName);
+        mMediaScanner = new MediaScanner(context);
+        openDevicePropertiesDatabase(context);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            native_finalize();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void openDevicePropertiesDatabase(Context context) {
+        mDevicePropDb = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
+        int version = mDevicePropDb.getVersion();
+
+        // initialize if necessary
+        if (version != DEVICE_PROPERTIES_DATABASE_VERSION) {
+            mDevicePropDb.execSQL("CREATE TABLE properties (" +
+                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    "code INTEGER UNIQUE ON CONFLICT REPLACE," +
+                    "value TEXT" +
+                    ");");
+            mDevicePropDb.execSQL("CREATE INDEX property_index ON properties (code);");
+            mDevicePropDb.setVersion(DEVICE_PROPERTIES_DATABASE_VERSION);
+        }
+    }
+
+    private int beginSendObject(String path, int format, int parent,
+                         int storage, long size, long modified) {
+        mDatabaseModified = true;
+        ContentValues values = new ContentValues();
+        values.put(Files.FileColumns.DATA, path);
+        values.put(Files.FileColumns.FORMAT, format);
+        values.put(Files.FileColumns.PARENT, parent);
+        // storage is ignored for now
+        values.put(Files.FileColumns.SIZE, size);
+        values.put(Files.FileColumns.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 == MtpConstants.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 {
+                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[] getSupportedPlaybackFormats() {
+        return new int[] {
+            // allow transfering arbitrary files
+            MtpConstants.FORMAT_UNDEFINED,
+
+            MtpConstants.FORMAT_ASSOCIATION,
+            MtpConstants.FORMAT_TEXT,
+            MtpConstants.FORMAT_HTML,
+            MtpConstants.FORMAT_WAV,
+            MtpConstants.FORMAT_MP3,
+            MtpConstants.FORMAT_MPEG,
+            MtpConstants.FORMAT_EXIF_JPEG,
+            MtpConstants.FORMAT_TIFF_EP,
+            MtpConstants.FORMAT_GIF,
+            MtpConstants.FORMAT_JFIF,
+            MtpConstants.FORMAT_PNG,
+            MtpConstants.FORMAT_TIFF,
+            MtpConstants.FORMAT_WMA,
+            MtpConstants.FORMAT_OGG,
+            MtpConstants.FORMAT_AAC,
+            MtpConstants.FORMAT_MP4_CONTAINER,
+            MtpConstants.FORMAT_MP2,
+            MtpConstants.FORMAT_3GP_CONTAINER,
+            MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
+            MtpConstants.FORMAT_WPL_PLAYLIST,
+            MtpConstants.FORMAT_M3U_PLAYLIST,
+            MtpConstants.FORMAT_PLS_PLAYLIST,
+            MtpConstants.FORMAT_XML_DOCUMENT,
+        };
+    }
+
+    private int[] getSupportedCaptureFormats() {
+        // no capture formats yet
+        return null;
+    }
+
+    static final int[] FILE_PROPERTIES = {
+            // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
+            // and IMAGE_PROPERTIES below
+            MtpConstants.PROPERTY_STORAGE_ID,
+            MtpConstants.PROPERTY_OBJECT_FORMAT,
+            MtpConstants.PROPERTY_PROTECTION_STATUS,
+            MtpConstants.PROPERTY_OBJECT_SIZE,
+            MtpConstants.PROPERTY_OBJECT_FILE_NAME,
+            MtpConstants.PROPERTY_DATE_MODIFIED,
+            MtpConstants.PROPERTY_PARENT_OBJECT,
+            MtpConstants.PROPERTY_PERSISTENT_UID,
+            MtpConstants.PROPERTY_NAME,
+            MtpConstants.PROPERTY_DATE_ADDED,
+    };
+
+    static final int[] AUDIO_PROPERTIES = {
+            // NOTE must match FILE_PROPERTIES above
+            MtpConstants.PROPERTY_STORAGE_ID,
+            MtpConstants.PROPERTY_OBJECT_FORMAT,
+            MtpConstants.PROPERTY_PROTECTION_STATUS,
+            MtpConstants.PROPERTY_OBJECT_SIZE,
+            MtpConstants.PROPERTY_OBJECT_FILE_NAME,
+            MtpConstants.PROPERTY_DATE_MODIFIED,
+            MtpConstants.PROPERTY_PARENT_OBJECT,
+            MtpConstants.PROPERTY_PERSISTENT_UID,
+            MtpConstants.PROPERTY_NAME,
+            MtpConstants.PROPERTY_DISPLAY_NAME,
+            MtpConstants.PROPERTY_DATE_ADDED,
+
+            // audio specific properties
+            MtpConstants.PROPERTY_ARTIST,
+            MtpConstants.PROPERTY_ALBUM_NAME,
+            MtpConstants.PROPERTY_ALBUM_ARTIST,
+            MtpConstants.PROPERTY_TRACK,
+            MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
+            MtpConstants.PROPERTY_DURATION,
+            MtpConstants.PROPERTY_GENRE,
+            MtpConstants.PROPERTY_COMPOSER,
+    };
+
+    static final int[] VIDEO_PROPERTIES = {
+            // NOTE must match FILE_PROPERTIES above
+            MtpConstants.PROPERTY_STORAGE_ID,
+            MtpConstants.PROPERTY_OBJECT_FORMAT,
+            MtpConstants.PROPERTY_PROTECTION_STATUS,
+            MtpConstants.PROPERTY_OBJECT_SIZE,
+            MtpConstants.PROPERTY_OBJECT_FILE_NAME,
+            MtpConstants.PROPERTY_DATE_MODIFIED,
+            MtpConstants.PROPERTY_PARENT_OBJECT,
+            MtpConstants.PROPERTY_PERSISTENT_UID,
+            MtpConstants.PROPERTY_NAME,
+            MtpConstants.PROPERTY_DISPLAY_NAME,
+            MtpConstants.PROPERTY_DATE_ADDED,
+
+            // video specific properties
+            MtpConstants.PROPERTY_ARTIST,
+            MtpConstants.PROPERTY_ALBUM_NAME,
+            MtpConstants.PROPERTY_DURATION,
+            MtpConstants.PROPERTY_DESCRIPTION,
+    };
+
+    static final int[] IMAGE_PROPERTIES = {
+            // NOTE must match FILE_PROPERTIES above
+            MtpConstants.PROPERTY_STORAGE_ID,
+            MtpConstants.PROPERTY_OBJECT_FORMAT,
+            MtpConstants.PROPERTY_PROTECTION_STATUS,
+            MtpConstants.PROPERTY_OBJECT_SIZE,
+            MtpConstants.PROPERTY_OBJECT_FILE_NAME,
+            MtpConstants.PROPERTY_DATE_MODIFIED,
+            MtpConstants.PROPERTY_PARENT_OBJECT,
+            MtpConstants.PROPERTY_PERSISTENT_UID,
+            MtpConstants.PROPERTY_NAME,
+            MtpConstants.PROPERTY_DISPLAY_NAME,
+            MtpConstants.PROPERTY_DATE_ADDED,
+
+            // image specific properties
+            MtpConstants.PROPERTY_DESCRIPTION,
+    };
+
+    private int[] getSupportedObjectProperties(int format) {
+        switch (format) {
+            case MtpConstants.FORMAT_MP3:
+            case MtpConstants.FORMAT_WAV:
+            case MtpConstants.FORMAT_WMA:
+            case MtpConstants.FORMAT_OGG:
+            case MtpConstants.FORMAT_AAC:
+                return AUDIO_PROPERTIES;
+            case MtpConstants.FORMAT_MPEG:
+            case MtpConstants.FORMAT_3GP_CONTAINER:
+            case MtpConstants.FORMAT_WMV:
+                return VIDEO_PROPERTIES;
+            case MtpConstants.FORMAT_EXIF_JPEG:
+            case MtpConstants.FORMAT_GIF:
+            case MtpConstants.FORMAT_PNG:
+            case MtpConstants.FORMAT_BMP:
+                return IMAGE_PROPERTIES;
+            default:
+                return FILE_PROPERTIES;
+        }
+    }
+
+    private int[] getSupportedDeviceProperties() {
+        return new int[] {
+            MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
+            MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
+        };
+    }
+
+    private String queryString(int id, String column) {
+        Cursor c = null;
+        try {
+            // for now we are only reading properties from the "objects" table
+            c = mMediaProvider.query(mObjectsUri,
+                            new String [] { Files.FileColumns._ID, column },
+                            ID_WHERE, new String[] { Integer.toString(id) }, null);
+            if (c != null && c.moveToNext()) {
+                return c.getString(1);
+            } else {
+                return "";
+            }
+        } catch (Exception e) {
+            return null;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    private String queryGenre(int id) {
+        Cursor c = null;
+        try {
+            Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id);
+            c = mMediaProvider.query(uri,
+                            new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME },
+                            null, null, null);
+            if (c != null && c.moveToNext()) {
+                return c.getString(1);
+            } else {
+                return "";
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "queryGenre exception", e);
+            return null;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    private boolean queryInt(int id, String column, long[] outValue) {
+        Cursor c = null;
+        try {
+            // for now we are only reading properties from the "objects" table
+            c = mMediaProvider.query(mObjectsUri,
+                            new String [] { Files.FileColumns._ID, column },
+                            ID_WHERE, new String[] { Integer.toString(id) }, null);
+            if (c != null && c.moveToNext()) {
+                outValue[0] = c.getLong(1);
+                return true;
+            }
+            return false;
+        } catch (Exception e) {
+            return false;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    private String nameFromPath(String path) {
+        // extract name from full path
+        int start = 0;
+        int lastSlash = path.lastIndexOf('/');
+        if (lastSlash >= 0) {
+            start = lastSlash + 1;
+        }
+        int end = path.length();
+        if (end - start > 255) {
+            end = start + 255;
+        }
+        return path.substring(start, end);
+    }
+
+    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 MtpConstants.PROPERTY_STORAGE_ID:
+                outIntValue[0] = mStorageID;
+                return MtpConstants.RESPONSE_OK;
+            case MtpConstants.PROPERTY_OBJECT_FORMAT:
+                column = Files.FileColumns.FORMAT;
+                break;
+            case MtpConstants.PROPERTY_PROTECTION_STATUS:
+                // protection status is always 0
+                outIntValue[0] = 0;
+                return MtpConstants.RESPONSE_OK;
+            case MtpConstants.PROPERTY_OBJECT_SIZE:
+                column = Files.FileColumns.SIZE;
+                break;
+            case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
+                // special case - need to extract file name from full path
+                String value = queryString(handle, Files.FileColumns.DATA);
+                if (value != null) {
+                    value = nameFromPath(value);
+                    value.getChars(0, value.length(), outStringValue, 0);
+                    outStringValue[value.length()] = 0;
+                    return MtpConstants.RESPONSE_OK;
+                } else {
+                    return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+                }
+            case MtpConstants.PROPERTY_NAME:
+                // first try title
+                String name = queryString(handle, MediaColumns.TITLE);
+                // then try name
+                if (name == null) {
+                    name = queryString(handle, Audio.PlaylistsColumns.NAME);
+                }
+                // if title and name fail, extract name from full path
+                if (name == null) {
+                    name = queryString(handle, Files.FileColumns.DATA);
+                    if (name != null) {
+                        name = nameFromPath(name);
+                    }
+                }
+                if (name != null) {
+                    name.getChars(0, name.length(), outStringValue, 0);
+                    outStringValue[name.length()] = 0;
+                    return MtpConstants.RESPONSE_OK;
+                } else {
+                    return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+                }
+            case MtpConstants.PROPERTY_DATE_MODIFIED:
+                column = Files.FileColumns.DATE_MODIFIED;
+                break;
+            case MtpConstants.PROPERTY_DATE_ADDED:
+                column = Files.FileColumns.DATE_ADDED;
+                break;
+            case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
+                column = Audio.AudioColumns.YEAR;
+                break;
+            case MtpConstants.PROPERTY_PARENT_OBJECT:
+                column = Files.FileColumns.PARENT;
+                break;
+            case MtpConstants.PROPERTY_PERSISTENT_UID:
+                // PUID is concatenation of storageID and object handle
+                long puid = mStorageID;
+                puid <<= 32;
+                puid += handle;
+                outIntValue[0] = puid;
+                return MtpConstants.RESPONSE_OK;
+            case MtpConstants.PROPERTY_DURATION:
+                column = Audio.AudioColumns.DURATION;
+                break;
+            case MtpConstants.PROPERTY_TRACK:
+                if (queryInt(handle, Audio.AudioColumns.TRACK, outIntValue)) {
+                    // track is stored in lower 3 decimal digits
+                    outIntValue[0] %= 1000;
+                    return MtpConstants.RESPONSE_OK;
+                } else {
+                    return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+                }
+            case MtpConstants.PROPERTY_DISPLAY_NAME:
+                column = MediaColumns.DISPLAY_NAME;
+                isString = true;
+                break;
+            case MtpConstants.PROPERTY_ARTIST:
+                column = Audio.AudioColumns.ARTIST;
+                isString = true;
+                break;
+            case MtpConstants.PROPERTY_ALBUM_NAME:
+                column = Audio.AudioColumns.ALBUM;
+                isString = true;
+                break;
+            case MtpConstants.PROPERTY_ALBUM_ARTIST:
+                column = Audio.AudioColumns.ALBUM_ARTIST;
+                isString = true;
+                break;
+            case MtpConstants.PROPERTY_GENRE:
+                String genre = queryGenre(handle);
+                if (genre != null) {
+                    genre.getChars(0, genre.length(), outStringValue, 0);
+                    outStringValue[genre.length()] = 0;
+                    return MtpConstants.RESPONSE_OK;
+                } else {
+                    return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+                }
+            case MtpConstants.PROPERTY_COMPOSER:
+                column = Audio.AudioColumns.COMPOSER;
+                isString = true;
+                break;
+            case MtpConstants.PROPERTY_DESCRIPTION:
+                column = Images.ImageColumns.DESCRIPTION;
+                isString = true;
+                break;
+            default:
+                return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+        }
+
+        if (isString) {
+            String value = queryString(handle, column);
+            if (value != null) {
+                value.getChars(0, value.length(), outStringValue, 0);
+                outStringValue[value.length()] = 0;
+                return MtpConstants.RESPONSE_OK;
+            }
+        } else {
+            if (queryInt(handle, column, outIntValue)) {
+                return MtpConstants.RESPONSE_OK;
+            }
+        }
+        // query failed if we get here
+        return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+    }
+
+    private int setObjectProperty(int handle, int property,
+                            long intValue, String stringValue) {
+        Log.d(TAG, "setObjectProperty: " + property);
+        return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+    }
+
+    private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
+        Log.d(TAG, "getDeviceProperty: " + property);
+
+        switch (property) {
+            case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+            case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+                // writable string properties kept in our device property database
+                Cursor c = null;
+                try {
+                    c = mDevicePropDb.query("properties", DEVICE_PROPERTY_PROJECTION,
+                        DEVICE_PROPERTY_WHERE, new String[] {  Integer.toString(property) },
+                        null, null, null);
+
+                    if (c != null && c.moveToNext()) {
+                        String value = c.getString(1);
+                        int length = value.length();
+                        if (length > 255) {
+                            length = 255;
+                        }
+                        value.getChars(0, length, outStringValue, 0);
+                        outStringValue[length] = 0;
+                    } else {
+                        outStringValue[0] = 0;
+                    }
+                    return MtpConstants.RESPONSE_OK;
+                } finally {
+                    if (c != null) {
+                        c.close();
+                    }
+                }
+        }
+
+        return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+    }
+
+    private int setDeviceProperty(int property, long intValue, String stringValue) {
+        Log.d(TAG, "setDeviceProperty: " + property + " : " + stringValue);
+
+        switch (property) {
+            case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+            case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+                // writable string properties kept in our device property database
+                try {
+                    ContentValues values = new ContentValues();
+                    values.put("code", property);
+                    values.put("value", stringValue);
+                    mDevicePropDb.insert("properties", "code", values);
+                    return MtpConstants.RESPONSE_OK;
+                } catch (Exception e) {
+                    return MtpConstants.RESPONSE_GENERAL_ERROR;
+                }
+        }
+
+        return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+    }
+
+    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 MtpConstants.RESPONSE_OK;
+            } else {
+                return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getObjectFilePath", e);
+            return MtpConstants.RESPONSE_GENERAL_ERROR;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    private int deleteRecursive(int handle) throws RemoteException {
+        int[] children = getObjectList(0 /* storageID */, 0 /* format */, handle);
+        Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
+        // delete parent first, to avoid potential infinite recursion
+        int count = mMediaProvider.delete(uri, null, null);
+        if (count == 1) {
+            if (children != null) {
+                for (int i = 0; i < children.length; i++) {
+                    count += deleteRecursive(children[i]);
+                }
+            }
+        }
+        return count;
+    }
+
+    private int deleteFile(int handle) {
+        Log.d(TAG, "deleteFile: " + handle);
+        mDatabaseModified = true;
+        try {
+            if (deleteRecursive(handle) > 0) {
+                return MtpConstants.RESPONSE_OK;
+            } else {
+                return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in deleteFile", e);
+            return MtpConstants.RESPONSE_GENERAL_ERROR;
+        }
+    }
+
+    private int[] getObjectReferences(int handle) {
+        Log.d(TAG, "getObjectReferences for: " + handle);
+        Uri uri = Files.getMtpReferencesUri(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) {
+        mDatabaseModified = true;
+        Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
+        int count = references.length;
+        ContentValues[] valuesList = new ContentValues[count];
+        for (int i = 0; i < count; i++) {
+            ContentValues values = new ContentValues();
+            values.put(Files.FileColumns._ID, references[i]);
+            valuesList[i] = values;
+        }
+        try {
+            if (count == mMediaProvider.bulkInsert(uri, valuesList)) {
+                return MtpConstants.RESPONSE_OK;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in setObjectReferences", e);
+        }
+        return MtpConstants.RESPONSE_GENERAL_ERROR;
+    }
+
+    private void sessionStarted() {
+        Log.d(TAG, "sessionStarted");
+        mDatabaseModified = false;
+    }
+
+    private void sessionEnded() {
+        Log.d(TAG, "sessionEnded");
+        if (mDatabaseModified) {
+            Log.d(TAG, "sending ACTION_MTP_SESSION_END");
+            mContext.sendBroadcast(new Intent(Mtp.ACTION_MTP_SESSION_END));
+            mDatabaseModified = false;
+        }
+    }
+
+    // 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..7f15276
--- /dev/null
+++ b/media/java/android/media/MtpServer.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 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() throws Throwable {
+        try {
+            native_finalize();
+        } finally {
+            super.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);
+    }
+
+    public void setPtpMode(boolean usePtp) {
+        native_set_ptp_mode(usePtp);
+    }
+
+    // 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);
+    private native final void native_set_ptp_mode(boolean usePtp);
+}
diff --git a/media/java/android/media/videoeditor/AudioTrack.java b/media/java/android/media/videoeditor/AudioTrack.java
new file mode 100755
index 0000000..3ebad00
--- /dev/null
+++ b/media/java/android/media/videoeditor/AudioTrack.java
@@ -0,0 +1,607 @@
+/*

+ * Copyright (C) 2010 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES 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.videoeditor;

+

+import java.io.IOException;

+

+import android.util.Log;

+

+/**

+ * This class allows to handle an audio track. This audio file is mixed with the

+ * audio samples of the MediaItems.

+ * {@hide}

+ */

+public class AudioTrack {

+    // Logging

+    private static final String TAG = "AudioTrack";

+

+    // Instance variables

+    private final String mUniqueId;

+    private final String mFilename;

+    private long mStartTimeMs;

+    private long mTimelineDurationMs;

+    private int mVolumePercent;

+    private long mBeginBoundaryTimeMs;

+    private long mEndBoundaryTimeMs;

+    private boolean mLoop;

+    private boolean mMuted;

+

+    private final long mDurationMs;

+    private final int mAudioChannels;

+    private final int mAudioType;

+    private final int mAudioBitrate;

+    private final int mAudioSamplingFrequency;

+

+    // Ducking variables

+    private int mDuckingThreshold;

+    private int mDuckingLowVolume;

+    private boolean mIsDuckingEnabled;

+

+    // The audio waveform filename

+    private String mAudioWaveformFilename;

+    private PlaybackThread mPlaybackThread;

+

+    /**

+     * This listener interface is used by the AudioTrack to emit playback

+     * progress notifications.

+     */

+    public interface PlaybackProgressListener {

+        /**

+         * This method notifies the listener of the current time position while

+         * playing an audio track

+         *

+         * @param audioTrack The audio track

+         * @param timeMs The current playback position (expressed in milliseconds

+         *            since the beginning of the audio track).

+         * @param end true if the end of the audio track was reached

+         */

+        public void onProgress(AudioTrack audioTrack, long timeMs, boolean end);

+    }

+

+    /**

+     * The playback thread

+     */

+    private class PlaybackThread extends Thread {

+        // Instance variables

+        private final PlaybackProgressListener mListener;

+        private final long mFromMs, mToMs;

+        private boolean mRun;

+        private final boolean mLoop;

+        private long mPositionMs;

+

+        /**

+         * Constructor

+         *

+         * @param fromMs The time (relative to the beginning of the audio track)

+         *            at which the playback will start

+         * @param toMs The time (relative to the beginning of the audio track) at

+         *            which the playback will stop. Use -1 to play to the end of

+         *            the audio track

+         * @param loop true if the playback should be looped once it reaches the

+         *            end

+         * @param listener The listener which will be notified of the playback

+         *            progress

+         */

+        public PlaybackThread(long fromMs, long toMs, boolean loop,

+                PlaybackProgressListener listener) {

+            mPositionMs = mFromMs = fromMs;

+            if (toMs < 0) {

+                mToMs = mDurationMs;

+            } else {

+                mToMs = toMs;

+            }

+            mLoop = loop;

+            mListener = listener;

+            mRun = true;

+        }

+

+        /*

+         * {@inheritDoc}

+         */

+        @Override

+        public void run() {

+            if (Log.isLoggable(TAG, Log.DEBUG)) {

+                Log.d(TAG, "===> PlaybackThread.run enter");

+            }

+

+            while (mRun) {

+                try {

+                    sleep(100);

+                } catch (InterruptedException ex) {

+                    break;

+                }

+

+                mPositionMs += 100;

+

+                if (mPositionMs >= mToMs) {

+                    if (!mLoop) {

+                        if (mListener != null) {

+                            mListener.onProgress(AudioTrack.this, mPositionMs, true);

+                        }

+                        if (Log.isLoggable(TAG, Log.DEBUG)) {

+                            Log.d(TAG, "PlaybackThread.run playback complete");

+                        }

+                        break;

+                    } else {

+                        // Fire a notification for the end of the clip

+                        if (mListener != null) {

+                            mListener.onProgress(AudioTrack.this, mToMs, false);

+                        }

+

+                        // Rewind

+                        mPositionMs = mFromMs;

+                        if (mListener != null) {

+                            mListener.onProgress(AudioTrack.this, mPositionMs, false);

+                        }

+                        if (Log.isLoggable(TAG, Log.DEBUG)) {

+                            Log.d(TAG, "PlaybackThread.run playback complete");

+                        }

+                    }

+                } else {

+                    if (mListener != null) {

+                        mListener.onProgress(AudioTrack.this, mPositionMs, false);

+                    }

+                }

+            }

+            if (Log.isLoggable(TAG, Log.DEBUG)) {

+                Log.d(TAG, "===> PlaybackThread.run exit");

+            }

+        }

+

+        /**

+         * Stop the playback

+         *

+         * @return The stop position

+         */

+        public long stopPlayback() {

+            mRun = false;

+            try {

+                join();

+            } catch (InterruptedException ex) {

+            }

+            return mPositionMs;

+        }

+    };

+

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private AudioTrack() throws IOException {

+        this(null, null, null);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param editor The video editor reference

+     * @param audioTrackId The audio track id

+     * @param filename The absolute file name

+     *

+     * @throws IOException if file is not found

+     * @throws IllegalArgumentException if file format is not supported or if

+     *             the codec is not supported

+     */

+    public AudioTrack(VideoEditor editor, String audioTrackId, String filename)

+            throws IOException {

+        mUniqueId = audioTrackId;

+        mFilename = filename;

+        mStartTimeMs = 0;

+        // TODO: This value represents to the duration of the audio file

+        mDurationMs = 300000;

+        // TODO: This value needs to be read from the audio track of the source

+        // file

+        mAudioChannels = 2;

+        mAudioType = MediaProperties.ACODEC_AAC_LC;

+        mAudioBitrate = 128000;

+        mAudioSamplingFrequency = 44100;

+

+        mTimelineDurationMs = mDurationMs;

+        mVolumePercent = 100;

+

+        // Play the entire audio track

+        mBeginBoundaryTimeMs = 0;

+        mEndBoundaryTimeMs = mDurationMs;

+

+        // By default loop is disabled

+        mLoop = false;

+

+        // By default the audio track is not muted

+        mMuted = false;

+

+        // Ducking is enabled by default

+        mDuckingThreshold = 0;

+        mDuckingLowVolume = 0;

+        mIsDuckingEnabled = true;

+

+        // The audio waveform file is generated later

+        mAudioWaveformFilename = null;

+    }

+

+    /**

+     * Constructor

+     *

+     * @param editor The video editor reference

+     * @param audioTrackId The audio track id

+     * @param filename The audio filename

+     * @param startTimeMs the start time in milliseconds (relative to the

+     *              timeline)

+     * @param beginMs start time in the audio track in milliseconds (relative to

+     *            the beginning of the audio track)

+     * @param endMs end time in the audio track in milliseconds (relative to the

+     *            beginning of the audio track)

+     * @param loop true to loop the audio track

+     * @param volume The volume in percentage

+     * @param muted true if the audio track is muted

+     * @param audioWaveformFilename The name of the waveform file

+     *

+     * @throws IOException if file is not found

+     */

+    AudioTrack(VideoEditor editor, String audioTrackId, String filename, long startTimeMs,

+            long beginMs, long endMs, boolean loop, int volume, boolean muted,

+            String audioWaveformFilename) throws IOException {

+        mUniqueId = audioTrackId;

+        mFilename = filename;

+        mStartTimeMs = startTimeMs;

+

+        // TODO: This value represents to the duration of the audio file

+        mDurationMs = 300000;

+

+        // TODO: This value needs to be read from the audio track of the source

+        // file

+        mAudioChannels = 2;

+        mAudioType = MediaProperties.ACODEC_AAC_LC;

+        mAudioBitrate = 128000;

+        mAudioSamplingFrequency = 44100;

+

+        mTimelineDurationMs = endMs - beginMs;

+        mVolumePercent = volume;

+

+        mBeginBoundaryTimeMs = beginMs;

+        mEndBoundaryTimeMs = endMs;

+

+        mLoop = loop;

+        mMuted = muted;

+

+        mAudioWaveformFilename = audioWaveformFilename;

+    }

+

+    /**

+     * @return The id of the audio track

+     */

+    public String getId() {

+        return mUniqueId;

+    }

+

+    /**

+     * Get the filename source for this audio track.

+     *

+     * @return The filename as an absolute file name

+     */

+    public String getFilename() {

+        return mFilename;

+    }

+

+    /**

+     * @return The number of audio channels in the source of this audio track

+     */

+    public int getAudioChannels() {

+        return mAudioChannels;

+    }

+

+    /**

+     * @return The audio codec of the source of this audio track

+     */

+    public int getAudioType() {

+        return mAudioType;

+    }

+

+    /**

+     * @return The audio sample frequency of the audio track

+     */

+    public int getAudioSamplingFrequency() {

+        return mAudioSamplingFrequency;

+    }

+

+    /**

+     * @return The audio bitrate of the audio track

+     */

+    public int getAudioBitrate() {

+        return mAudioBitrate;

+    }

+

+    /**

+     * Set the volume of this audio track as percentage of the volume in the

+     * original audio source file.

+     *

+     * @param volumePercent Percentage of the volume to apply. If it is set to

+     *            0, then volume becomes mute. It it is set to 100, then volume

+     *            is same as original volume. It it is set to 200, then volume

+     *            is doubled (provided that volume amplification is supported)

+     *

+     * @throws UnsupportedOperationException if volume amplification is

+     *             requested and is not supported.

+     */

+    public void setVolume(int volumePercent) {

+        mVolumePercent = volumePercent;

+    }

+

+    /**

+     * Get the volume of the audio track as percentage of the volume in the

+     * original audio source file.

+     *

+     * @return The volume in percentage

+     */

+    public int getVolume() {

+        return mVolumePercent;

+    }

+

+    /**

+     * @param muted true to mute the audio track

+     */

+    public void setMute(boolean muted) {

+        mMuted = muted;

+    }

+

+    /**

+     * @return true if the audio track is muted

+     */

+    public boolean isMuted() {

+        return mMuted;

+    }

+

+    /**

+     * Set the start time of this audio track relative to the storyboard

+     * timeline. Default value is 0.

+     *

+     * @param startTimeMs the start time in milliseconds

+     */

+    public void setStartTime(long startTimeMs) {

+        mStartTimeMs = startTimeMs;

+    }

+

+    /**

+     * Get the start time of this audio track relative to the storyboard

+     * timeline.

+     *

+     * @return The start time in milliseconds

+     */

+    public long getStartTime() {

+        return mStartTimeMs;

+    }

+

+    /**

+     * @return The duration in milliseconds. This value represents the audio

+     *         track duration (not looped)

+     */

+    public long getDuration() {

+        return mDurationMs;

+    }

+

+    /**

+     * @return The timeline duration. If looping is enabled this value

+     *         represents the duration of the looped audio track, otherwise it

+     *         is the duration of the audio track (mDurationMs).

+     */

+    public long getTimelineDuration() {

+        return mTimelineDurationMs;

+    }

+

+    /**

+     * Sets the start and end marks for trimming an audio track

+     *

+     * @param beginMs start time in the audio track in milliseconds (relative to

+     *            the beginning of the audio track)

+     * @param endMs end time in the audio track in milliseconds (relative to the

+     *            beginning of the audio track)

+     */

+    public void setExtractBoundaries(long beginMs, long endMs) {

+        if (beginMs > mDurationMs) {

+            throw new IllegalArgumentException("Invalid start time");

+        }

+        if (endMs > mDurationMs) {

+            throw new IllegalArgumentException("Invalid end time");

+        }

+

+        mBeginBoundaryTimeMs = beginMs;

+        mEndBoundaryTimeMs = endMs;

+        if (mLoop) {

+            // TODO: Compute mDurationMs (from the beginning of the loop until

+            // the end of all the loops.

+            mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;

+        } else {

+            mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;

+        }

+    }

+

+    /**

+     * @return The boundary begin time

+     */

+    public long getBoundaryBeginTime() {

+        return mBeginBoundaryTimeMs;

+    }

+

+    /**

+     * @return The boundary end time

+     */

+    public long getBoundaryEndTime() {

+        return mEndBoundaryTimeMs;

+    }

+

+    /**

+     * Enable the loop mode for this audio track. Note that only one of the

+     * audio tracks in the timeline can have the loop mode enabled. When looping

+     * is enabled the samples between mBeginBoundaryTimeMs and

+     * mEndBoundaryTimeMs are looped.

+     */

+    public void enableLoop() {

+        mLoop = true;

+    }

+

+    /**

+     * Disable the loop mode

+     */

+    public void disableLoop() {

+        mLoop = false;

+    }

+

+    /**

+     * @return true if looping is enabled

+     */

+    public boolean isLooping() {

+        return mLoop;

+    }

+

+    /**

+     * Disable the audio duck effect

+     */

+    public void disableDucking() {

+        mIsDuckingEnabled = false;

+    }

+

+    /**

+     * TODO DEFINE

+     *

+     * @param threshold

+     * @param lowVolume

+     * @param volume

+     */

+    public void enableDucking(int threshold, int lowVolume, int volume) {

+        mDuckingThreshold = threshold;

+        mDuckingLowVolume = lowVolume;

+        mIsDuckingEnabled = true;

+    }

+

+    /**

+     * @return true if ducking is enabled

+     */

+    public boolean isDuckingEnabled() {

+        return mIsDuckingEnabled;

+    }

+

+    /**

+     * @return The ducking threshold

+     */

+    public int getDuckingThreshhold() {

+        return mDuckingThreshold;

+    }

+

+    /**

+     * @return The ducking low level

+     */

+    public int getDuckingLowVolume() {

+        return mDuckingLowVolume;

+    }

+

+    /**

+     * Start the playback of this audio track. This method does not block (does

+     * not wait for the playback to complete).

+     *

+     * @param fromMs The time (relative to the beginning of the audio track) at

+     *            which the playback will start

+     * @param toMs The time (relative to the beginning of the audio track) at

+     *            which the playback will stop. Use -1 to play to the end of the

+     *            audio track

+     * @param loop true if the playback should be looped once it reaches the end

+     * @param listener The listener which will be notified of the playback

+     *            progress

+     * @throws IllegalArgumentException if fromMs or toMs is beyond the playback

+     *             duration

+     * @throws IllegalStateException if a playback, preview or an export is

+     *             already in progress

+     */

+    public void startPlayback(long fromMs, long toMs, boolean loop,

+            PlaybackProgressListener listener) {

+        if (fromMs >= mDurationMs) {

+            return;

+        }

+        mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, listener);

+        mPlaybackThread.start();

+    }

+

+    /**

+     * Stop the audio track playback. This method blocks until the ongoing

+     * playback is stopped.

+     *

+     * @return The accurate current time when stop is effective expressed in

+     *         milliseconds

+     */

+    public long stopPlayback() {

+        final long stopTimeMs;

+        if (mPlaybackThread != null) {

+            stopTimeMs = mPlaybackThread.stopPlayback();

+            mPlaybackThread = null;

+        } else {

+            stopTimeMs = 0;

+        }

+        return stopTimeMs;

+    }

+

+    /**

+     * This API allows to generate a file containing the sample volume levels of

+     * this audio track object. This function may take significant time and is

+     * blocking. The filename can be retrieved using getAudioWaveformFilename().

+     *

+     * @param listener The progress listener

+     *

+     * @throws IOException if the output file cannot be created

+     * @throws IllegalArgumentException if the audio file does not have a valid

+     *             audio track

+     */

+    public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)

+            throws IOException {

+        // TODO: Set mAudioWaveformFilename at the end once the extract is

+        // complete

+    }

+

+    /**

+     * Get the audio waveform file name if extractAudioWaveform was successful.

+     * The file format is as following:

+     * <ul>

+     * <li>first 4 bytes provide the number of samples for each value, as

+     * big-endian signed</li>

+     * <li>4 following bytes is the total number of values in the file, as

+     * big-endian signed</li>

+     * <li>then, all values follow as bytes</li>

+     * </ul>

+     *

+     * @return the name of the file, null if the file does not exist

+     */

+    public String getAudioWaveformFilename() {

+        return mAudioWaveformFilename;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public boolean equals(Object object) {

+        if (!(object instanceof AudioTrack)) {

+            return false;

+        }

+        return mUniqueId.equals(((AudioTrack)object).mUniqueId);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int hashCode() {

+        return mUniqueId.hashCode();

+    }

+}

diff --git a/media/java/android/media/videoeditor/Effect.java b/media/java/android/media/videoeditor/Effect.java
new file mode 100755
index 0000000..f5e6a67
--- /dev/null
+++ b/media/java/android/media/videoeditor/Effect.java
@@ -0,0 +1,168 @@
+/*

+ * Copyright (C) 2010 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES 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.videoeditor;

+

+/**

+ * This is the super class for all effects. An effect can only be applied to a

+ * single media item. If one wants to apply the same effect to multiple media

+ * items, multiple @{MediaItem.addEffect(Effect)} call must be invoked on each

+ * of the MediaItem objects.

+ * {@hide}

+ */

+public abstract class Effect {

+    // Instance variables

+    private final String mUniqueId;

+    // The effect owner

+    private final MediaItem mMediaItem;

+    protected long mDurationMs;

+    // The start time of the effect relative to the media item timeline

+    protected long mStartTimeMs;

+

+    /**

+     * Default constructor

+     */

+    @SuppressWarnings("unused")

+    private Effect() {

+        mMediaItem = null;

+        mUniqueId = null;

+        mStartTimeMs = 0;

+        mDurationMs = 0;

+    }

+

+    /**

+     * Constructor

+     *

+     * @param mediaItem The media item owner

+     * @param effectId The effect id

+     * @param startTimeMs The start time relative to the media item to which it

+     *            is applied

+     * @param durationMs The effect duration in milliseconds

+     */

+    public Effect(MediaItem mediaItem, String effectId, long startTimeMs, long durationMs) {

+        if (mediaItem == null) {

+            throw new IllegalArgumentException("Media item cannot be null");

+        }

+

+        if (startTimeMs + durationMs > mediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Invalid start time and duration");

+        }

+

+        mMediaItem = mediaItem;

+        mUniqueId = effectId;

+        mStartTimeMs = startTimeMs;

+        mDurationMs = durationMs;

+    }

+

+    /**

+     * @return The id of the effect

+     */

+    public String getId() {

+        return mUniqueId;

+    }

+

+    /**

+     * Set the duration of the effect. If a preview or export is in progress,

+     * then this change is effective for next preview or export session. s

+     *

+     * @param durationMs of the effect in milliseconds

+     */

+    public void setDuration(long durationMs) {

+        if (mStartTimeMs + durationMs > mMediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Duration is too large");

+        }

+

+        mDurationMs = durationMs;

+

+        mMediaItem.invalidateTransitions(this);

+    }

+

+    /**

+     * Get the duration of the effect

+     *

+     * @return The duration of the effect in milliseconds

+     */

+    public long getDuration() {

+        return mDurationMs;

+    }

+

+    /**

+     * Set start time of the effect. If a preview or export is in progress, then

+     * this change is effective for next preview or export session.

+     *

+     * @param startTimeMs The start time of the effect relative to the beginning

+     *            of the media item in milliseconds

+     */

+    public void setStartTime(long startTimeMs) {

+        if (startTimeMs + mDurationMs > mMediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Start time is too large");

+        }

+

+        mStartTimeMs = startTimeMs;

+

+        mMediaItem.invalidateTransitions(this);

+    }

+

+    /**

+     * @return The start time in milliseconds

+     */

+    public long getStartTime() {

+        return mStartTimeMs;

+    }

+

+    /**

+     * Set the start time and duration

+     *

+     * @param startTimeMs start time in milliseconds

+     * @param durationMs The duration in milliseconds

+     */

+    public void setStartTimeAndDuration(long startTimeMs, long durationMs) {

+        if (startTimeMs + durationMs > mMediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Invalid start time or duration");

+        }

+

+        mStartTimeMs = startTimeMs;

+        mDurationMs = durationMs;

+

+        mMediaItem.invalidateTransitions(this);

+    }

+

+    /**

+     * @return The media item owner

+     */

+    public MediaItem getMediaItem() {

+        return mMediaItem;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public boolean equals(Object object) {

+        if (!(object instanceof Effect)) {

+            return false;

+        }

+        return mUniqueId.equals(((Effect)object).mUniqueId);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int hashCode() {

+        return mUniqueId.hashCode();

+    }

+}

diff --git a/media/java/android/media/videoeditor/EffectColor.java b/media/java/android/media/videoeditor/EffectColor.java
new file mode 100755
index 0000000..f38cf75
--- /dev/null
+++ b/media/java/android/media/videoeditor/EffectColor.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.media.videoeditor;

+

+/**

+ * This class allows to apply color on a media item.

+ * {@hide}

+ */

+public class EffectColor extends Effect {

+

+    /**

+     * Change the video frame color to the RGB color value provided

+     */

+    public static final int TYPE_COLOR = 1; // color as 888 RGB

+    /**

+     * Change the video frame color to a gradation from RGB color (at the top of

+     * the frame) to black (at the bottom of the frame).

+     */

+    public static final int TYPE_GRADIENT = 2;

+    /**

+     * Change the video frame color to sepia

+     */

+    public static final int TYPE_SEPIA = 3;

+    /**

+     * Invert the video frame color

+     */

+    public static final int TYPE_NEGATIVE = 4;

+    /**

+     * Make the video look like as if it was recorded in 50's

+     */

+    public static final int TYPE_FIFTIES = 5;

+

+    // Colors for the color effect

+    public static final int GREEN = 0x0000ff00;

+    public static final int PINK = 0x00ff66cc;

+    public static final int GRAY = 0x007f7f7f;

+

+    // The effect type

+    private final int mType;

+

+    // The effect parameter

+    private final int mParam;

+

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private EffectColor() {

+        this(null, null, 0, 0, 0, 0);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param mediaItem The media item owner

+     * @param effectId The effect id

+     * @param startTimeMs The start time relative to the media item to which it

+     *            is applied

+     * @param durationMs The duration of this effect in milliseconds

+     * @param type type of the effect. type is one of: TYPE_COLOR,

+     *            TYPE_GRADIENT, TYPE_SEPIA, TYPE_NEGATIVE, TYPE_FIFTIES. If

+     *            type is not supported, the argument is ignored

+     * @param param if type is TYPE_COLOR, param is the RGB color as 888.

+     *            Otherwise, param is ignored

+     */

+    public EffectColor(MediaItem mediaItem, String effectId, long startTimeMs, long durationMs,

+            int type, int param) {

+        super(mediaItem, effectId, startTimeMs, durationMs);

+        mType = type;

+        mParam = param;

+    }

+

+    /**

+     * @return The type of this effect

+     */

+    public int getType() {

+        return mType;

+    }

+

+    /**

+     * @return the color as RGB 888 if type is TYPE_COLOR. Otherwise, ignore.

+     */

+    public int getParam() {

+        return mParam;

+    }

+}

diff --git a/media/java/android/media/videoeditor/EffectKenBurns.java b/media/java/android/media/videoeditor/EffectKenBurns.java
new file mode 100755
index 0000000..ae2e70d
--- /dev/null
+++ b/media/java/android/media/videoeditor/EffectKenBurns.java
@@ -0,0 +1,88 @@
+/*

+ * Copyright (C) 2010 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES 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.videoeditor;

+

+import android.graphics.Rect;

+

+/**

+ * This class represents a Ken Burns effect.

+ * {@hide}

+ */

+public class EffectKenBurns extends Effect {

+    // Instance variables

+    private Rect mStartRect;

+    private Rect mEndRect;

+

+    /**

+     * Objects of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private EffectKenBurns() {

+        this(null, null, null, null, 0, 0);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param mediaItem The media item owner

+     * @param effectId The effect id

+     * @param startRect The start rectangle

+     * @param endRect The end rectangle

+     * @param startTimeMs The start time

+     * @param durationMs The duration of the Ken Burns effect in milliseconds

+     */

+    public EffectKenBurns(MediaItem mediaItem, String effectId, Rect startRect, Rect endRect,

+            long startTimeMs, long durationMs) {

+        super(mediaItem, effectId, startTimeMs, durationMs);

+

+        mStartRect = startRect;

+        mEndRect = endRect;

+    }

+

+    /**

+     * @param startRect The start rectangle

+     *

+     * @throws IllegalArgumentException if start rectangle is incorrectly set.

+     */

+    public void setStartRect(Rect startRect) {

+        mStartRect = startRect;

+    }

+

+    /**

+     * @return The start rectangle

+     */

+    public Rect getStartRect() {

+        return mStartRect;

+    }

+

+    /**

+     * @param endRect The end rectangle

+     *

+     * @throws IllegalArgumentException if end rectangle is incorrectly set.

+     */

+    public void setEndRect(Rect endRect) {

+        mEndRect = endRect;

+    }

+

+    /**

+     * @return The end rectangle

+     */

+    public Rect getEndRect() {

+        return mEndRect;

+    }

+}

diff --git a/media/java/android/media/videoeditor/ExtractAudioWaveformProgressListener.java b/media/java/android/media/videoeditor/ExtractAudioWaveformProgressListener.java
new file mode 100644
index 0000000..1cce148
--- /dev/null
+++ b/media/java/android/media/videoeditor/ExtractAudioWaveformProgressListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.videoeditor;
+
+/**
+ * This listener interface is used by
+ * {@link MediaVideoItem#extractAudioWaveform(ExtractAudioWaveformProgressListener listener)}
+ * or
+ * {@link AudioTrack#extractAudioWaveform(ExtractAudioWaveformProgressListener listener)}
+ * {@hide}
+ */
+public interface ExtractAudioWaveformProgressListener {
+    /**
+     * This method notifies the listener of the progress status of
+     * an extractAudioWaveform operation.
+     * This method may be called maximum 100 times for one operation.
+     *
+     * @param progress The progress in %. At the beginning of the operation,
+     *          this value is set to 0; at the end, the value is set to 100.
+     */
+    public void onProgress(int progress);
+}
+
diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java
new file mode 100755
index 0000000..df3c5fb
--- /dev/null
+++ b/media/java/android/media/videoeditor/MediaImageItem.java
@@ -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.

+ */

+

+package android.media.videoeditor;

+

+import java.io.IOException;

+

+import android.graphics.Bitmap;

+import android.graphics.BitmapFactory;

+import android.graphics.Canvas;

+import android.graphics.Paint;

+import android.graphics.Rect;

+import android.util.Log;

+import android.util.Pair;

+

+/**

+ * This class represents an image item on the storyboard. Note that images are

+ * scaled down to the maximum supported resolution by preserving the native

+ * aspect ratio. To learn the scaled image dimensions use

+ * {@link #getScaledWidth()} and {@link #getScaledHeight()} respectively.

+ *

+ * {@hide}

+ */

+public class MediaImageItem extends MediaItem {

+    // Logging

+    private static final String TAG = "MediaImageItem";

+

+    // The resize paint

+    private static final Paint sResizePaint = new Paint(Paint.FILTER_BITMAP_FLAG);

+

+    // Instance variables

+    private final int mWidth;

+    private final int mHeight;

+    private final int mAspectRatio;

+    private long mDurationMs;

+    private int mScaledWidth, mScaledHeight;

+

+    /**

+     * This class cannot be instantiated by using the default constructor

+     */

+    @SuppressWarnings("unused")

+    private MediaImageItem() throws IOException {

+        this(null, null, null, 0, RENDERING_MODE_BLACK_BORDER);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param editor The video editor reference

+     * @param mediaItemId The media item id

+     * @param filename The image file name

+     * @param durationMs The duration of the image on the storyboard

+     * @param renderingMode The rendering mode

+     *

+     * @throws IOException

+     */

+    public MediaImageItem(VideoEditor editor, String mediaItemId, String filename, long durationMs,

+            int renderingMode)

+            throws IOException {

+        super(editor, mediaItemId, filename, renderingMode);

+

+        // Determine the dimensions of the image

+        final BitmapFactory.Options dbo = new BitmapFactory.Options();

+        dbo.inJustDecodeBounds = true;

+        BitmapFactory.decodeFile(filename, dbo);

+

+        mWidth = dbo.outWidth;

+        mHeight = dbo.outHeight;

+        mDurationMs = durationMs;

+

+        // TODO: Determine the aspect ratio from the width and height

+        mAspectRatio = MediaProperties.ASPECT_RATIO_4_3;

+

+        // Images are stored in memory scaled to the maximum resolution to

+        // save memory.

+        final Pair<Integer, Integer>[] resolutions =

+            MediaProperties.getSupportedResolutions(mAspectRatio);

+        // Get the highest resolution

+        final Pair<Integer, Integer> maxResolution = resolutions[resolutions.length - 1];

+        if (mHeight > maxResolution.second) {

+            // We need to scale the image

+            scaleImage(filename, maxResolution.first, maxResolution.second);

+            mScaledWidth = maxResolution.first;

+            mScaledHeight = maxResolution.second;

+        } else {

+            mScaledWidth = mWidth;

+            mScaledHeight = mHeight;

+        }

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int getFileType() {

+        if (mFilename.endsWith(".jpg") || mFilename.endsWith(".jpeg")) {

+            return MediaProperties.FILE_JPEG;

+        } else if (mFilename.endsWith(".png")) {

+            return MediaProperties.FILE_PNG;

+        } else {

+            return MediaProperties.FILE_UNSUPPORTED;

+        }

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int getWidth() {

+        return mWidth;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int getHeight() {

+        return mHeight;

+    }

+

+    /**

+     * @return The scaled width of the image.

+     */

+    public int getScaledWidth() {

+        return mScaledWidth;

+    }

+

+    /**

+     * @return The scaled height of the image.

+     */

+    public int getScaledHeight() {

+        return mScaledHeight;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int getAspectRatio() {

+        return mAspectRatio;

+    }

+

+    /**

+     * This method will adjust the duration of bounding transitions, effects

+     * and overlays if the current duration of the transactions become greater

+     * than the maximum allowable duration.

+     *

+     * @param durationMs The duration of the image in the storyboard timeline

+     */

+    public void setDuration(long durationMs) {

+        mDurationMs = durationMs;

+

+        adjustElementsDuration();

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public long getTimelineDuration() {

+        return mDurationMs;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public Bitmap getThumbnail(int width, int height, long timeMs) throws IOException {

+        return scaleImage(mFilename, width, height);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,

+            int thumbnailCount) throws IOException {

+        final Bitmap thumbnail = scaleImage(mFilename, width, height);

+        final Bitmap[] thumbnailArray = new Bitmap[thumbnailCount];

+        for (int i = 0; i < thumbnailCount; i++) {

+            thumbnailArray[i] = thumbnail;

+        }

+        return thumbnailArray;

+    }

+

+    /**

+     * Resize a bitmap to the specified width and height

+     *

+     * @param filename The filename

+     * @param width The thumbnail width

+     * @param height The thumbnail height

+     *

+     * @return The resized bitmap

+     */

+    private Bitmap scaleImage(String filename, int width, int height)

+            throws IOException {

+        final BitmapFactory.Options dbo = new BitmapFactory.Options();

+        dbo.inJustDecodeBounds = true;

+        BitmapFactory.decodeFile(filename, dbo);

+

+        final int nativeWidth = dbo.outWidth;

+        final int nativeHeight = dbo.outHeight;

+        if (Log.isLoggable(TAG, Log.DEBUG)) {

+            Log.d(TAG, "generateThumbnail: Input: " + nativeWidth + "x" + nativeHeight

+                    + ", resize to: " + width + "x" + height);

+        }

+

+        final Bitmap srcBitmap;

+        float bitmapWidth, bitmapHeight;

+        if (nativeWidth > width || nativeHeight > height) {

+            float dx = ((float)nativeWidth) / ((float)width);

+            float dy = ((float)nativeHeight) / ((float)height);

+            if (dx > dy) {

+                bitmapWidth = width;

+                bitmapHeight = nativeHeight / dx;

+            } else {

+                bitmapWidth = nativeWidth / dy;

+                bitmapHeight = height;

+            }

+            // Create the bitmap from file

+            if (nativeWidth / bitmapWidth > 1) {

+                final BitmapFactory.Options options = new BitmapFactory.Options();

+                options.inSampleSize = nativeWidth / (int)bitmapWidth;

+                srcBitmap = BitmapFactory.decodeFile(filename, options);

+            } else {

+                srcBitmap = BitmapFactory.decodeFile(filename);

+            }

+        } else {

+            bitmapWidth = width;

+            bitmapHeight = height;

+            srcBitmap = BitmapFactory.decodeFile(filename);

+        }

+

+        if (srcBitmap == null) {

+            Log.e(TAG, "generateThumbnail: Cannot decode image bytes");

+            throw new IOException("Cannot decode file: " + mFilename);

+        }

+

+        // Create the canvas bitmap

+        final Bitmap bitmap = Bitmap.createBitmap((int)bitmapWidth, (int)bitmapHeight,

+                Bitmap.Config.ARGB_8888);

+        final Canvas canvas = new Canvas(bitmap);

+        canvas.drawBitmap(srcBitmap, new Rect(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight()),

+                new Rect(0, 0, (int)bitmapWidth, (int)bitmapHeight), sResizePaint);

+        // Release the source bitmap

+        srcBitmap.recycle();

+        return bitmap;

+    }

+}

diff --git a/media/java/android/media/videoeditor/MediaItem.java b/media/java/android/media/videoeditor/MediaItem.java
new file mode 100755
index 0000000..d9c38af
--- /dev/null
+++ b/media/java/android/media/videoeditor/MediaItem.java
@@ -0,0 +1,546 @@
+/*

+ * Copyright (C) 2010 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES 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.videoeditor;

+

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.List;

+

+import android.graphics.Bitmap;

+

+/**

+ * This abstract class describes the base class for any MediaItem. Objects are

+ * defined with a file path as a source data.

+ * {@hide}

+s */

+public abstract class MediaItem {

+    // A constant which can be used to specify the end of the file (instead of

+    // providing the actual duration of the media item).

+    public final static int END_OF_FILE = -1;

+

+    // Rendering modes

+    /**

+     * When using the RENDERING_MODE_BLACK_BORDER rendering mode video frames

+     * are resized by preserving the aspect ratio until the movie matches one of

+     * the dimensions of the output movie. The areas outside the resized video

+     * clip are rendered black.

+     */

+    public static final int RENDERING_MODE_BLACK_BORDER = 0;

+    /**

+     * When using the RENDERING_MODE_STRETCH rendering mode video frames are

+     * stretched horizontally or vertically to match the current aspect ratio of

+     * the movie.

+     */

+    public static final int RENDERING_MODE_STRETCH = 1;

+

+

+    // The unique id of the MediaItem

+    private final String mUniqueId;

+

+    // The name of the file associated with the MediaItem

+    protected final String mFilename;

+

+    // List of effects

+    private final List<Effect> mEffects;

+

+    // List of overlays

+    private final List<Overlay> mOverlays;

+

+    // The rendering mode

+    private int mRenderingMode;

+

+    // Beginning and end transitions

+    protected Transition mBeginTransition;

+    protected Transition mEndTransition;

+

+    /**

+     * Constructor

+     *

+     * @param editor The video editor reference

+     * @param mediaItemId The MediaItem id

+     * @param filename name of the media file.

+     * @param renderingMode The rendering mode

+     *

+     * @throws IOException if file is not found

+     * @throws IllegalArgumentException if a capability such as file format is not

+     *             supported the exception object contains the unsupported

+     *             capability

+     */

+    protected MediaItem(VideoEditor editor, String mediaItemId, String filename,

+            int renderingMode) throws IOException {

+        mUniqueId = mediaItemId;

+        mFilename = filename;

+        mRenderingMode = renderingMode;

+        mEffects = new ArrayList<Effect>();

+        mOverlays = new ArrayList<Overlay>();

+        mBeginTransition = null;

+        mEndTransition = null;

+    }

+

+    /**

+     * @return The id of the media item

+     */

+    public String getId() {

+        return mUniqueId;

+    }

+

+    /**

+     * @return The media source file name

+     */

+    public String getFilename() {

+        return mFilename;

+    }

+

+    /**

+     * If aspect ratio of the MediaItem is different from the aspect ratio of

+     * the editor then this API controls the rendering mode.

+     *

+     * @param renderingMode rendering mode. It is one of:

+     *            {@link #RENDERING_MODE_BLACK_BORDER},

+     *            {@link #RENDERING_MODE_STRETCH}

+     */

+    public void setRenderingMode(int renderingMode) {

+        mRenderingMode = renderingMode;

+        if (mBeginTransition != null) {

+            mBeginTransition.invalidate();

+        }

+

+        if (mEndTransition != null) {

+            mEndTransition.invalidate();

+        }

+    }

+

+    /**

+     * @return The rendering mode

+     */

+    public int getRenderingMode() {

+        return mRenderingMode;

+    }

+

+    /**

+     * @param transition The beginning transition

+     */

+    void setBeginTransition(Transition transition) {

+        mBeginTransition = transition;

+    }

+

+    /**

+     * @return The begin transition

+     */

+    public Transition getBeginTransition() {

+        return mBeginTransition;

+    }

+

+    /**

+     * @param transition The end transition

+     */

+    void setEndTransition(Transition transition) {

+        mEndTransition = transition;

+    }

+

+    /**

+     * @return The end transition

+     */

+    public Transition getEndTransition() {

+        return mEndTransition;

+    }

+

+    /**

+     * @return The timeline duration. This is the actual duration in the

+     *      timeline (trimmed duration)

+     */

+    public abstract long getTimelineDuration();

+

+    /**

+     * @return The source file type

+     */

+    public abstract int getFileType();

+

+    /**

+     * @return Get the native width of the media item

+     */

+    public abstract int getWidth();

+

+    /**

+     * @return Get the native height of the media item

+     */

+    public abstract int getHeight();

+

+    /**

+     * Get aspect ratio of the source media item.

+     *

+     * @return the aspect ratio as described in MediaProperties.

+     *  MediaProperties.ASPECT_RATIO_UNDEFINED if aspect ratio is not

+     *  supported as in MediaProperties

+     */

+    public abstract int getAspectRatio();

+

+    /**

+     * Add the specified effect to this media item.

+     *

+     * Note that certain types of effects cannot be applied to video and to

+     * image media items. For example in certain implementation a Ken Burns

+     * implementation cannot be applied to video media item.

+     *

+     * This method invalidates transition video clips if the

+     * effect overlaps with the beginning and/or the end transition.

+     *

+     * @param effect The effect to apply

+     * @throws IllegalStateException if a preview or an export is in progress

+     * @throws IllegalArgumentException if the effect start and/or duration are

+     *      invalid or if the effect cannot be applied to this type of media

+     *      item or if the effect id is not unique across all the Effects

+     *      added.

+     */

+    public void addEffect(Effect effect) {

+        if (effect.getMediaItem() != this) {

+            throw new IllegalArgumentException("Media item mismatch");

+        }

+

+        if (mEffects.contains(effect)) {

+            throw new IllegalArgumentException("Effect already exists: " + effect.getId());

+        }

+

+        if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {

+            throw new IllegalArgumentException(

+                    "Effect start time + effect duration > media clip duration");

+        }

+

+        mEffects.add(effect);

+        invalidateTransitions(effect);

+    }

+

+    /**

+     * Remove the effect with the specified id.

+     *

+     * This method invalidates a transition video clip if the effect overlaps

+     * with a transition.

+     *

+     * @param effectId The id of the effect to be removed

+     *

+     * @return The effect that was removed

+     * @throws IllegalStateException if a preview or an export is in progress

+     */

+    public Effect removeEffect(String effectId) {

+        for (Effect effect : mEffects) {

+            if (effect.getId().equals(effectId)) {

+                mEffects.remove(effect);

+                invalidateTransitions(effect);

+                return effect;

+            }

+        }

+

+        return null;

+    }

+

+    /**

+     * Find the effect with the specified id

+     *

+     * @param effectId The effect id

+     *

+     * @return The effect with the specified id (null if it does not exist)

+     */

+    public Effect getEffect(String effectId) {

+        for (Effect effect : mEffects) {

+            if (effect.getId().equals(effectId)) {

+                return effect;

+            }

+        }

+

+        return null;

+    }

+

+    /**

+     * Get the list of effects.

+     *

+     * @return the effects list. If no effects exist an empty list will be returned.

+     */

+    public List<Effect> getAllEffects() {

+        return mEffects;

+    }

+

+    /**

+     * Add an overlay to the storyboard. This method invalidates a transition

+     * video clip if the overlay overlaps with a transition.

+     *

+     * @param overlay The overlay to add

+     * @throws IllegalStateException if a preview or an export is in progress or

+     *             if the overlay id is not unique across all the overlays

+     *             added or if the bitmap is not specified or if the dimensions of

+     *             the bitmap do not match the dimensions of the media item

+     */

+    public void addOverlay(Overlay overlay) {

+        if (overlay.getMediaItem() != this) {

+            throw new IllegalArgumentException("Media item mismatch");

+        }

+

+        if (mOverlays.contains(overlay)) {

+            throw new IllegalArgumentException("Overlay already exists: " + overlay.getId());

+        }

+

+        if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {

+            throw new IllegalArgumentException(

+                    "Overlay start time + overlay duration > media clip duration");

+        }

+

+        if (overlay instanceof OverlayFrame) {

+            final OverlayFrame frame = (OverlayFrame)overlay;

+            final Bitmap bitmap = frame.getBitmap();

+            if (bitmap == null) {

+                throw new IllegalArgumentException("Overlay bitmap not specified");

+            }

+

+            final int scaledWidth, scaledHeight;

+            if (this instanceof MediaVideoItem) {

+                scaledWidth = getWidth();

+                scaledHeight = getHeight();

+            } else {

+                scaledWidth = ((MediaImageItem)this).getScaledWidth();

+                scaledHeight = ((MediaImageItem)this).getScaledHeight();

+            }

+

+            // The dimensions of the overlay bitmap must be the same as the

+            // media item dimensions

+            if (bitmap.getWidth() != scaledWidth || bitmap.getHeight() != scaledHeight) {

+                throw new IllegalArgumentException(

+                        "Bitmap dimensions must match media item dimensions");

+            }

+        } else {

+            throw new IllegalArgumentException("Overlay not supported");

+        }

+

+        mOverlays.add(overlay);

+        invalidateTransitions(overlay);

+    }

+

+    /**

+     * Remove the overlay with the specified id.

+     *

+     * This method invalidates a transition video clip if the overlay overlaps

+     * with a transition.

+     *

+     * @param overlayId The id of the overlay to be removed

+     *

+     * @return The overlay that was removed

+     * @throws IllegalStateException if a preview or an export is in progress

+     */

+    public Overlay removeOverlay(String overlayId) {

+        for (Overlay overlay : mOverlays) {

+            if (overlay.getId().equals(overlayId)) {

+                mOverlays.remove(overlay);

+                if (overlay instanceof OverlayFrame) {

+                    ((OverlayFrame)overlay).invalidate();

+                }

+                invalidateTransitions(overlay);

+                return overlay;

+            }

+        }

+

+        return null;

+    }

+

+    /**

+     * Find the overlay with the specified id

+     *

+     * @param overlayId The overlay id

+     *

+     * @return The overlay with the specified id (null if it does not exist)

+     */

+    public Overlay getOverlay(String overlayId) {

+        for (Overlay overlay : mOverlays) {

+            if (overlay.getId().equals(overlayId)) {

+                return overlay;

+            }

+        }

+

+        return null;

+    }

+

+    /**

+     * Get the list of overlays associated with this media item

+     *

+     * Note that if any overlay source files are not accessible anymore,

+     * this method will still provide the full list of overlays.

+     *

+     * @return The list of overlays. If no overlays exist an empty list will

+     *  be returned.

+     */

+    public List<Overlay> getAllOverlays() {

+        return mOverlays;

+    }

+

+    /**

+     * Create a thumbnail at specified time in a video stream in Bitmap format

+     *

+     * @param width width of the thumbnail in pixels

+     * @param height height of the thumbnail in pixels

+     * @param timeMs The time in the source video file at which the thumbnail is

+     *            requested (even if trimmed).

+     *

+     * @return The thumbnail as a Bitmap.

+     *

+     * @throws IOException if a file error occurs

+     * @throws IllegalArgumentException if time is out of video duration

+     */

+    public abstract Bitmap getThumbnail(int width, int height, long timeMs) throws IOException;

+

+    /**

+     * Get the array of Bitmap thumbnails between start and end.

+     *

+     * @param width width of the thumbnail in pixels

+     * @param height height of the thumbnail in pixels

+     * @param startMs The start of time range in milliseconds

+     * @param endMs The end of the time range in milliseconds

+     * @param thumbnailCount The thumbnail count

+     *

+     * @return The array of Bitmaps

+     *

+     * @throws IOException if a file error occurs

+     */

+    public abstract Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,

+            int thumbnailCount) throws IOException;

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public boolean equals(Object object) {

+        if (!(object instanceof MediaItem)) {

+            return false;

+        }

+        return mUniqueId.equals(((MediaItem)object).mUniqueId);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int hashCode() {

+        return mUniqueId.hashCode();

+    }

+

+    /**

+     * Invalidate the start and end transitions if necessary

+     *

+     * @param effect The effect that was added or removed

+     */

+    void invalidateTransitions(Effect effect) {

+        // Check if the effect overlaps with the beginning and end transitions

+        if (mBeginTransition != null) {

+            if (effect.getStartTime() < mBeginTransition.getDuration()) {

+                mBeginTransition.invalidate();

+            }

+        }

+

+        if (mEndTransition != null) {

+            if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()

+                    - mEndTransition.getDuration()) {

+                mEndTransition.invalidate();

+            }

+        }

+    }

+

+    /**

+     * Invalidate the start and end transitions if necessary

+     *

+     * @param overlay The effect that was added or removed

+     */

+    void invalidateTransitions(Overlay overlay) {

+        // Check if the overlay overlaps with the beginning and end transitions

+        if (mBeginTransition != null) {

+            if (overlay.getStartTime() < mBeginTransition.getDuration()) {

+                mBeginTransition.invalidate();

+            }

+        }

+

+        if (mEndTransition != null) {

+            if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()

+                    - mEndTransition.getDuration()) {

+                mEndTransition.invalidate();

+            }

+        }

+    }

+

+    /**

+     * Adjust the duration of effects, overlays and transitions.

+     * This method will be called after a media item duration is changed.

+     */

+    protected void adjustElementsDuration() {

+        // Check if the duration of transitions need to be adjusted

+        if (mBeginTransition != null) {

+            final long maxDurationMs = mBeginTransition.getMaximumDuration();

+            if (mBeginTransition.getDuration() > maxDurationMs) {

+                mBeginTransition.setDuration(maxDurationMs);

+            }

+        }

+

+        if (mEndTransition != null) {

+            final long maxDurationMs = mEndTransition.getMaximumDuration();

+            if (mEndTransition.getDuration() > maxDurationMs) {

+                mEndTransition.setDuration(maxDurationMs);

+            }

+        }

+

+        final List<Overlay> overlays = getAllOverlays();

+        for (Overlay overlay : overlays) {

+            // Adjust the start time if necessary

+            final long overlayStartTimeMs;

+            if (overlay.getStartTime() > getTimelineDuration()) {

+                overlayStartTimeMs = 0;

+            } else {

+                overlayStartTimeMs = overlay.getStartTime();

+            }

+

+            // Adjust the duration if necessary

+            final long overlayDurationMs;

+            if (overlayStartTimeMs + overlay.getDuration() > getTimelineDuration()) {

+                overlayDurationMs = getTimelineDuration() - overlayStartTimeMs;

+            } else {

+                overlayDurationMs = overlay.getDuration();

+            }

+

+            if (overlayStartTimeMs != overlay.getStartTime() ||

+                    overlayDurationMs != overlay.getDuration()) {

+                overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs);

+            }

+        }

+

+        final List<Effect> effects = getAllEffects();

+        for (Effect effect : effects) {

+            // Adjust the start time if necessary

+            final long effectStartTimeMs;

+            if (effect.getStartTime() > getTimelineDuration()) {

+                effectStartTimeMs = 0;

+            } else {

+                effectStartTimeMs = effect.getStartTime();

+            }

+

+            // Adjust the duration if necessary

+            final long effectDurationMs;

+            if (effectStartTimeMs + effect.getDuration() > getTimelineDuration()) {

+                effectDurationMs = getTimelineDuration() - effectStartTimeMs;

+            } else {

+                effectDurationMs = effect.getDuration();

+            }

+

+            if (effectStartTimeMs != effect.getStartTime() ||

+                    effectDurationMs != effect.getDuration()) {

+                effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs);

+            }

+        }

+    }

+}

diff --git a/media/java/android/media/videoeditor/MediaProperties.java b/media/java/android/media/videoeditor/MediaProperties.java
new file mode 100755
index 0000000..34088fc
--- /dev/null
+++ b/media/java/android/media/videoeditor/MediaProperties.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 android.media.videoeditor;

+

+import android.util.Pair;

+

+/**

+ * This class defines all properties of a media file such as supported height, aspect ratio,

+ * bitrate for export function.

+ * {@hide}

+ */

+public class MediaProperties {

+    // Supported heights

+    public static final int HEIGHT_144 = 144;

+    public static final int HEIGHT_360 = 360;

+    public static final int HEIGHT_480 = 480;

+    public static final int HEIGHT_720 = 720;

+    public static final int HEIGHT_1080 = 1080;

+

+    // Supported aspect ratios

+    public static final int ASPECT_RATIO_UNDEFINED = 0;

+    public static final int ASPECT_RATIO_3_2 = 1;

+    public static final int ASPECT_RATIO_16_9 = 2;

+    public static final int ASPECT_RATIO_4_3 = 3;

+    public static final int ASPECT_RATIO_5_3 = 4;

+    public static final int ASPECT_RATIO_11_9 = 5;

+

+    // The array of supported aspect ratios

+    private static final int[] ASPECT_RATIOS = new int[] {

+        ASPECT_RATIO_3_2,

+        ASPECT_RATIO_16_9,

+        ASPECT_RATIO_4_3,

+        ASPECT_RATIO_5_3,

+        ASPECT_RATIO_11_9

+    };

+

+    // Supported resolutions for specific aspect ratios

+    @SuppressWarnings({"unchecked"})

+    private static final Pair<Integer, Integer>[] ASPECT_RATIO_3_2_RESOLUTIONS =

+        new Pair[] {

+        new Pair<Integer, Integer>(720, HEIGHT_480),

+        new Pair<Integer, Integer>(1080, HEIGHT_720)

+    };

+

+    @SuppressWarnings({"unchecked"})

+    private static final Pair<Integer, Integer>[] ASPECT_RATIO_4_3_RESOLUTIONS =

+        new Pair[] {

+        new Pair<Integer, Integer>(640, HEIGHT_480),

+        new Pair<Integer, Integer>(960, HEIGHT_720)

+    };

+

+    @SuppressWarnings({"unchecked"})

+    private static final Pair<Integer, Integer>[] ASPECT_RATIO_5_3_RESOLUTIONS =

+        new Pair[] {

+        new Pair<Integer, Integer>(800, HEIGHT_480)

+    };

+

+    @SuppressWarnings({"unchecked"})

+    private static final Pair<Integer, Integer>[] ASPECT_RATIO_11_9_RESOLUTIONS =

+        new Pair[] {

+        new Pair<Integer, Integer>(176, HEIGHT_144)

+    };

+

+    @SuppressWarnings({"unchecked"})

+    private static final Pair<Integer, Integer>[] ASPECT_RATIO_16_9_RESOLUTIONS =

+        new Pair[] {

+        new Pair<Integer, Integer>(640, HEIGHT_360),

+        new Pair<Integer, Integer>(854, HEIGHT_480),

+        new Pair<Integer, Integer>(1280, HEIGHT_720),

+    };

+

+

+    // Bitrate values (in bits per second)

+    public static final int BITRATE_28K = 28000;

+    public static final int BITRATE_40K = 40000;

+    public static final int BITRATE_64K = 64000;

+    public static final int BITRATE_96K = 96000;

+    public static final int BITRATE_128K = 128000;

+    public static final int BITRATE_192K = 192000;

+    public static final int BITRATE_256K = 256000;

+    public static final int BITRATE_384K = 384000;

+    public static final int BITRATE_512K = 512000;

+    public static final int BITRATE_800K = 800000;

+

+    // The array of supported bitrates

+    private static final int[] SUPPORTED_BITRATES = new int[] {

+        BITRATE_28K,

+        BITRATE_40K,

+        BITRATE_64K,

+        BITRATE_96K,

+        BITRATE_128K,

+        BITRATE_192K,

+        BITRATE_256K,

+        BITRATE_384K,

+        BITRATE_512K,

+        BITRATE_800K

+    };

+

+    // Video codec types

+    public static final int VCODEC_H264BP = 1;

+    public static final int VCODEC_H264MP = 2;

+    public static final int VCODEC_H263 = 3;

+    public static final int VCODEC_MPEG4 = 4;

+

+    // The array of supported video codecs

+    private static final int[] SUPPORTED_VCODECS = new int[] {

+        VCODEC_H264BP,

+        VCODEC_H263,

+        VCODEC_MPEG4,

+    };

+

+    // Audio codec types

+    public static final int ACODEC_AAC_LC = 1;

+    public static final int ACODEC_AMRNB = 2;

+    public static final int ACODEC_AMRWB = 3;

+    public static final int ACODEC_MP3 = 4;

+    public static final int ACODEC_OGG = 5;

+

+    // The array of supported video codecs

+    private static final int[] SUPPORTED_ACODECS = new int[] {

+        ACODEC_AAC_LC,

+        ACODEC_AMRNB,

+        ACODEC_AMRWB

+    };

+

+    // File format types

+    public static final int FILE_UNSUPPORTED = 0;

+    public static final int FILE_3GP = 1;

+    public static final int FILE_MP4 = 2;

+    public static final int FILE_JPEG = 3;

+    public static final int FILE_PNG = 4;

+

+    // The array of the supported file formats

+    private static final int[] SUPPORTED_VIDEO_FILE_FORMATS = new int[] {

+        FILE_3GP,

+        FILE_MP4

+    };

+

+    // The maximum count of audio tracks supported

+    public static final int AUDIO_MAX_TRACK_COUNT = 1;

+

+    // The maximum volume supported (100 means that no amplification is

+    // supported, i.e. attenuation only)

+    public static final int AUDIO_MAX_VOLUME_PERCENT = 100;

+

+    /**

+     * This class cannot be instantiated

+     */

+    private MediaProperties() {

+    }

+

+    /**

+     * @return The array of supported aspect ratios

+     */

+    public static int[] getAllSupportedAspectRatios() {

+        return ASPECT_RATIOS;

+    }

+

+    /**

+     * Get the supported resolutions for the specified aspect ratio.

+     *

+     * @param aspectRatio The aspect ratio for which the resolutions are requested

+     *

+     * @return The array of width and height pairs

+     */

+    public static Pair<Integer, Integer>[] getSupportedResolutions(int aspectRatio) {

+        final Pair<Integer, Integer>[] resolutions;

+        switch(aspectRatio) {

+            case ASPECT_RATIO_3_2: {

+                resolutions = ASPECT_RATIO_3_2_RESOLUTIONS;

+                break;

+            }

+

+            case ASPECT_RATIO_4_3: {

+                resolutions = ASPECT_RATIO_4_3_RESOLUTIONS;

+                break;

+            }

+

+            case ASPECT_RATIO_5_3: {

+                resolutions = ASPECT_RATIO_5_3_RESOLUTIONS;

+                break;

+            }

+

+            case ASPECT_RATIO_11_9: {

+                resolutions = ASPECT_RATIO_11_9_RESOLUTIONS;

+                break;

+            }

+

+            case ASPECT_RATIO_16_9: {

+                resolutions = ASPECT_RATIO_16_9_RESOLUTIONS;

+                break;

+            }

+

+            default: {

+                throw new IllegalArgumentException("Unknown aspect ratio: " + aspectRatio);

+            }

+        }

+

+        return resolutions;

+    }

+

+    /**

+     * @return The array of supported video codecs

+     */

+    public static int[] getSupportedVideoCodecs() {

+        return SUPPORTED_VCODECS;

+    }

+

+    /**

+     * @return The array of supported audio codecs

+     */

+    public static int[] getSupportedAudioCodecs() {

+        return SUPPORTED_ACODECS;

+    }

+

+    /**

+     * @return The array of supported file formats

+     */

+    public static int[] getSupportedVideoFileFormat() {

+        return SUPPORTED_VIDEO_FILE_FORMATS;

+    }

+

+    /**

+     * @return The array of supported video bitrates

+     */

+    public static int[] getSupportedVideoBitrates() {

+        return SUPPORTED_BITRATES;

+    }

+

+    /**

+     * @return The maximum value for the audio volume

+     */

+    public static int getSupportedMaxVolume() {

+        return MediaProperties.AUDIO_MAX_VOLUME_PERCENT;

+    }

+

+    /**

+     * @return The maximum number of audio tracks supported

+     */

+    public static int getSupportedAudioTrackCount() {

+        return MediaProperties.AUDIO_MAX_TRACK_COUNT;

+    }

+}

diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java
new file mode 100755
index 0000000..dd12336
--- /dev/null
+++ b/media/java/android/media/videoeditor/MediaVideoItem.java
@@ -0,0 +1,583 @@
+/*

+ * Copyright (C) 2010 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES 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.videoeditor;

+

+import java.io.IOException;

+

+import android.graphics.Bitmap;

+import android.util.Log;

+import android.view.SurfaceHolder;

+

+/**

+ * This class represents a video clip item on the storyboard

+ * {@hide}

+ */

+public class MediaVideoItem extends MediaItem {

+    // Logging

+    private static final String TAG = "MediaVideoItem";

+

+    // Instance variables

+    private final int mWidth;

+    private final int mHeight;

+    private final int mAspectRatio;

+    private final int mFileType;

+    private final int mVideoType;

+    private final int mVideoProfile;

+    private final int mVideoBitrate;

+    private final long mDurationMs;

+    private final int mAudioBitrate;

+    private final int mFps;

+    private final int mAudioType;

+    private final int mAudioChannels;

+    private final int mAudioSamplingFrequency;

+

+    private long mBeginBoundaryTimeMs;

+    private long mEndBoundaryTimeMs;

+    private int mVolumePercentage;

+    private boolean mMuted;

+    private String mAudioWaveformFilename;

+    private PlaybackThread mPlaybackThread;

+

+    /**

+     * This listener interface is used by the MediaVideoItem to emit playback

+     * progress notifications. This callback should be invoked after the

+     * number of frames specified by

+     * {@link #startPlayback(SurfaceHolder surfaceHolder, long fromMs,

+     *           int callbackAfterFrameCount, PlaybackProgressListener listener)}

+     */

+    public interface PlaybackProgressListener {

+        /**

+         * This method notifies the listener of the current time position while

+         * playing a media item

+         *

+         * @param mediaItem The media item

+         * @param timeMs The current playback position (expressed in milliseconds

+         *            since the beginning of the media item).

+         * @param end true if the end of the media item was reached

+         */

+        public void onProgress(MediaVideoItem mediaItem, long timeMs, boolean end);

+    }

+

+    /**

+     * The playback thread

+     */

+    private class PlaybackThread extends Thread {

+        // Instance variables

+        private final static long FRAME_DURATION = 33;

+        private final PlaybackProgressListener mListener;

+        private final int mCallbackAfterFrameCount;

+        private final long mFromMs, mToMs;

+        private boolean mRun;

+        private final boolean mLoop;

+        private long mPositionMs;

+

+        /**

+         * Constructor

+         *

+         * @param fromMs The time (relative to the beginning of the media item)

+         *            at which the playback will start

+         * @param toMs The time (relative to the beginning of the media item) at

+         *            which the playback will stop. Use -1 to play to the end of

+         *            the media item

+         * @param loop true if the playback should be looped once it reaches the

+         *            end

+         * @param callbackAfterFrameCount The listener interface should be

+         *            invoked after the number of frames specified by this

+         *            parameter.

+         * @param listener The listener which will be notified of the playback

+         *            progress

+         */

+        public PlaybackThread(long fromMs, long toMs, boolean loop, int callbackAfterFrameCount,

+                PlaybackProgressListener listener) {

+            mPositionMs = mFromMs = fromMs;

+            if (toMs < 0) {

+                mToMs = mDurationMs;

+            } else {

+                mToMs = toMs;

+            }

+            mLoop = loop;

+            mCallbackAfterFrameCount = callbackAfterFrameCount;

+            mListener = listener;

+            mRun = true;

+        }

+

+        /*

+         * {@inheritDoc}

+         */

+        @Override

+        public void run() {

+            if (Log.isLoggable(TAG, Log.DEBUG)) {

+                Log.d(TAG, "===> PlaybackThread.run enter");

+            }

+            int frameCount = 0;

+            while (mRun) {

+                try {

+                    sleep(FRAME_DURATION);

+                } catch (InterruptedException ex) {

+                    break;

+                }

+                frameCount++;

+                mPositionMs += FRAME_DURATION;

+

+                if (mPositionMs >= mToMs) {

+                    if (!mLoop) {

+                        if (mListener != null) {

+                            mListener.onProgress(MediaVideoItem.this, mPositionMs, true);

+                        }

+                        if (Log.isLoggable(TAG, Log.DEBUG)) {

+                            Log.d(TAG, "PlaybackThread.run playback complete");

+                        }

+                        break;

+                    } else {

+                        // Fire a notification for the end of the clip

+                        if (mListener != null) {

+                            mListener.onProgress(MediaVideoItem.this, mToMs, false);

+                        }

+

+                        // Rewind

+                        mPositionMs = mFromMs;

+                        if (mListener != null) {

+                            mListener.onProgress(MediaVideoItem.this, mPositionMs, false);

+                        }

+                        if (Log.isLoggable(TAG, Log.DEBUG)) {

+                            Log.d(TAG, "PlaybackThread.run playback complete");

+                        }

+                        frameCount = 0;

+                    }

+                } else {

+                    if (frameCount == mCallbackAfterFrameCount) {

+                        if (mListener != null) {

+                            mListener.onProgress(MediaVideoItem.this, mPositionMs, false);

+                        }

+                        frameCount = 0;

+                    }

+                }

+            }

+            if (Log.isLoggable(TAG, Log.DEBUG)) {

+                Log.d(TAG, "===> PlaybackThread.run exit");

+            }

+        }

+

+        /**

+         * Stop the playback

+         *

+         * @return The stop position

+         */

+        public long stopPlayback() {

+            mRun = false;

+            try {

+                join();

+            } catch (InterruptedException ex) {

+            }

+            return mPositionMs;

+        }

+    };

+

+    /**

+     * An object of this type cannot be instantiated with a default constructor

+     */

+    @SuppressWarnings("unused")

+    private MediaVideoItem() throws IOException {

+        this(null, null, null, RENDERING_MODE_BLACK_BORDER);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param editor The video editor reference

+     * @param mediaItemId The MediaItem id

+     * @param filename The image file name

+     * @param renderingMode The rendering mode

+     *

+     * @throws IOException if the file cannot be opened for reading

+     */

+    public MediaVideoItem(VideoEditor editor, String mediaItemId, String filename,

+            int renderingMode)

+        throws IOException {

+        this(editor, mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param editor The video editor reference

+     * @param mediaItemId The MediaItem id

+     * @param filename The image file name

+     * @param renderingMode The rendering mode

+     * @param beginMs Start time in milliseconds. Set to 0 to extract from the

+     *           beginning

+     * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to

+     *           extract until the end

+     * @param volumePercent in %/. 100% means no change; 50% means half value, 200%

+     *            means double, 0% means silent.

+     * @param muted true if the audio is muted

+     * @param audioWaveformFilename The name of the audio waveform file

+     *

+     * @throws IOException if the file cannot be opened for reading

+     */

+    MediaVideoItem(VideoEditor editor, String mediaItemId, String filename, int renderingMode,

+            long beginMs, long endMs, int volumePercent, boolean muted,

+            String audioWaveformFilename)  throws IOException {

+        super(editor, mediaItemId, filename, renderingMode);

+        // TODO: Set these variables correctly

+        mWidth = 1080;

+        mHeight = 720;

+        mAspectRatio = MediaProperties.ASPECT_RATIO_3_2;

+        mFileType = MediaProperties.FILE_MP4;

+        mVideoType = MediaProperties.VCODEC_H264BP;

+        // Do we have predefined values for this variable?

+        mVideoProfile = 0;

+        // Can video and audio duration be different?

+        mDurationMs = 10000;

+        mVideoBitrate = 800000;

+        mAudioBitrate = 30000;

+        mFps = 30;

+        mAudioType = MediaProperties.ACODEC_AAC_LC;

+        mAudioChannels = 2;

+        mAudioSamplingFrequency = 16000;

+

+        mBeginBoundaryTimeMs = beginMs;

+        mEndBoundaryTimeMs = endMs == END_OF_FILE ? mDurationMs : endMs;

+        mVolumePercentage = volumePercent;

+        mMuted = muted;

+        mAudioWaveformFilename = audioWaveformFilename;

+    }

+

+    /**

+     * Sets the start and end marks for trimming a video media item.

+     * This method will adjust the duration of bounding transitions, effects

+     * and overlays if the current duration of the transactions become greater

+     * than the maximum allowable duration.

+     *

+     * @param beginMs Start time in milliseconds. Set to 0 to extract from the

+     *           beginning

+     * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to

+     *           extract until the end

+     *

+     * @throws IllegalArgumentException if the start time is greater or equal than

+     *           end time, the end time is beyond the file duration, the start time

+     *           is negative

+     */

+    public void setExtractBoundaries(long beginMs, long endMs) {

+        if (beginMs > mDurationMs) {

+            throw new IllegalArgumentException("Invalid start time");

+        }

+        if (endMs > mDurationMs) {

+            throw new IllegalArgumentException("Invalid end time");

+        }

+

+        if (beginMs != mBeginBoundaryTimeMs) {

+            if (mBeginTransition != null) {

+                mBeginTransition.invalidate();

+            }

+        }

+

+        if (endMs != mEndBoundaryTimeMs) {

+            if (mEndTransition != null) {

+                mEndTransition.invalidate();

+            }

+        }

+

+        mBeginBoundaryTimeMs = beginMs;

+        mEndBoundaryTimeMs = endMs;

+

+        adjustElementsDuration();

+    }

+

+    /**

+     * @return The boundary begin time

+     */

+    public long getBoundaryBeginTime() {

+        return mBeginBoundaryTimeMs;

+    }

+

+    /**

+     * @return The boundary end time

+     */

+    public long getBoundaryEndTime() {

+        return mEndBoundaryTimeMs;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public void addEffect(Effect effect) {

+        if (effect instanceof EffectKenBurns) {

+            throw new IllegalArgumentException("Ken Burns effects cannot be applied to MediaVideoItem");

+        }

+        super.addEffect(effect);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public Bitmap getThumbnail(int width, int height, long timeMs) {

+        return null;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,

+            int thumbnailCount) throws IOException {

+        return null;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int getAspectRatio() {

+        return mAspectRatio;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int getFileType() {

+        return mFileType;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int getWidth() {

+        return mWidth;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int getHeight() {

+        return mHeight;

+    }

+

+    /**

+     * @return The duration of the video clip

+     */

+    public long getDuration() {

+        return mDurationMs;

+    }

+

+    /**

+     * @return The timeline duration. This is the actual duration in the

+     *      timeline (trimmed duration)

+     */

+    @Override

+    public long getTimelineDuration() {

+        return mEndBoundaryTimeMs - mBeginBoundaryTimeMs;

+    }

+

+    /**

+     * Render a frame according to the playback (in the native aspect ratio) for

+     * the specified media item. All effects and overlays applied to the media

+     * item are ignored. The extract boundaries are also ignored. This method

+     * can be used to playback frames when implementing trimming functionality.

+     *

+     * @param surfaceHolder SurfaceHolder used by the application

+     * @param timeMs time corresponding to the frame to display (relative to the

+     *            the beginning of the media item).

+     * @return The accurate time stamp of the frame that is rendered .

+     * @throws IllegalStateException if a playback, preview or an export is

+     *             already in progress

+     * @throws IllegalArgumentException if time is negative or greater than the

+     *             media item duration

+     */

+    public long renderFrame(SurfaceHolder surfaceHolder, long timeMs) {

+        return timeMs;

+    }

+

+    /**

+     * Start the playback of this media item. This method does not block (does

+     * not wait for the playback to complete). The PlaybackProgressListener

+     * allows to track the progress at the time interval determined by the

+     * callbackAfterFrameCount parameter. The SurfaceHolder has to be created

+     * and ready for use before calling this method.

+     *

+     * @param surfaceHolder SurfaceHolder where the frames are rendered.

+     * @param fromMs The time (relative to the beginning of the media item) at

+     *            which the playback will start

+     * @param toMs The time (relative to the beginning of the media item) at

+     *            which the playback will stop. Use -1 to play to the end of the

+     *            media item

+     * @param loop true if the playback should be looped once it reaches the end

+     * @param callbackAfterFrameCount The listener interface should be invoked

+     *            after the number of frames specified by this parameter.

+     * @param listener The listener which will be notified of the playback

+     *            progress

+     * @throws IllegalArgumentException if fromMs or toMs is beyond the playback

+     *             duration

+     * @throws IllegalStateException if a playback, preview or an export is

+     *             already in progress

+     */

+    public void startPlayback(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop,

+            int callbackAfterFrameCount, PlaybackProgressListener listener) {

+        if (fromMs >= mDurationMs) {

+            return;

+        }

+        mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, callbackAfterFrameCount,

+                listener);

+        mPlaybackThread.start();

+    }

+

+    /**

+     * Stop the media item playback. This method blocks until the ongoing

+     * playback is stopped.

+     *

+     * @return The accurate current time when stop is effective expressed in

+     *         milliseconds

+     */

+    public long stopPlayback() {

+        final long stopTimeMs;

+        if (mPlaybackThread != null) {

+            stopTimeMs = mPlaybackThread.stopPlayback();

+            mPlaybackThread = null;

+        } else {

+            stopTimeMs = 0;

+        }

+        return stopTimeMs;

+    }

+

+    /**

+     * This API allows to generate a file containing the sample volume levels of

+     * the Audio track of this media item. This function may take significant

+     * time and is blocking. The file can be retrieved using

+     * getAudioWaveformFilename().

+     *

+     * @param listener The progress listener

+     *

+     * @throws IOException if the output file cannot be created

+     * @throws IllegalArgumentException if the mediaItem does not have a valid

+     *             Audio track

+     */

+    public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)

+            throws IOException {

+        // TODO: Set mAudioWaveformFilename at the end once the export is complete

+    }

+

+    /**

+     * Get the audio waveform file name if {@link #extractAudioWaveform()} was

+     * successful. The file format is as following:

+     * <ul>

+     *  <li>first 4 bytes provide the number of samples for each value, as big-endian signed</li>

+     *  <li>4 following bytes is the total number of values in the file, as big-endian signed</li>

+     *  <li>all values follow as bytes Name is unique.</li>

+     *</ul>

+     * @return the name of the file, null if the file has not been computed or

+     *         if there is no Audio track in the mediaItem

+     */

+    public String getAudioWaveformFilename() {

+        return mAudioWaveformFilename;

+    }

+

+    /**

+     * Set volume of the Audio track of this mediaItem

+     *

+     * @param volumePercent in %/. 100% means no change; 50% means half value, 200%

+     *            means double, 0% means silent.

+     * @throws UsupportedOperationException if volume value is not supported

+     */

+    public void setVolume(int volumePercent) {

+        mVolumePercentage = volumePercent;

+    }

+

+    /**

+     * Get the volume value of the audio track as percentage. Call of this

+     * method before calling setVolume will always return 100%

+     *

+     * @return the volume in percentage

+     */

+    public int getVolume() {

+        return mVolumePercentage;

+    }

+

+    /**

+     * @param muted true to mute the media item

+     */

+    public void setMute(boolean muted) {

+        mMuted = muted;

+    }

+

+    /**

+     * @return true if the media item is muted

+     */

+    public boolean isMuted() {

+        return mMuted;

+    }

+

+    /**

+     * @return The video type

+     */

+    public int getVideoType() {

+        return mVideoType;

+    }

+

+    /**

+     * @return The video profile

+     */

+    public int getVideoProfile() {

+        return mVideoProfile;

+    }

+

+    /**

+     * @return The video bitrate

+     */

+    public int getVideoBitrate() {

+        return mVideoBitrate;

+    }

+

+    /**

+     * @return The audio bitrate

+     */

+    public int getAudioBitrate() {

+        return mAudioBitrate;

+    }

+

+    /**

+     * @return The number of frames per second

+     */

+    public int getFps() {

+        return mFps;

+    }

+

+    /**

+     * @return The audio codec

+     */

+    public int getAudioType() {

+        return mAudioType;

+    }

+

+    /**

+     * @return The number of audio channels

+     */

+    public int getAudioChannels() {

+        return mAudioChannels;

+    }

+

+    /**

+     * @return The audio sample frequency

+     */

+    public int getAudioSamplingFrequency() {

+        return mAudioSamplingFrequency;

+    }

+}

diff --git a/media/java/android/media/videoeditor/Overlay.java b/media/java/android/media/videoeditor/Overlay.java
new file mode 100755
index 0000000..c58b5cb
--- /dev/null
+++ b/media/java/android/media/videoeditor/Overlay.java
@@ -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.

+ */

+

+package android.media.videoeditor;

+

+import java.util.HashMap;

+import java.util.Map;

+

+

+/**

+ * This is the super class for all Overlay classes.

+ * {@hide}

+ */

+public abstract class Overlay {

+    // Instance variables

+    private final String mUniqueId;

+    // The overlay owner

+    private final MediaItem mMediaItem;

+    // user attributes

+    private final Map<String, String> mUserAttributes;

+

+    protected long mStartTimeMs;

+    protected long mDurationMs;

+

+

+    /**

+     * Default constructor

+     */

+    @SuppressWarnings("unused")

+    private Overlay() {

+        this(null, null, 0, 0);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param mediaItem The media item owner

+     * @param overlayId The overlay id

+     * @param startTimeMs The start time relative to the media item start time

+     * @param durationMs The duration

+     *

+     * @throws IllegalArgumentException if the file type is not PNG or the

+     *      startTimeMs and durationMs are incorrect.

+     */

+    public Overlay(MediaItem mediaItem, String overlayId, long startTimeMs, long durationMs) {

+        if (mediaItem == null) {

+            throw new IllegalArgumentException("Media item cannot be null");

+        }

+

+        if (startTimeMs + durationMs > mediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Invalid start time and duration");

+        }

+

+        mMediaItem = mediaItem;

+        mUniqueId = overlayId;

+        mStartTimeMs = startTimeMs;

+        mDurationMs = durationMs;

+        mUserAttributes = new HashMap<String, String>();

+    }

+

+    /**

+     * @return The of the overlay

+     */

+    public String getId() {

+        return mUniqueId;

+    }

+

+    /**

+     * @return The duration of the overlay effect

+     */

+    public long getDuration() {

+        return mDurationMs;

+    }

+

+    /**

+     * If a preview or export is in progress, then this change is effective for

+     * next preview or export session.

+     *

+     * @param durationMs The duration in milliseconds

+     */

+    public void setDuration(long durationMs) {

+        if (mStartTimeMs + durationMs > mMediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Duration is too large");

+        }

+

+        mDurationMs = durationMs;

+

+        mMediaItem.invalidateTransitions(this);

+    }

+

+    /**

+     * @return the start time of the overlay

+     */

+    public long getStartTime() {

+        return mStartTimeMs;

+    }

+

+    /**

+     * Set the start time for the overlay. If a preview or export is in

+     * progress, then this change is effective for next preview or export

+     * session.

+     *

+     * @param startTimeMs start time in milliseconds

+     */

+    public void setStartTime(long startTimeMs) {

+        if (startTimeMs + mDurationMs > mMediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Start time is too large");

+        }

+

+        mStartTimeMs = startTimeMs;

+

+        mMediaItem.invalidateTransitions(this);

+    }

+

+    /**

+     * Set the start time and duration

+     *

+     * @param startTimeMs start time in milliseconds

+     * @param durationMs The duration in milliseconds

+     */

+    public void setStartTimeAndDuration(long startTimeMs, long durationMs) {

+        if (startTimeMs + durationMs > mMediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Invalid start time or duration");

+        }

+

+        mStartTimeMs = startTimeMs;

+        mDurationMs = durationMs;

+

+        mMediaItem.invalidateTransitions(this);

+    }

+

+    /**

+     * @return The media item owner

+     */

+    public MediaItem getMediaItem() {

+        return mMediaItem;

+    }

+

+    /**

+     * Set a user attribute

+     *

+     * @param name The attribute name

+     * @param value The attribute value

+     */

+    public void setUserAttribute(String name, String value) {

+        mUserAttributes.put(name, value);

+    }

+

+    /**

+     * @return The user attributes

+     */

+    public Map<String, String> getUserAttributes() {

+        return mUserAttributes;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public boolean equals(Object object) {

+        if (!(object instanceof Overlay)) {

+            return false;

+        }

+        return mUniqueId.equals(((Overlay)object).mUniqueId);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int hashCode() {

+        return mUniqueId.hashCode();

+    }

+}

diff --git a/media/java/android/media/videoeditor/OverlayFrame.java b/media/java/android/media/videoeditor/OverlayFrame.java
new file mode 100755
index 0000000..dcac4ba
--- /dev/null
+++ b/media/java/android/media/videoeditor/OverlayFrame.java
@@ -0,0 +1,149 @@
+/*

+ * Copyright (C) 2010 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES 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.videoeditor;

+

+import java.io.File;

+import java.io.FileNotFoundException;

+import java.io.FileOutputStream;

+import java.io.IOException;

+

+import android.graphics.Bitmap;

+import android.graphics.BitmapFactory;

+import android.graphics.Bitmap.CompressFormat;

+

+

+/**

+ * This class is used to overlay an image on top of a media item.

+ * {@hide}

+ */

+public class OverlayFrame extends Overlay {

+    // Instance variables

+    private Bitmap mBitmap;

+    private String mFilename;

+

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private OverlayFrame() {

+        this(null, null, (String)null, 0, 0);

+    }

+

+    /**

+     * Constructor for an OverlayFrame

+     *

+     * @param mediaItem The media item owner

+     * @param overlayId The overlay id

+     * @param bitmap The bitmap to be used as an overlay. The size of the

+     *      bitmap must equal to the size of the media item to which it is

+     *      added. The bitmap is typically a decoded PNG file.

+     * @param startTimeMs The overlay start time in milliseconds

+     * @param durationMs The overlay duration in milliseconds

+     *

+     * @throws IllegalArgumentException if the file type is not PNG or the

+     *      startTimeMs and durationMs are incorrect.

+     */

+    public OverlayFrame(MediaItem mediaItem, String overlayId, Bitmap bitmap, long startTimeMs,

+            long durationMs) {

+        super(mediaItem, overlayId, startTimeMs, durationMs);

+        mBitmap = bitmap;

+        mFilename = null;

+    }

+

+    /**

+     * Constructor for an OverlayFrame. This constructor can be used to

+     * restore the overlay after it was saved internally by the video editor.

+     *

+     * @param mediaItem The media item owner

+     * @param overlayId The overlay id

+     * @param filename The file name that contains the overlay.

+     * @param startTimeMs The overlay start time in milliseconds

+     * @param durationMs The overlay duration in milliseconds

+     *

+     * @throws IllegalArgumentException if the file type is not PNG or the

+     *      startTimeMs and durationMs are incorrect.

+     */

+    OverlayFrame(MediaItem mediaItem, String overlayId, String filename, long startTimeMs,

+            long durationMs) {

+        super(mediaItem, overlayId, startTimeMs, durationMs);

+        mFilename = filename;

+        mBitmap = BitmapFactory.decodeFile(mFilename);

+    }

+

+    /**

+     * @return Get the overlay bitmap

+     */

+    public Bitmap getBitmap() {

+        return mBitmap;

+    }

+

+    /**

+     * @param bitmap The overlay bitmap

+     */

+    public void setBitmap(Bitmap bitmap) {

+        mBitmap = bitmap;

+        if (mFilename != null) {

+            // Delete the file

+            new File(mFilename).delete();

+            // Invalidate the filename

+            mFilename = null;

+        }

+

+        // Invalidate the transitions if necessary

+        getMediaItem().invalidateTransitions(this);

+    }

+

+    /**

+     * Get the file name of this overlay

+     */

+    String getFilename() {

+        return mFilename;

+    }

+

+    /**

+     * Save the overlay to the project folder

+     *

+     * @param path The path where the overlay will be saved

+     *

+     * @return The filename

+     * @throws FileNotFoundException if the bitmap cannot be saved

+     * @throws IOException if the bitmap file cannot be saved

+     */

+    String save(String path) throws FileNotFoundException, IOException {

+        if (mFilename != null) {

+            return mFilename;

+        }

+

+        mFilename = path + "/" + getId() + ".png";

+        // Save the image to a local file

+        final FileOutputStream out = new FileOutputStream(mFilename);

+        mBitmap.compress(CompressFormat.PNG, 100, out);

+        out.flush();

+        out.close();

+        return mFilename;

+    }

+

+    /**

+     * Delete the overlay file

+     */

+    void invalidate() {

+        if (mFilename != null) {

+            new File(mFilename).delete();

+        }

+    }

+}

diff --git a/media/java/android/media/videoeditor/Transition.java b/media/java/android/media/videoeditor/Transition.java
new file mode 100755
index 0000000..1c82742
--- /dev/null
+++ b/media/java/android/media/videoeditor/Transition.java
@@ -0,0 +1,210 @@
+/*

+ * Copyright (C) 2010 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES 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.videoeditor;

+

+import java.io.File;

+

+/**

+ * This class is super class for all transitions. Transitions (with the

+ * exception of TransitionAtStart and TransitioAtEnd) can only be inserted

+ * between media items.

+ *

+ * Adding a transition between MediaItems makes the

+ * duration of the storyboard shorter by the duration of the Transition itself.

+ * As a result, if the duration of the transition is larger than the smaller

+ * duration of the two MediaItems associated with the Transition, an exception

+ * will be thrown.

+ *

+ * During a transition, the audio track are cross-fading

+ * automatically. {@hide}

+ */

+public abstract class Transition {

+    // The transition behavior

+    private static final int BEHAVIOR_MIN_VALUE = 0;

+    /** The transition starts slowly and speed up */

+    public static final int BEHAVIOR_SPEED_UP = 0;

+    /** The transition start fast and speed down */

+    public static final int BEHAVIOR_SPEED_DOWN = 1;

+    /** The transition speed is constant */

+    public static final int BEHAVIOR_LINEAR = 2;

+    /** The transition starts fast and ends fast with a slow middle */

+    public static final int BEHAVIOR_MIDDLE_SLOW = 3;

+    /** The transition starts slowly and ends slowly with a fast middle */

+    public static final int BEHAVIOR_MIDDLE_FAST = 4;

+

+    private static final int BEHAVIOR_MAX_VALUE = 4;

+

+    // The unique id of the transition

+    private final String mUniqueId;

+

+    // The transition is applied at the end of this media item

+    private final MediaItem mAfterMediaItem;

+    // The transition is applied at the beginning of this media item

+    private final MediaItem mBeforeMediaItem;

+

+    // The transition behavior

+    protected final int mBehavior;

+

+    // The transition duration

+    protected long mDurationMs;

+

+    // The transition filename

+    protected String mFilename;

+

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private Transition() {

+        this(null, null, null, 0, 0);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param transitionId The transition id

+     * @param afterMediaItem The transition is applied to the end of this

+     *      media item

+     * @param beforeMediaItem The transition is applied to the beginning of

+     *      this media item

+     * @param durationMs The duration of the transition in milliseconds

+     * @param behavior The transition behavior

+     */

+    protected Transition(String transitionId, MediaItem afterMediaItem, MediaItem beforeMediaItem,

+            long durationMs, int behavior) {

+        if (behavior < BEHAVIOR_MIN_VALUE || behavior > BEHAVIOR_MAX_VALUE) {

+            throw new IllegalArgumentException("Invalid behavior: " + behavior);

+        }

+        mUniqueId = transitionId;

+        mAfterMediaItem = afterMediaItem;

+        mBeforeMediaItem = beforeMediaItem;

+        mDurationMs = durationMs;

+        mBehavior = behavior;

+    }

+

+    /**

+     * @return The of the transition

+     */

+    public String getId() {

+        return mUniqueId;

+    }

+

+    /**

+     * @return The media item at the end of which the transition is applied

+     */

+    public MediaItem getAfterMediaItem() {

+        return mAfterMediaItem;

+    }

+

+    /**

+     * @return The media item at the beginning of which the transition is applied

+     */

+    public MediaItem getBeforeMediaItem() {

+        return mBeforeMediaItem;

+    }

+

+    /**

+     * Set the duration of the transition.

+     *

+     * @param durationMs the duration of the transition in milliseconds

+     */

+    public void setDuration(long durationMs) {

+        if (durationMs > getMaximumDuration()) {

+            throw new IllegalArgumentException("The duration is too large");

+        }

+

+        mDurationMs = durationMs;

+        invalidate();

+    }

+

+    /**

+     * @return the duration of the transition in milliseconds

+     */

+    public long getDuration() {

+        return mDurationMs;

+    }

+

+    /**

+     * The duration of a transition cannot be greater than half of the minimum

+     * duration of the bounding media items.

+     *

+     * @return The maximum duration of this transition

+     */

+    public long getMaximumDuration() {

+        if (mAfterMediaItem == null) {

+            return mBeforeMediaItem.getTimelineDuration() / 2;

+        } else if (mBeforeMediaItem == null) {

+            return mAfterMediaItem.getTimelineDuration() / 2;

+        } else {

+            return (Math.min(mAfterMediaItem.getTimelineDuration(),

+                    mBeforeMediaItem.getTimelineDuration()) / 2);

+        }

+    }

+

+    /**

+     * @return The behavior

+     */

+    public int getBehavior() {

+        return mBehavior;

+    }

+

+    /**

+     * Generate the video clip for the specified transition.

+     * This method may block for a significant amount of time.

+     *

+     * Before the method completes execution it sets the mFilename to

+     * the name of the newly generated transition video clip file.

+     */

+    abstract void generate();

+

+    /**

+     * Remove any resources associated with this transition

+     */

+    void invalidate() {

+        if (mFilename != null) {

+            new File(mFilename).delete();

+            mFilename = null;

+        }

+    }

+

+    /**

+     * @return true if the transition is generated

+     */

+    boolean isGenerated() {

+        return (mFilename != null);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public boolean equals(Object object) {

+        if (!(object instanceof Transition)) {

+            return false;

+        }

+        return mUniqueId.equals(((Transition)object).mUniqueId);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int hashCode() {

+        return mUniqueId.hashCode();

+    }

+}

diff --git a/media/java/android/media/videoeditor/TransitionAlpha.java b/media/java/android/media/videoeditor/TransitionAlpha.java
new file mode 100755
index 0000000..30e66fc
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionAlpha.java
@@ -0,0 +1,114 @@
+/*

+ * Copyright (C) 2010 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES 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.videoeditor;

+

+

+/**

+ * This class allows to render an "alpha blending" transition according to a

+ * bitmap mask. The mask shows the shape of the transition all along the

+ * duration of the transition: just before the transition, video 1 is fully

+ * displayed. When the transition starts, as the time goes on, pixels of video 2

+ * replace pixels of video 1 according to the gray scale pixel value of the

+ * mask.

+ * {@hide}

+ */

+public class TransitionAlpha extends Transition {

+    /** This is the input JPEG file for the mask */

+    private final String mMaskFilename;

+

+    /**

+     * This is percentage (between 0 and 100) of blending between video 1 and

+     * video 2 if this value equals 0, then the mask is strictly applied if this

+     * value equals 100, then the mask is not at all applied (no transition

+     * effect)

+     */

+    private final int mBlendingPercent;

+

+    /**

+     * If true, this value inverts the direction of the mask: white pixels of

+     * the mask show video 2 pixels first black pixels of the mask show video 2

+     * pixels last.

+     */

+    private final boolean mIsInvert;

+

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionAlpha() {

+        this(null, null, null, 0, 0, null, 0, false);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param transitionId The transition id

+     * @param afterMediaItem The transition is applied to the end of this media

+     *            item

+     * @param beforeMediaItem The transition is applied to the beginning of this

+     *            media item

+     * @param durationMs duration of the transition in milliseconds

+     * @param behavior behavior is one of the behavior defined in Transition

+     *            class

+     * @param maskFilename JPEG file name. The dimension of the image

+     *           corresponds to 720p (16:9 aspect ratio). Mask files are

+     *           shared between video editors and can be created in the

+     *           projects folder (the parent folder for all projects).

+     * @param blendingPercent The blending percent applied

+     * @param invert true to invert the direction of the alpha blending

+     * @throws IllegalArgumentException if behavior is not supported, or if

+     *             direction are not supported.

+     */

+    public TransitionAlpha(String transitionId, MediaItem afterMediaItem,

+            MediaItem beforeMediaItem, long durationMs, int behavior, String maskFilename,

+            int blendingPercent, boolean invert) {

+        super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior);

+

+        mMaskFilename = maskFilename;

+        mBlendingPercent = blendingPercent;

+        mIsInvert = invert;

+    }

+

+    /**

+     * @return The blending percentage

+     */

+    public int getBlendingPercent() {

+        return mBlendingPercent;

+    }

+

+    /**

+     * @return The mask filename

+     */

+    public String getMaskFilename() {

+        return mMaskFilename;

+    }

+

+    /**

+     * @return true if the direction of the alpha blending is inverted

+     */

+    public boolean isInvert() {

+        return mIsInvert;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/TransitionCrossfade.java b/media/java/android/media/videoeditor/TransitionCrossfade.java
new file mode 100755
index 0000000..f8223e8
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionCrossfade.java
@@ -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.

+ */

+

+package android.media.videoeditor;

+

+

+/**

+ * This class allows to render a crossfade (dissolve) effect transition between

+ * two videos

+ * {@hide}

+ */

+public class TransitionCrossfade extends Transition {

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionCrossfade() {

+        this(null, null, null, 0, 0);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param transitionId The transition id

+     * @param afterMediaItem The transition is applied to the end of this

+     *      media item

+     * @param beforeMediaItem The transition is applied to the beginning of

+     *      this media item

+     * @param durationMs duration of the transition in milliseconds

+     * @param behavior behavior is one of the behavior defined in Transition

+     *            class

+     *

+     * @throws IllegalArgumentException if behavior is not supported.

+     */

+    public TransitionCrossfade(String transitionId, MediaItem afterMediaItem,

+            MediaItem beforeMediaItem, long durationMs, int behavior) {

+        super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/TransitionEndCurtainClosing.java b/media/java/android/media/videoeditor/TransitionEndCurtainClosing.java
new file mode 100644
index 0000000..b1c6bb5
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionEndCurtainClosing.java
@@ -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.

+ */

+

+package android.media.videoeditor;

+

+/**

+ * This transition fades to black frame using curtain closing: A black image is

+ * moved from top to bottom to cover the video. This transition is always

+ * applied at the end of the movie. {@hide}

+ */

+public class TransitionEndCurtainClosing extends Transition {

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionEndCurtainClosing() {

+        this(null, null, 0, BEHAVIOR_LINEAR);

+    }

+

+    /**

+     * Constructor.

+     *

+     * @param transitionId The transition id

+     * @param afterMediaItem The transition is applied to the end of this

+     *      media item

+     * @param durationMs duration of the transition in milliseconds

+     * @param behavior The transition behavior

+     */

+    public TransitionEndCurtainClosing(String transitionId, MediaItem afterMediaItem,

+            long duration, int behavior) {

+        super(transitionId, afterMediaItem, null, duration, behavior);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/TransitionEndFadeToBlack.java b/media/java/android/media/videoeditor/TransitionEndFadeToBlack.java
new file mode 100755
index 0000000..5f913fc
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionEndFadeToBlack.java
@@ -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.

+ */

+

+package android.media.videoeditor;

+

+/**

+ * This transition fades to black frame using fade out in a certain provided

+ * duration. This transition is always applied at the end of the movie. {@hide

+ * }

+ */

+public class TransitionEndFadeToBlack extends Transition {

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionEndFadeToBlack() {

+        this(null, null, 0, BEHAVIOR_LINEAR);

+    }

+

+    /**

+     * Constructor.

+     *

+     * @param transitionId The transition id

+     * @param afterMediaItem The transition is applied to the end of this

+     *      media item

+     * @param durationMs duration of the transition in milliseconds

+     * @param behavior The transition behavior

+     */

+    public TransitionEndFadeToBlack(String transitionId, MediaItem afterMediaItem, long duration,

+            int behavior) {

+        super(transitionId, afterMediaItem, null, duration, behavior);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/TransitionFadeToBlack.java b/media/java/android/media/videoeditor/TransitionFadeToBlack.java
new file mode 100755
index 0000000..9569a65
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionFadeToBlack.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.media.videoeditor;

+

+

+/**

+ * This class is used to render a fade to black transition between two videos.

+ * {@hide}

+ */

+public class TransitionFadeToBlack extends Transition {

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionFadeToBlack() {

+        this(null, null, null, 0, 0);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param transitionId The transition id

+     * @param afterMediaItem The transition is applied to the end of this

+     *      media item

+     * @param beforeMediaItem The transition is applied to the beginning of

+     *      this media item

+     * @param durationMs duration of the transition

+     * @param behavior behavior is one of the behavior defined in Transition

+     *            class

+     *

+     * @throws IllegalArgumentException if behavior is not supported.

+     */

+    public TransitionFadeToBlack(String transitionId, MediaItem afterMediaItem,

+            MediaItem beforeMediaItem, long durationMs, int behavior) {

+        super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/TransitionSliding.java b/media/java/android/media/videoeditor/TransitionSliding.java
new file mode 100755
index 0000000..cc9f4b2
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionSliding.java
@@ -0,0 +1,82 @@
+/*

+ * Copyright (C) 2010 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+

+package android.media.videoeditor;

+

+/**

+ * This class allows to create sliding transitions

+ * {@hide}

+ */

+public class TransitionSliding extends Transition {

+

+    /** Video 1 is pushed to the right while video 2 is coming from left */

+    public final static int DIRECTION_RIGHT_OUT_LEFT_IN = 0;

+    /** Video 1 is pushed to the left while video 2 is coming from right */

+    public static final int DIRECTION_LEFT_OUT_RIGHT_IN = 1;

+    /** Video 1 is pushed to the top while video 2 is coming from bottom */

+    public static final int DIRECTION_TOP_OUT_BOTTOM_IN = 2;

+    /** Video 1 is pushed to the bottom while video 2 is coming from top */

+    public static final int DIRECTION_BOTTOM_OUT_TOP_IN = 3;

+

+    // The sliding transitions

+    private final int mSlidingDirection;

+

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionSliding() {

+        this(null, null, null, 0, 0, 0);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param transitionId The transition id

+     * @param afterMediaItem The transition is applied to the end of this

+     *      media item

+     * @param beforeMediaItem The transition is applied to the beginning of

+     *      this media item

+     * @param durationMs duration of the transition in milliseconds

+     * @param behavior behavior is one of the behavior defined in Transition

+     *            class

+     * @param direction direction shall be one of the supported directions like

+     *            RIGHT_OUT_LEFT_IN

+     *

+     * @throws IllegalArgumentException if behavior is not supported.

+     */

+    public TransitionSliding(String transitionId, MediaItem afterMediaItem,

+            MediaItem beforeMediaItem, long durationMs, int behavior, int direction) {

+        super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior);

+        mSlidingDirection = direction;

+    }

+

+    /**

+     * @return The sliding direction

+     */

+    public int getDirection() {

+        return mSlidingDirection;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/TransitionStartCurtainOpening.java b/media/java/android/media/videoeditor/TransitionStartCurtainOpening.java
new file mode 100755
index 0000000..b787b32
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionStartCurtainOpening.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.media.videoeditor;

+

+

+/**

+ * This transition fades from black frame using curtain opening. A black

+ * image is displayed and moves from bottom to top making the video visible.

+ * This transition is always applied at the beginning of the movie.

+ * {@hide}

+ */

+public class TransitionStartCurtainOpening extends Transition {

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionStartCurtainOpening() {

+        this(null, null, 0, Transition.BEHAVIOR_LINEAR);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param transitionId The transition id

+     * @param beforeMediaItem The transition is applied to the beginning of

+     *      this media item

+     * @param durationMs The duration of the transition in milliseconds

+     * @param behavior The transition behavior

+     */

+    public TransitionStartCurtainOpening(String transitionId, MediaItem beforeMediaItem,

+            long durationMs, int behavior) {

+        super(transitionId, null, beforeMediaItem, durationMs, behavior);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/TransitionStartFadeFromBlack.java b/media/java/android/media/videoeditor/TransitionStartFadeFromBlack.java
new file mode 100644
index 0000000..be993a5
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionStartFadeFromBlack.java
@@ -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.

+ */

+

+package android.media.videoeditor;

+

+/**

+ * This transition fades from black using fade-in in a certain provided

+ * duration. This transition is always applied at the beginning of the movie.

+ * {@hide}

+ */

+public class TransitionStartFadeFromBlack extends Transition {

+    /**

+     * An object of this type cannot be instantiated by using the default

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionStartFadeFromBlack() {

+        this(null, null, 0, Transition.BEHAVIOR_LINEAR);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param transitionId The transition id

+     * @param beforeMediaItem The transition is applied to the beginning of

+     *      this media item

+     * @param durationMs The duration of the transition in milliseconds

+     * @param behavior The transition behavior

+     */

+    public TransitionStartFadeFromBlack(String transitionId, MediaItem beforeMediaItem,

+            long durationMs, int behavior) {

+        super(transitionId, null, beforeMediaItem, durationMs, behavior);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/VideoEditor.java b/media/java/android/media/videoeditor/VideoEditor.java
new file mode 100755
index 0000000..aa8f2cb
--- /dev/null
+++ b/media/java/android/media/videoeditor/VideoEditor.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.media.videoeditor;

+

+import java.io.IOException;

+import java.util.List;

+import java.util.concurrent.CancellationException;

+

+import android.view.SurfaceHolder;

+

+/**

+ * This is the interface implemented by classes which provide video editing

+ * functionality. The VideoEditor implementation class manages all input and

+ * output files. Unless specifically mentioned, methods are blocking. A

+ * typical editing session may consist of the following sequence of operations:

+ *

+ * <ul>

+ *  <li>Add a set of MediaItems</li>

+ *  <li>Apply a set of Transitions between MediaItems</li>

+ *  <li>Add Effects and Overlays to media items</li>

+ *  <li>Preview the movie at any time</li>

+ *  <li>Save the VideoEditor implementation class internal state</li>

+ *  <li>Release the VideoEditor implementation class instance by invoking

+ * {@link #release()}

+ * </ul>

+ * The internal VideoEditor state consists of the following elements:

+ * <ul>

+ *  <li>Ordered & trimmed MediaItems</li>

+ *  <li>Transition video clips</li>

+ *  <li>Overlays</li>

+ *  <li>Effects</li>

+ *  <li>Audio waveform for the background audio and MediaItems</li>

+ *  <li>Project thumbnail</li>

+ *  <li>Last exported movie.</li>

+ *  <li>Other project specific data such as the current aspect ratio.</li>

+ * </ul>

+ * {@hide}

+ */

+public interface VideoEditor {

+    // The file name of the project thumbnail

+    public static final String THUMBNAIL_FILENAME = "thumbnail.jpg";

+

+    // Use this value instead of the specific end of the storyboard timeline

+    // value.

+    public final static int DURATION_OF_STORYBOARD = -1;

+

+    /**

+     * This listener interface is used by the VideoEditor to emit preview

+     * progress notifications. This callback should be invoked after the

+     * number of frames specified by

+     * {@link #startPreview(SurfaceHolder surfaceHolder, long fromMs,

+     *           int callbackAfterFrameCount, PreviewProgressListener listener)}

+     */

+    public interface PreviewProgressListener {

+        /**

+         * This method notifies the listener of the current time position while

+         * previewing a project.

+         *

+         * @param videoEditor The VideoEditor instance

+         * @param timeMs The current preview position (expressed in milliseconds

+         *            since the beginning of the storyboard timeline).

+         * @param end true if the end of the timeline was reached

+         */

+        public void onProgress(VideoEditor videoEditor, long timeMs, boolean end);

+    }

+

+    /**

+     * This listener interface is used by the VideoEditor to emit export status

+     * notifications.

+     * {@link #export(String filename, ExportProgressListener listener, int height, int bitrate)}

+     */

+    public interface ExportProgressListener {

+        /**

+         * This method notifies the listener of the progress status of a export

+         * operation.

+         *

+         * @param videoEditor The VideoEditor instance

+         * @param filename The name of the file which is in the process of being

+         *            exported.

+         * @param progress The progress in %. At the beginning of the export, this

+         *            value is set to 0; at the end, the value is set to 100.

+         */

+        public void onProgress(VideoEditor videoEditor, String filename, int progress);

+    }

+

+    /**

+     * @return The path where the VideoEditor stores all files related to the

+     * project

+     */

+    public String getPath();

+

+    /**

+     * This method releases all in-memory resources used by the VideoEditor

+     * instance. All pending operations such as preview, export and extract

+     * audio waveform must be canceled.

+     */

+    public void release();

+

+    /**

+     * Persist the current internal state of VideoEditor to the project path.

+     * The VideoEditor state may be restored by invoking the

+     * {@link VideoEditorFactory#load(String)} method. This method does not

+     * release the internal in-memory state of the VideoEditor. To release

+     * the in-memory state of the VideoEditor the {@link #release()} method

+     * must be invoked.

+     *

+     * Pending transition generations must be allowed to complete before the

+     * state is saved.

+     * Pending audio waveform generations must be allowed to complete.

+     * Pending export operations must be allowed to continue.

+     */

+    public void save() throws IOException;

+

+    /**

+     * Create the output movie based on all media items added and the applied

+     * storyboard items. This method can take a long time to execute and is

+     * blocking. The application will receive progress notifications via the

+     * ExportProgressListener. Specific implementations may not support multiple

+     * simultaneous export operations.

+     *

+     * Note that invoking methods which would change the contents of the output

+     * movie throw an IllegalStateException while an export operation is

+     * pending.

+     *

+     * @param filename The output file name (including the full path)

+     * @param height The height of the output video file. The supported values

+     *            for height are described in the MediaProperties class, for

+     *            example: HEIGHT_480. The width will be automatically

+     *            computed according to the aspect ratio provided by

+     *            {@link #setAspectRatio(int)}

+     * @param bitrate The bitrate of the output video file. This is approximate

+     *            value for the output movie. Supported bitrate values are

+     *            described in the MediaProperties class for example:

+     *            BITRATE_384K

+     * @param listener The listener for progress notifications. Use null if

+     *            export progress notifications are not needed.

+     *

+     * @throws IllegalArgumentException if height or bitrate are not supported.

+     * @throws IOException if output file cannot be created

+     * @throws IllegalStateException if a preview or an export is in progress or

+     *             if no MediaItem has been added

+     * @throws CancellationException if export is canceled by calling

+     *             {@link #cancelExport()}

+     * @throws UnsupportOperationException if multiple simultaneous export()

+     *  are not allowed

+     */

+    public void export(String filename, int height, int bitrate, ExportProgressListener listener)

+            throws IOException;

+

+    /**

+     * Cancel the running export operation. This method blocks until the

+     * export is canceled and the exported file (if any) is deleted. If the

+     * export completed by the time this method is invoked, the export file

+     * will be deleted.

+     *

+     * @param filename The filename which identifies the export operation to be

+     *            canceled.

+     **/

+    public void cancelExport(String filename);

+

+    /**

+     * Add a media item at the end of the storyboard.

+     *

+     * @param mediaItem The media item object to add

+     * @throws IllegalStateException if a preview or an export is in progress or

+     *             if the media item id is not unique across all the media items

+     *             added.

+     */

+    public void addMediaItem(MediaItem mediaItem);

+

+    /**

+     * Insert a media item after the media item with the specified id.

+     *

+     * @param mediaItem The media item object to insert

+     * @param afterMediaItemId Insert the mediaItem after the media item

+     *            identified by this id. If this parameter is null, the media

+     *            item is inserted at the beginning of the timeline.

+     *

+     * @throws IllegalStateException if a preview or an export is in progress

+     * @throws IllegalArgumentException if media item with the specified id does

+     *             not exist (null is a valid value) or if the media item id is

+     *             not unique across all the media items added.

+     */

+    public void insertMediaItem(MediaItem mediaItem, String afterMediaItemId);

+

+    /**

+     * Move a media item after the media item with the specified id.

+     *

+     * Note: The project thumbnail is regenerated if the media item is or

+     * becomes the first media item in the storyboard timeline.

+     *

+     * @param mediaItemId The id of the media item to move

+     * @param afterMediaItemId Move the media item identified by mediaItemId after

+     *          the media item identified by this parameter. If this parameter

+     *          is null, the media item is moved at the beginning of the

+     *          timeline.

+     *

+     * @throws IllegalStateException if a preview or an export is in progress

+     * @throws IllegalArgumentException if one of media item ids is invalid

+     *          (null is a valid value)

+     */

+    public void moveMediaItem(String mediaItemId, String afterMediaItemId);

+

+    /**

+     * Remove the media item with the specified id. If there are transitions

+     * before or after this media item, then this/these transition(s) are

+     * removed from the storyboard. If the extraction of the audio waveform is

+     * in progress, the extraction is canceled and the file is deleted.

+     *

+     * Effects and overlays associated with the media item will also be

+     * removed.

+     *

+     * Note: The project thumbnail is regenerated if the media item which

+     * is removed is the first media item in the storyboard or if the

+     * media item is the only one in the storyboard. If the

+     * media item is the only one in the storyboard, the project thumbnail

+     * will be set to a black frame and the aspect ratio will revert to the

+     * default aspect ratio, and this method is equivalent to

+     * removeAllMediaItems() in this case.

+     *

+     * @param mediaItemId The unique id of the media item to be removed

+     *

+     * @return The media item that was removed

+     *

+     * @throws IllegalStateException if a preview or an export is in progress

+     * @throws IllegalArgumentException if media item with the specified id

+     *          does not exist

+     */

+    public MediaItem removeMediaItem(String mediaItemId);

+

+    /**

+     * Remove all media items in the storyboard. All effects, overlays and all

+     * transitions are also removed.

+     *

+     * Note: The project thumbnail will be set to a black frame and the aspect

+     * ratio will revert to the default aspect ratio.

+     *

+     * @throws IllegalStateException if a preview or an export is in progress

+     */

+    public void removeAllMediaItems();

+

+    /**

+     * Get the list of media items in the order in which it they appear in the

+     * storyboard timeline.

+     *

+     * Note that if any media item source files are no longer

+     * accessible, this method will still provide the full list of media items.

+     *

+     * @return The list of media items. If no media item exist an empty list

+     *          will be returned.

+     */

+    public List<MediaItem> getAllMediaItems();

+

+    /**

+     * Find the media item with the specified id

+     *

+     * @param mediaItemId The media item id

+     *

+     * @return The media item with the specified id (null if it does not exist)

+     */

+    public MediaItem getMediaItem(String mediaItemId);

+

+    /**

+     * Add a transition between the media items specified by the transition.

+     * If a transition existed at the same position it is invalidated and then

+     * the transition is replaced. Note that the new transition video clip is

+     * not automatically generated by this method. The

+     * {@link Transition#generate()} method must be invoked to generate

+     * the transition video clip.

+     *

+     * Note that the TransitionAtEnd and TransitionAtStart are special kinds

+     * that can not be applied between two media items.

+     *

+     * A crossfade audio transition will be automatically applied regardless of

+     * the video transition.

+     *

+     * @param transition The transition to apply

+     *

+     * @throws IllegalStateException if a preview or an export is in progress

+     * @throws IllegalArgumentException if the transition duration is larger

+     *              than the smallest duration of the two media item files or

+     *              if the two media items specified in the transition are not

+     *              adjacent

+     */

+    public void addTransition(Transition transition);

+

+    /**

+     * Remove the transition with the specified id.

+     *

+     * @param transitionId The id of the transition to be removed

+     *

+     * @return The transition that was removed

+     * @throws IllegalStateException if a preview or an export is in progress

+     * @throws IllegalArgumentException if transition with the specified id does

+     *             not exist

+     */

+    public Transition removeTransition(String transitionId);

+

+    /**

+     * Get the list of transitions

+     *

+     * @return The list of transitions. If no transitions exist an empty list

+     *  will be returned.

+     */

+    public List<Transition> getAllTransitions();

+

+    /**

+     * Find the transition with the specified transition id.

+     *

+     * @param transitionId The transition id

+     *

+     * @return The transition

+     */

+    public Transition getTransition(String transitionId);

+

+    /**

+     * Add the specified AudioTrack to the storyboard. Note: Specific

+     * implementations may support a limited number of audio tracks (e.g. only

+     * one audio track)

+     *

+     * @param audioTrack The AudioTrack to add

+     * @throws UnsupportedOperationException if the implementation supports a

+     *             limited number of audio tracks.

+     * @throws IllegalArgumentException if media item is not unique across all

+     *             the audio tracks already added.

+     */

+    public void addAudioTrack(AudioTrack audioTrack);

+

+    /**

+     * Insert an audio track after the audio track with the specified id. Use

+     * addAudioTrack to add an audio track at the end of the storyboard

+     * timeline.

+     *

+     * @param audioTrack The audio track object to insert

+     * @param afterAudioTrackId Insert the audio track after the audio track

+     *            identified by this parameter. If this parameter is null the

+     *            audio track is added at the beginning of the timeline.

+     * @throws IllegalStateException if a preview or an export is in progress

+     * @throws IllegalArgumentException if media item with the specified id does

+     *             not exist (null is a valid value). if media item is not

+     *             unique across all the audio tracks already added.

+     * @throws UnsupportedOperationException if the implementation supports a

+     *             limited number of audio tracks

+     */

+    public void insertAudioTrack(AudioTrack audioTrack, String afterAudioTrackId);

+

+    /**

+     * Move an AudioTrack after the AudioTrack with the specified id.

+     *

+     * @param audioTrackId The id of the AudioTrack to move

+     * @param afterAudioTrackId Move the AudioTrack identified by audioTrackId

+     *            after the AudioTrack identified by this parameter. If this

+     *            parameter is null the audio track is added at the beginning of

+     *            the timeline.

+     * @throws IllegalStateException if a preview or an export is in progress

+     * @throws IllegalArgumentException if one of media item ids is invalid

+     *             (null is a valid value)

+     */

+    public void moveAudioTrack(String audioTrackId, String afterAudioTrackId);

+

+    /**

+     * Remove the audio track with the specified id. If the extraction of the

+     * audio waveform is in progress, the extraction is canceled and the file is

+     * deleted.

+     *

+     * @param audioTrackId The id of the audio track to be removed

+     *

+     * @return The audio track that was removed

+     * @throws IllegalStateException if a preview or an export is in progress

+     */

+    public AudioTrack removeAudioTrack(String audioTrackId);

+

+    /**

+     * Get the list of AudioTracks in order in which they appear in the storyboard.

+     *

+     * Note that if any AudioTrack source files are not accessible anymore,

+     * this method will still provide the full list of audio tracks.

+     *

+     * @return The list of AudioTracks. If no audio tracks exist an empty list

+     *  will be returned.

+     */

+    public List<AudioTrack> getAllAudioTracks();

+

+    /**

+     * Find the AudioTrack with the specified id

+     *

+     * @param audioTrackId The AudioTrack id

+     *

+     * @return The AudioTrack with the specified id (null if it does not exist)

+     */

+    public AudioTrack getAudioTrack(String audioTrackId);

+

+    /**

+     * Set the aspect ratio used in the preview and the export movie.

+     *

+     * The default aspect ratio is ASPECTRATIO_16_9 (16:9).

+     *

+     * @param aspectRatio to apply. If aspectRatio is the same as the current

+     *            aspect ratio, then this function just returns. The supported

+     *            aspect ratio are defined in the MediaProperties class for

+     *            example: ASPECTRATIO_16_9

+     *

+     * @throws IllegalStateException if a preview or an export is in progress

+     * @throws IllegalArgumentException if aspect ratio is not supported

+     */

+    public void setAspectRatio(int aspectRatio);

+

+    /**

+     * Get current aspect ratio.

+     *

+     * @return The aspect ratio as described in MediaProperties

+     */

+    public int getAspectRatio();

+

+    /**

+     * Get the preview (and output movie) duration.

+     *

+     * @return The duration of the preview (and output movie)

+     */

+    public long getDuration();

+

+    /**

+     * Render a frame according to the preview aspect ratio and activating all

+     * storyboard items relative to the specified time.

+     *

+     * @param surfaceHolder SurfaceHolder used by the application

+     * @param timeMs time corresponding to the frame to display

+     *

+     * @return The accurate time stamp of the frame that is rendered

+     * .

+     * @throws IllegalStateException if a preview or an export is already

+     *             in progress

+     * @throws IllegalArgumentException if time is negative or beyond the

+     *             preview duration

+     */

+    public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs);

+

+    /**

+     * This method must be called after the aspect ratio of the project changes

+     * and before startPreview is called. Note that this method may block for

+     * an extensive period of time.

+     */

+    public void generatePreview();

+

+    /**

+     * Start the preview of all the storyboard items applied on all MediaItems

+     * This method does not block (does not wait for the preview to complete).

+     * The PreviewProgressListener allows to track the progress at the time

+     * interval determined by the callbackAfterFrameCount parameter. The

+     * SurfaceHolder has to be created and ready for use before calling this

+     * method. The method is a no-op if there are no MediaItems in the

+     * storyboard.

+     *

+     * @param surfaceHolder SurfaceHolder where the preview is rendered.

+     * @param fromMs The time (relative to the timeline) at which the preview

+     *            will start

+     * @param toMs The time (relative to the timeline) at which the preview will

+     *            stop. Use -1 to play to the end of the timeline

+     * @param loop true if the preview should be looped once it reaches the end

+     * @param callbackAfterFrameCount The listener interface should be invoked

+     *            after the number of frames specified by this parameter.

+     * @param listener The listener which will be notified of the preview

+     *            progress

+     * @throws IllegalArgumentException if fromMs is beyond the preview duration

+     * @throws IllegalStateException if a preview or an export is already in

+     *             progress

+     */

+    public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop,

+            int callbackAfterFrameCount, PreviewProgressListener listener);

+

+    /**

+     * Stop the current preview. This method blocks until ongoing preview is

+     * stopped. Ignored if there is no preview running.

+     *

+     * @return The accurate current time when stop is effective expressed in

+     *          milliseconds

+     */

+    public long stopPreview();

+}

diff --git a/media/java/android/media/videoeditor/VideoEditorFactory.java b/media/java/android/media/videoeditor/VideoEditorFactory.java
new file mode 100755
index 0000000..0a377e2
--- /dev/null
+++ b/media/java/android/media/videoeditor/VideoEditorFactory.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.videoeditor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * The VideoEditorFactory class must be used to instantiate VideoEditor objects
+ * by creating a new project {@link #create(String)} or by loading an
+ * existing project {@link #load(String)}.
+ * {@hide}
+ */
+public class VideoEditorFactory {
+    // VideoEditor implementation classes
+    public static final String TEST_CLASS_IMPLEMENTATION
+            = "android.media.videoeditor.VideoEditorTestImpl";
+    public static final String DEFAULT_CLASS_IMPLEMENTATION
+            = "android.media.videoeditor.VideoEditorImpl";
+
+    /**
+     * This is the factory method for creating a new VideoEditor instance.
+     *
+     * @param projectPath The path where all VideoEditor internal
+     *            files are stored. When a project is deleted the application is
+     *            responsible for deleting the path and its contents.
+     * @param className The implementation class name
+     *
+     * @return The VideoEditor instance
+     *
+     * @throws IOException if path does not exist or if the path can
+     *             not be accessed in read/write mode
+     * @throws IllegalStateException if a previous VideoEditor instance has not
+     *             been released
+     * @throws ClassNotFoundException, NoSuchMethodException,
+     *             InvocationTargetException, IllegalAccessException,
+     *             InstantiationException if the implementation class cannot
+     *             be instantiated.
+     */
+    public static VideoEditor create(String projectPath, String className) throws IOException,
+            ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
+            IllegalAccessException, InstantiationException {
+        // If the project path does not exist create it
+        final File dir = new File(projectPath);
+        if (!dir.exists()) {
+            if (!dir.mkdirs()) {
+                throw new FileNotFoundException("Cannot create project path: " + projectPath);
+            }
+        }
+
+        final Class<?> cls = Class.forName(className);
+        final Class<?> partypes[] = new Class[1];
+        partypes[0] = String.class;
+        final Constructor<?> ct = cls.getConstructor(partypes);
+        final Object arglist[] = new Object[1];
+        arglist[0] = projectPath;
+
+        return (VideoEditor)ct.newInstance(arglist);
+    }
+
+    /**
+     * This is the factory method for instantiating a VideoEditor from the
+     * internal state previously saved with the
+     * {@link VideoEditor#save(String)} method.
+     *
+     * @param projectPath The path where all VideoEditor internal files
+     *            are stored. When a project is deleted the application is
+     *            responsible for deleting the path and its contents.
+     * @param className The implementation class name
+     * @param generatePreview if set to true the
+     *      {@link MediaEditor#generatePreview()} will be called internally to
+     *      generate any needed transitions.
+     *
+     * @return The VideoEditor instance
+     *
+     * @throws IOException if path does not exist or if the path can
+     *             not be accessed in read/write mode or if one of the resource
+     *             media files cannot be retrieved
+     * @throws IllegalStateException if a previous VideoEditor instance has not
+     *             been released
+     */
+    public static VideoEditor load(String projectPath, String className, boolean generatePreview)
+            throws IOException, ClassNotFoundException, NoSuchMethodException,
+            InvocationTargetException, IllegalAccessException, InstantiationException {
+        final Class<?> cls = Class.forName(className);
+        final Class<?> partypes[] = new Class[1];
+        partypes[0] = String.class;
+        final Constructor<?> ct = cls.getConstructor(partypes);
+        final Object arglist[] = new Object[1];
+        arglist[0] = projectPath;
+
+        final VideoEditor videoEditor = (VideoEditor)ct.newInstance(arglist);
+        if (generatePreview) {
+            videoEditor.generatePreview();
+        }
+        return videoEditor;
+    }
+}
diff --git a/media/java/android/media/videoeditor/VideoEditorTestImpl.java b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
new file mode 100644
index 0000000..c3cb82a
--- /dev/null
+++ b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
@@ -0,0 +1,1202 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.videoeditor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.Xml;
+import android.view.SurfaceHolder;
+
+/**
+ * The VideoEditor implementation {@hide}
+ */
+public class VideoEditorTestImpl implements VideoEditor {
+    // Logging
+    private static final String TAG = "VideoEditorImpl";
+
+    // The project filename
+    private static final String PROJECT_FILENAME = "videoeditor.xml";
+
+    // XML tags
+    private static final String TAG_PROJECT = "project";
+    private static final String TAG_MEDIA_ITEMS = "media_items";
+    private static final String TAG_MEDIA_ITEM = "media_item";
+    private static final String TAG_TRANSITIONS = "transitions";
+    private static final String TAG_TRANSITION = "transition";
+    private static final String TAG_OVERLAYS = "overlays";
+    private static final String TAG_OVERLAY = "overlay";
+    private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes";
+    private static final String TAG_EFFECTS = "effects";
+    private static final String TAG_EFFECT = "effect";
+    private static final String TAG_AUDIO_TRACKS = "audio_tracks";
+    private static final String TAG_AUDIO_TRACK = "audio_track";
+
+    private static final String ATTR_ID = "id";
+    private static final String ATTR_FILENAME = "filename";
+    private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "wavefoem";
+    private static final String ATTR_RENDERING_MODE = "rendering_mode";
+    private static final String ATTR_ASPECT_RATIO = "aspect_ratio";
+    private static final String ATTR_TYPE = "type";
+    private static final String ATTR_DURATION = "duration";
+    private static final String ATTR_START_TIME = "start_time";
+    private static final String ATTR_BEGIN_TIME = "begin_time";
+    private static final String ATTR_END_TIME = "end_time";
+    private static final String ATTR_VOLUME = "volume";
+    private static final String ATTR_BEHAVIOR = "behavior";
+    private static final String ATTR_DIRECTION = "direction";
+    private static final String ATTR_BLENDING = "blending";
+    private static final String ATTR_INVERT = "invert";
+    private static final String ATTR_MASK = "mask";
+    private static final String ATTR_BEFORE_MEDIA_ITEM_ID = "before_media_item";
+    private static final String ATTR_AFTER_MEDIA_ITEM_ID = "after_media_item";
+    private static final String ATTR_COLOR_EFFECT_TYPE = "color_type";
+    private static final String ATTR_COLOR_EFFECT_VALUE = "color_value";
+    private static final String ATTR_START_RECT_L = "start_l";
+    private static final String ATTR_START_RECT_T = "start_t";
+    private static final String ATTR_START_RECT_R = "start_r";
+    private static final String ATTR_START_RECT_B = "start_b";
+    private static final String ATTR_END_RECT_L = "end_l";
+    private static final String ATTR_END_RECT_T = "end_t";
+    private static final String ATTR_END_RECT_R = "end_r";
+    private static final String ATTR_END_RECT_B = "end_b";
+    private static final String ATTR_LOOP = "loop";
+    private static final String ATTR_MUTED = "muted";
+
+    // Instance variables
+    private long mDurationMs;
+    private final String mProjectPath;
+    private final List<MediaItem> mMediaItems = new ArrayList<MediaItem>();
+    private final List<AudioTrack> mAudioTracks = new ArrayList<AudioTrack>();
+    private final List<Transition> mTransitions = new ArrayList<Transition>();
+    private PreviewThread mPreviewThread;
+    private int mAspectRatio;
+
+    /**
+     * The preview thread
+     */
+    private class PreviewThread extends Thread {
+        // Instance variables
+        private final static long FRAME_DURATION = 33;
+
+        // Instance variables
+        private final PreviewProgressListener mListener;
+        private final int mCallbackAfterFrameCount;
+        private final long mFromMs, mToMs;
+        private boolean mRun, mLoop;
+        private long mPositionMs;
+
+        /**
+         * Constructor
+         *
+         * @param fromMs Start preview at this position
+         * @param toMs The time (relative to the timeline) at which the preview
+         *            will stop. Use -1 to play to the end of the timeline
+         * @param callbackAfterFrameCount The listener interface should be
+         *            invoked after the number of frames specified by this
+         *            parameter.
+         * @param loop true if the preview should be looped once it reaches the
+         *            end
+         * @param listener The listener
+         */
+        public PreviewThread(long fromMs, long toMs, boolean loop, int callbackAfterFrameCount,
+                PreviewProgressListener listener) {
+            mPositionMs = mFromMs = fromMs;
+            if (toMs < 0) {
+                mToMs = mDurationMs;
+            } else {
+                mToMs = toMs;
+            }
+            mLoop = loop;
+            mCallbackAfterFrameCount = callbackAfterFrameCount;
+            mListener = listener;
+            mRun = true;
+        }
+
+        /*
+         * {@inheritDoc}
+         */
+        @Override
+        public void run() {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "===> PreviewThread.run enter");
+            }
+            int frameCount = 0;
+            while (mRun) {
+                try {
+                    sleep(FRAME_DURATION);
+                } catch (InterruptedException ex) {
+                    break;
+                }
+                frameCount++;
+                mPositionMs += FRAME_DURATION;
+
+                if (mPositionMs >= mToMs) {
+                    if (!mLoop) {
+                        if (mListener != null) {
+                            mListener.onProgress(VideoEditorTestImpl.this, mPositionMs, true);
+                        }
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "PreviewThread.run playback complete");
+                        }
+                        break;
+                    } else {
+                        // Fire a notification for the end of the clip
+                        if (mListener != null) {
+                            mListener.onProgress(VideoEditorTestImpl.this, mToMs, false);
+                        }
+
+                        // Rewind
+                        mPositionMs = mFromMs;
+                        if (mListener != null) {
+                            mListener.onProgress(VideoEditorTestImpl.this, mPositionMs, false);
+                        }
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "PreviewThread.run playback complete");
+                        }
+                        frameCount = 0;
+                    }
+                } else {
+                    if (frameCount == mCallbackAfterFrameCount) {
+                        if (mListener != null) {
+                            mListener.onProgress(VideoEditorTestImpl.this, mPositionMs, false);
+                        }
+                        frameCount = 0;
+                    }
+                }
+            }
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "===> PreviewThread.run exit");
+            }
+        }
+
+        /**
+         * Stop the preview
+         *
+         * @return The stop position
+         */
+        public long stopPreview() {
+            mRun = false;
+            try {
+                join();
+            } catch (InterruptedException ex) {
+            }
+            return mPositionMs;
+        }
+    };
+
+    /**
+     * Constructor
+     *
+     * @param projectPath
+     */
+    public VideoEditorTestImpl(String projectPath) throws IOException {
+        mProjectPath = projectPath;
+        final File projectXml = new File(projectPath, PROJECT_FILENAME);
+        if (projectXml.exists()) {
+            try {
+                load();
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        } else {
+            mAspectRatio = MediaProperties.ASPECT_RATIO_16_9;
+            mDurationMs = 0;
+        }
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public String getPath() {
+        return mProjectPath;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized void addMediaItem(MediaItem mediaItem) {
+        if (mPreviewThread != null) {
+            throw new IllegalStateException("Previewing is in progress");
+        }
+
+        if (mMediaItems.contains(mediaItem)) {
+            throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
+        }
+
+        // Invalidate the end transition if necessary
+        final int mediaItemsCount = mMediaItems.size();
+        if ( mediaItemsCount > 0) {
+            removeTransitionAfter(mediaItemsCount - 1);
+        }
+
+        // Add the new media item
+        mMediaItems.add(mediaItem);
+
+        computeTimelineDuration();
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized void insertMediaItem(MediaItem mediaItem, String afterMediaItemId) {
+        if (mPreviewThread != null) {
+            throw new IllegalStateException("Previewing is in progress");
+        }
+
+        if (mMediaItems.contains(mediaItem)) {
+            throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
+        }
+
+        if (afterMediaItemId == null) {
+            if (mMediaItems.size() > 0) {
+                // Invalidate the transition at the beginning of the timeline
+                removeTransitionBefore(0);
+            }
+            mMediaItems.add(0, mediaItem);
+            computeTimelineDuration();
+        } else {
+            final int mediaItemCount = mMediaItems.size();
+            for (int i = 0; i < mediaItemCount; i++) {
+                final MediaItem mi = mMediaItems.get(i);
+                if (mi.getId().equals(afterMediaItemId)) {
+                    // Invalidate the transition at this position
+                    removeTransitionAfter(i);
+                    // Insert the new media item
+                    mMediaItems.add(i + 1, mediaItem);
+                    computeTimelineDuration();
+                    return;
+                }
+            }
+            throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
+        }
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized void moveMediaItem(String mediaItemId, String afterMediaItemId) {
+        if (mPreviewThread != null) {
+            throw new IllegalStateException("Previewing is in progress");
+        }
+
+        final MediaItem moveMediaItem = removeMediaItem(mediaItemId);
+        if (moveMediaItem == null) {
+            throw new IllegalArgumentException("Target MediaItem not found: " + mediaItemId);
+        }
+
+        if (afterMediaItemId == null) {
+            if (mMediaItems.size() > 0) {
+                // Invalidate adjacent transitions at the insertion point
+                removeTransitionBefore(0);
+
+                // Insert the media item at the new position
+                mMediaItems.add(0, moveMediaItem);
+                computeTimelineDuration();
+            } else {
+                throw new IllegalStateException("Cannot move media item (it is the only item)");
+            }
+        } else {
+            final int mediaItemCount = mMediaItems.size();
+            for (int i = 0; i < mediaItemCount; i++) {
+                final MediaItem mi = mMediaItems.get(i);
+                if (mi.getId().equals(afterMediaItemId)) {
+                    // Invalidate adjacent transitions at the insertion point
+                    removeTransitionAfter(i);
+                    // Insert the media item at the new position
+                    mMediaItems.add(i + 1, moveMediaItem);
+                    computeTimelineDuration();
+                    return;
+                }
+            }
+
+            throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
+        }
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized MediaItem removeMediaItem(String mediaItemId) {
+        if (mPreviewThread != null) {
+            throw new IllegalStateException("Previewing is in progress");
+        }
+
+        final MediaItem mediaItem = getMediaItem(mediaItemId);
+        if (mediaItem != null) {
+            // Remove the media item
+            mMediaItems.remove(mediaItem);
+            // Remove the adjacent transitions
+            removeAdjacentTransitions(mediaItem);
+            computeTimelineDuration();
+        }
+
+        return mediaItem;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized MediaItem getMediaItem(String mediaItemId) {
+        for (MediaItem mediaItem : mMediaItems) {
+            if (mediaItem.getId().equals(mediaItemId)) {
+                return mediaItem;
+            }
+        }
+
+        return null;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized List<MediaItem> getAllMediaItems() {
+        return mMediaItems;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized void removeAllMediaItems() {
+        mMediaItems.clear();
+
+        // Invalidate all transitions
+        for (Transition transition : mTransitions) {
+            transition.invalidate();
+        }
+        mTransitions.clear();
+
+        mDurationMs = 0;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized void addTransition(Transition transition) {
+        mTransitions.add(transition);
+
+        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
+        final MediaItem afterMediaItem = transition.getAfterMediaItem();
+
+        // Cross reference the transitions
+        if (afterMediaItem != null) {
+            // If a transition already exists at the specified position then
+            // invalidate it.
+            if (afterMediaItem.getEndTransition() != null) {
+                afterMediaItem.getEndTransition().invalidate();
+            }
+            afterMediaItem.setEndTransition(transition);
+        }
+
+        if (beforeMediaItem != null) {
+            // If a transition already exists at the specified position then
+            // invalidate it.
+            if (beforeMediaItem.getBeginTransition() != null) {
+                beforeMediaItem.getBeginTransition().invalidate();
+            }
+            beforeMediaItem.setBeginTransition(transition);
+        }
+
+        computeTimelineDuration();
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized Transition removeTransition(String transitionId) {
+        if (mPreviewThread != null) {
+            throw new IllegalStateException("Previewing is in progress");
+        }
+
+        final Transition transition = getTransition(transitionId);
+        if (transition == null) {
+            throw new IllegalStateException("Transition not found: " + transitionId);
+        }
+
+        // Remove the transition references
+        final MediaItem afterMediaItem = transition.getAfterMediaItem();
+        if (afterMediaItem != null) {
+            afterMediaItem.setEndTransition(null);
+        }
+
+        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
+        if (beforeMediaItem != null) {
+            beforeMediaItem.setBeginTransition(null);
+        }
+
+        mTransitions.remove(transition);
+        transition.invalidate();
+        computeTimelineDuration();
+
+        return transition;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public List<Transition> getAllTransitions() {
+        return mTransitions;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public Transition getTransition(String transitionId) {
+        for (Transition transition : mTransitions) {
+            if (transition.getId().equals(transitionId)) {
+                return transition;
+            }
+        }
+
+        return null;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized void addAudioTrack(AudioTrack audioTrack) {
+        if (mPreviewThread != null) {
+            throw new IllegalStateException("Previewing is in progress");
+        }
+
+        mAudioTracks.add(audioTrack);
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized void insertAudioTrack(AudioTrack audioTrack, String afterAudioTrackId) {
+        if (mPreviewThread != null) {
+            throw new IllegalStateException("Previewing is in progress");
+        }
+
+        if (afterAudioTrackId == null) {
+            mAudioTracks.add(0, audioTrack);
+        } else {
+            final int audioTrackCount = mAudioTracks.size();
+            for (int i = 0; i < audioTrackCount; i++) {
+                AudioTrack at = mAudioTracks.get(i);
+                if (at.getId().equals(afterAudioTrackId)) {
+                    mAudioTracks.add(i + 1, audioTrack);
+                    return;
+                }
+            }
+
+            throw new IllegalArgumentException("AudioTrack not found: " + afterAudioTrackId);
+        }
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) {
+        throw new IllegalStateException("Not supported");
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized AudioTrack removeAudioTrack(String audioTrackId) {
+        if (mPreviewThread != null) {
+            throw new IllegalStateException("Previewing is in progress");
+        }
+
+        final AudioTrack audioTrack = getAudioTrack(audioTrackId);
+        if (audioTrack != null) {
+            mAudioTracks.remove(audioTrack);
+        }
+
+        return audioTrack;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public AudioTrack getAudioTrack(String audioTrackId) {
+        for (AudioTrack at : mAudioTracks) {
+            if (at.getId().equals(audioTrackId)) {
+                return at;
+            }
+        }
+
+        return null;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public List<AudioTrack> getAllAudioTracks() {
+        return mAudioTracks;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public void save() throws IOException {
+        final XmlSerializer serializer = Xml.newSerializer();
+        final StringWriter writer = new StringWriter();
+        serializer.setOutput(writer);
+        serializer.startDocument("UTF-8", true);
+        serializer.startTag("", TAG_PROJECT);
+        serializer.attribute("", ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio));
+
+        serializer.startTag("", TAG_MEDIA_ITEMS);
+        for (MediaItem mediaItem : mMediaItems) {
+            serializer.startTag("", TAG_MEDIA_ITEM);
+            serializer.attribute("", ATTR_ID, mediaItem.getId());
+            serializer.attribute("", ATTR_TYPE, mediaItem.getClass().getSimpleName());
+            serializer.attribute("", ATTR_FILENAME, mediaItem.getFilename());
+            serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString(
+                    mediaItem.getRenderingMode()));
+            if (mediaItem instanceof MediaVideoItem) {
+                final MediaVideoItem mvi = (MediaVideoItem)mediaItem;
+                serializer
+                        .attribute("", ATTR_BEGIN_TIME, Long.toString(mvi.getBoundaryBeginTime()));
+                serializer.attribute("", ATTR_END_TIME, Long.toString(mvi.getBoundaryEndTime()));
+                serializer.attribute("", ATTR_VOLUME, Integer.toString(mvi.getVolume()));
+                serializer.attribute("", ATTR_MUTED, Boolean.toString(mvi.isMuted()));
+                if (mvi.getAudioWaveformFilename() != null) {
+                    serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
+                            mvi.getAudioWaveformFilename());
+                }
+            } else if (mediaItem instanceof MediaImageItem) {
+                serializer.attribute("", ATTR_DURATION,
+                        Long.toString(mediaItem.getTimelineDuration()));
+            }
+
+            final List<Overlay> overlays = mediaItem.getAllOverlays();
+            if (overlays.size() > 0) {
+                serializer.startTag("", TAG_OVERLAYS);
+                for (Overlay overlay : overlays) {
+                    serializer.startTag("", TAG_OVERLAY);
+                    serializer.attribute("", ATTR_ID, overlay.getId());
+                    serializer.attribute("", ATTR_TYPE, overlay.getClass().getSimpleName());
+                    serializer.attribute("", ATTR_BEGIN_TIME,
+                            Long.toString(overlay.getStartTime()));
+                    serializer.attribute("", ATTR_DURATION, Long.toString(overlay.getDuration()));
+                    if (overlay instanceof OverlayFrame) {
+                        final OverlayFrame overlayFrame = (OverlayFrame)overlay;
+                        overlayFrame.save(getPath());
+                        if (overlayFrame.getFilename() != null) {
+                            serializer.attribute("", ATTR_FILENAME, overlayFrame.getFilename());
+                        }
+                    }
+
+                    // Save the user attributes
+                    serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES);
+                    final Map<String, String> userAttributes = overlay.getUserAttributes();
+                    for (String name : userAttributes.keySet()) {
+                        final String value = userAttributes.get(name);
+                        if (value != null) {
+                            serializer.attribute("", name, value);
+                        }
+                    }
+                    serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES);
+
+                    serializer.endTag("", TAG_OVERLAY);
+                }
+                serializer.endTag("", TAG_OVERLAYS);
+            }
+
+            final List<Effect> effects = mediaItem.getAllEffects();
+            if (effects.size() > 0) {
+                serializer.startTag("", TAG_EFFECTS);
+                for (Effect effect : effects) {
+                    serializer.startTag("", TAG_EFFECT);
+                    serializer.attribute("", ATTR_ID, effect.getId());
+                    serializer.attribute("", ATTR_TYPE, effect.getClass().getSimpleName());
+                    serializer.attribute("", ATTR_BEGIN_TIME,
+                            Long.toString(effect.getStartTime()));
+                    serializer.attribute("", ATTR_DURATION, Long.toString(effect.getDuration()));
+                    if (effect instanceof EffectColor) {
+                        final EffectColor colorEffect = (EffectColor)effect;
+                        serializer.attribute("", ATTR_COLOR_EFFECT_TYPE,
+                                Integer.toString(colorEffect.getType()));
+                        if (colorEffect.getType() == EffectColor.TYPE_COLOR) {
+                            serializer.attribute("", ATTR_COLOR_EFFECT_VALUE,
+                                    Integer.toString(colorEffect.getParam()));
+                        }
+                    } else if (effect instanceof EffectKenBurns) {
+                        final Rect startRect = ((EffectKenBurns)effect).getStartRect();
+                        serializer.attribute("", ATTR_START_RECT_L,
+                                Integer.toString(startRect.left));
+                        serializer.attribute("", ATTR_START_RECT_T,
+                                Integer.toString(startRect.top));
+                        serializer.attribute("", ATTR_START_RECT_R,
+                                Integer.toString(startRect.right));
+                        serializer.attribute("", ATTR_START_RECT_B,
+                                Integer.toString(startRect.bottom));
+
+                        final Rect endRect = ((EffectKenBurns)effect).getEndRect();
+                        serializer.attribute("", ATTR_END_RECT_L, Integer.toString(endRect.left));
+                        serializer.attribute("", ATTR_END_RECT_T, Integer.toString(endRect.top));
+                        serializer.attribute("", ATTR_END_RECT_R, Integer.toString(endRect.right));
+                        serializer.attribute("", ATTR_END_RECT_B,
+                                Integer.toString(endRect.bottom));
+                    }
+
+                    serializer.endTag("", TAG_EFFECT);
+                }
+                serializer.endTag("", TAG_EFFECTS);
+            }
+
+            serializer.endTag("", TAG_MEDIA_ITEM);
+        }
+        serializer.endTag("", TAG_MEDIA_ITEMS);
+
+        serializer.startTag("", TAG_TRANSITIONS);
+
+        for (Transition transition : mTransitions) {
+            serializer.startTag("", TAG_TRANSITION);
+            serializer.attribute("", ATTR_ID, transition.getId());
+            serializer.attribute("", ATTR_TYPE, transition.getClass().getSimpleName());
+            serializer.attribute("", ATTR_DURATION, Long.toString(transition.getDuration()));
+            serializer.attribute("", ATTR_BEHAVIOR, Integer.toString(transition.getBehavior()));
+            final MediaItem afterMediaItem = transition.getAfterMediaItem();
+            if (afterMediaItem != null) {
+                serializer.attribute("", ATTR_AFTER_MEDIA_ITEM_ID, afterMediaItem.getId());
+            }
+
+            final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
+            if (beforeMediaItem != null) {
+                serializer.attribute("", ATTR_BEFORE_MEDIA_ITEM_ID, beforeMediaItem.getId());
+            }
+
+            if (transition instanceof TransitionSliding) {
+                serializer.attribute("", ATTR_DIRECTION,
+                        Integer.toString(((TransitionSliding)transition).getDirection()));
+            } else if (transition instanceof TransitionAlpha) {
+                TransitionAlpha ta = (TransitionAlpha)transition;
+                serializer.attribute("", ATTR_BLENDING, Integer.toString(ta.getBlendingPercent()));
+                serializer.attribute("", ATTR_INVERT, Boolean.toString(ta.isInvert()));
+                if (ta.getMaskFilename() != null) {
+                    serializer.attribute("", ATTR_MASK, ta.getMaskFilename());
+                }
+            }
+            serializer.endTag("", TAG_TRANSITION);
+        }
+        serializer.endTag("", TAG_TRANSITIONS);
+
+        serializer.startTag("", TAG_AUDIO_TRACKS);
+        for (AudioTrack at : mAudioTracks) {
+            serializer.startTag("", TAG_AUDIO_TRACK);
+            serializer.attribute("", ATTR_ID, at.getId());
+            serializer.attribute("", ATTR_FILENAME, at.getFilename());
+            serializer.attribute("", ATTR_START_TIME, Long.toString(at.getStartTime()));
+            serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(at.getBoundaryBeginTime()));
+            serializer.attribute("", ATTR_END_TIME, Long.toString(at.getBoundaryEndTime()));
+            serializer.attribute("", ATTR_VOLUME, Integer.toString(at.getVolume()));
+            serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted()));
+            serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping()));
+            if (at.getAudioWaveformFilename() != null) {
+                serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
+                at.getAudioWaveformFilename());
+            }
+
+            serializer.endTag("", TAG_AUDIO_TRACK);
+        }
+        serializer.endTag("", TAG_AUDIO_TRACKS);
+
+        serializer.endTag("", TAG_PROJECT);
+        serializer.endDocument();
+
+        // Save the metadata XML file
+        final FileOutputStream out = new FileOutputStream(new File(getPath(), PROJECT_FILENAME));
+        out.write(writer.toString().getBytes());
+        out.flush();
+        out.close();
+    }
+
+    /**
+     * Load the project form XML
+     */
+    private void load() throws FileNotFoundException, XmlPullParserException, IOException {
+        final File file = new File(mProjectPath, PROJECT_FILENAME);
+        // Load the metadata
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new FileInputStream(file), "UTF-8");
+        int eventType = parser.getEventType();
+        String name;
+        MediaItem currentMediaItem = null;
+        Overlay currentOverlay = null;
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            switch (eventType) {
+                case XmlPullParser.START_TAG: {
+                    name = parser.getName();
+                    if (TAG_PROJECT.equals(name)) {
+                        mAspectRatio = Integer.parseInt(parser.getAttributeValue("",
+                                ATTR_ASPECT_RATIO));
+                    } else if (TAG_MEDIA_ITEM.equals(name)) {
+                        final String mediaItemId = parser.getAttributeValue("", ATTR_ID);
+                        final String type = parser.getAttributeValue("", ATTR_TYPE);
+                        final String filename = parser.getAttributeValue("", ATTR_FILENAME);
+                        final int renderingMode = Integer.parseInt(parser.getAttributeValue("",
+                                ATTR_RENDERING_MODE));
+
+                        if (MediaImageItem.class.getSimpleName().equals(type)) {
+                            final long durationMs = Long.parseLong(parser.getAttributeValue("",
+                                    ATTR_DURATION));
+                            currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
+                                    durationMs, renderingMode);
+                        } else if (MediaVideoItem.class.getSimpleName().equals(type)) {
+                            final long beginMs = Long.parseLong(parser.getAttributeValue("",
+                                    ATTR_BEGIN_TIME));
+                            final long endMs = Long.parseLong(parser.getAttributeValue("",
+                                    ATTR_END_TIME));
+                            final int volume = Integer.parseInt(parser.getAttributeValue("",
+                                    ATTR_VOLUME));
+                            final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("",
+                                    ATTR_MUTED));
+                            final String audioWaveformFilename = parser.getAttributeValue("",
+                                    ATTR_AUDIO_WAVEFORM_FILENAME);
+                            currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
+                                    renderingMode, beginMs, endMs, volume, muted,
+                                    audioWaveformFilename);
+
+                            final long beginTimeMs = Long.parseLong(parser.getAttributeValue("",
+                                    ATTR_BEGIN_TIME));
+                            final long endTimeMs = Long.parseLong(parser.getAttributeValue("",
+                                    ATTR_END_TIME));
+                            ((MediaVideoItem)currentMediaItem).setExtractBoundaries(beginTimeMs,
+                                    endTimeMs);
+
+                            final int volumePercent = Integer.parseInt(parser.getAttributeValue("",
+                                    ATTR_VOLUME));
+                            ((MediaVideoItem)currentMediaItem).setVolume(volumePercent);
+                        } else {
+                            Log.e(TAG, "Unknown media item type: " + type);
+                            currentMediaItem = null;
+                        }
+
+                        if (currentMediaItem != null) {
+                            mMediaItems.add(currentMediaItem);
+                        }
+                    } else if (TAG_TRANSITION.equals(name)) {
+                        final Transition transition = parseTransition(parser);
+                        if (transition != null) {
+                            mTransitions.add(transition);
+                        }
+                    } else if (TAG_OVERLAY.equals(name)) {
+                        if (currentMediaItem != null) {
+                            currentOverlay = parseOverlay(parser, currentMediaItem);
+                            if (currentOverlay != null) {
+                                currentMediaItem.addOverlay(currentOverlay);
+                            }
+                        }
+                    } else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) {
+                        if (currentOverlay != null) {
+                            final int attributesCount = parser.getAttributeCount();
+                            for (int i = 0; i < attributesCount; i++) {
+                                currentOverlay.setUserAttribute(parser.getAttributeName(i),
+                                        parser.getAttributeValue(i));
+                            }
+                        }
+                    } else if (TAG_EFFECT.equals(name)) {
+                        if (currentMediaItem != null) {
+                            final Effect effect = parseEffect(parser, currentMediaItem);
+                            if (effect != null) {
+                                currentMediaItem.addEffect(effect);
+                            }
+                        }
+                    } else if (TAG_AUDIO_TRACK.equals(name)) {
+                        final AudioTrack audioTrack = parseAudioTrack(parser);
+                        if (audioTrack != null) {
+                            addAudioTrack(audioTrack);
+                        }
+                    }
+                    break;
+                }
+
+                case XmlPullParser.END_TAG: {
+                    name = parser.getName();
+                    if (TAG_MEDIA_ITEM.equals(name)) {
+                        currentMediaItem = null;
+                    } else if (TAG_OVERLAY.equals(name)) {
+                        currentOverlay = null;
+                    }
+                    break;
+                }
+
+                default: {
+                    break;
+                }
+            }
+            eventType = parser.next();
+        }
+
+        computeTimelineDuration();
+    }
+
+    /**
+     * Parse the transition
+     *
+     * @param parser The parser
+     * @return The transition
+     */
+    private Transition parseTransition(XmlPullParser parser) {
+        final String transitionId = parser.getAttributeValue("", ATTR_ID);
+        final String type = parser.getAttributeValue("", ATTR_TYPE);
+        final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
+        final int behavior = Integer.parseInt(parser.getAttributeValue("", ATTR_BEHAVIOR));
+
+        final String beforeMediaItemId = parser.getAttributeValue("", ATTR_BEFORE_MEDIA_ITEM_ID);
+        final MediaItem beforeMediaItem;
+        if (beforeMediaItemId != null) {
+            beforeMediaItem = getMediaItem(beforeMediaItemId);
+        } else {
+            beforeMediaItem = null;
+        }
+
+        final String afterMediaItemId = parser.getAttributeValue("", ATTR_AFTER_MEDIA_ITEM_ID);
+        final MediaItem afterMediaItem;
+        if (afterMediaItemId != null) {
+            afterMediaItem = getMediaItem(afterMediaItemId);
+        } else {
+            afterMediaItem = null;
+        }
+
+        final Transition transition;
+        if (TransitionStartCurtainOpening.class.getSimpleName().equals(type)) {
+            transition = new TransitionStartCurtainOpening(transitionId, beforeMediaItem,
+                    durationMs, behavior);
+        } else if (TransitionStartFadeFromBlack.class.getSimpleName().equals(type)) {
+            transition = new TransitionStartFadeFromBlack(transitionId, beforeMediaItem,
+                    durationMs, behavior);
+        } else if (TransitionAlpha.class.getSimpleName().equals(type)) {
+            final int blending = Integer.parseInt(parser.getAttributeValue("", ATTR_BLENDING));
+            final String maskFilename = parser.getAttributeValue("", ATTR_MASK);
+            final boolean invert = Boolean.getBoolean(parser.getAttributeValue("", ATTR_INVERT));
+            transition = new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem,
+                    durationMs, behavior, maskFilename, blending, invert);
+        } else if (TransitionCrossfade.class.getSimpleName().equals(type)) {
+            transition = new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem,
+                    durationMs, behavior);
+        } else if (TransitionSliding.class.getSimpleName().equals(type)) {
+            final int direction = Integer.parseInt(parser.getAttributeValue("", ATTR_DIRECTION));
+            transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem,
+                    durationMs, behavior, direction);
+        } else if (TransitionFadeToBlack.class.getSimpleName().equals(type)) {
+            transition = new TransitionFadeToBlack(transitionId, afterMediaItem, beforeMediaItem,
+                    durationMs, behavior);
+        } else if (TransitionEndCurtainClosing.class.getSimpleName().equals(type)) {
+            transition = new TransitionEndCurtainClosing(transitionId, afterMediaItem, durationMs,
+                    behavior);
+        } else if (TransitionEndFadeToBlack.class.getSimpleName().equals(type)) {
+            transition = new TransitionEndFadeToBlack(transitionId, afterMediaItem, durationMs,
+                    behavior);
+        } else {
+            transition = null;
+        }
+
+        if (beforeMediaItem != null) {
+            beforeMediaItem.setBeginTransition(transition);
+        }
+
+        if (afterMediaItem != null) {
+            afterMediaItem.setEndTransition(transition);
+        }
+
+        return transition;
+    }
+
+    /**
+     * Parse the overlay
+     *
+     * @param parser The parser
+     * @param mediaItem The media item owner
+     *
+     * @return The overlay
+     */
+    private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) {
+        final String overlayId = parser.getAttributeValue("", ATTR_ID);
+        final String type = parser.getAttributeValue("", ATTR_TYPE);
+        final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
+        final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
+
+        final Overlay overlay;
+        if (OverlayFrame.class.getSimpleName().equals(type)) {
+            final String filename = parser.getAttributeValue("", ATTR_FILENAME);
+            overlay = new OverlayFrame(mediaItem, overlayId, filename, startTimeMs, durationMs);
+        } else {
+            overlay = null;
+        }
+
+        return overlay;
+    }
+
+    /**
+     * Parse the effect
+     *
+     * @param parser The parser
+     * @param mediaItem The media item owner
+     *
+     * @return The effect
+     */
+    private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) {
+        final String effectId = parser.getAttributeValue("", ATTR_ID);
+        final String type = parser.getAttributeValue("", ATTR_TYPE);
+        final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
+        final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
+
+        final Effect effect;
+        if (EffectColor.class.getSimpleName().equals(type)) {
+            final int colorEffectType =
+                Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_TYPE));
+            final int color;
+            if (colorEffectType == EffectColor.TYPE_COLOR) {
+                color = Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_VALUE));
+            } else {
+                color = 0;
+            }
+            effect = new EffectColor(mediaItem, effectId, startTimeMs, durationMs,
+                    colorEffectType, color);
+        } else if (EffectKenBurns.class.getSimpleName().equals(type)) {
+            final Rect startRect = new Rect(
+                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_L)),
+                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_T)),
+                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_R)),
+                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_B)));
+            final Rect endRect = new Rect(
+                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_L)),
+                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_T)),
+                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_R)),
+                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_B)));
+            effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect, startTimeMs,
+                    durationMs);
+        } else {
+            effect = null;
+        }
+
+        return effect;
+    }
+
+    /**
+     * Parse the audio track
+     *
+     * @param parser The parser
+     *
+     * @return The audio track
+     */
+    private AudioTrack parseAudioTrack(XmlPullParser parser) {
+        final String audioTrackId = parser.getAttributeValue("", ATTR_ID);
+        final String filename = parser.getAttributeValue("", ATTR_FILENAME);
+        final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_START_TIME));
+        final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
+        final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
+        final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
+        final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
+        final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP));
+        final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
+        try {
+            final AudioTrack audioTrack = new AudioTrack(this, audioTrackId, filename, startTimeMs,
+                    beginMs, endMs, loop, volume, muted, waveformFilename);
+
+            return audioTrack;
+        } catch (IOException ex) {
+            return null;
+        }
+    }
+
+    public void cancelExport(String filename) {
+    }
+
+    public void export(String filename, int height, int bitrate, ExportProgressListener listener)
+            throws IOException {
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public void generatePreview() {
+        // Generate all the needed transitions
+        for (Transition transition : mTransitions) {
+            if (!transition.isGenerated()) {
+                transition.generate();
+            }
+        }
+
+        // This is necessary because the user may had called setDuration on
+        // MediaImageItems
+        computeTimelineDuration();
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public void release() {
+        stopPreview();
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public long getDuration() {
+        // Since MediaImageItem can change duration we need to compute the
+        // duration here
+        computeTimelineDuration();
+        return mDurationMs;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public int getAspectRatio() {
+        return mAspectRatio;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public void setAspectRatio(int aspectRatio) {
+        mAspectRatio = aspectRatio;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs) {
+        if (mPreviewThread != null) {
+            throw new IllegalStateException("Previewing is in progress");
+        }
+        return timeMs;
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs,
+            boolean loop, int callbackAfterFrameCount, PreviewProgressListener listener) {
+        if (fromMs >= mDurationMs) {
+            return;
+        }
+        mPreviewThread = new PreviewThread(fromMs, toMs, loop, callbackAfterFrameCount, listener);
+        mPreviewThread.start();
+    }
+
+    /*
+     * {@inheritDoc}
+     */
+    public synchronized long stopPreview() {
+        final long stopTimeMs;
+        if (mPreviewThread != null) {
+            stopTimeMs = mPreviewThread.stopPreview();
+            mPreviewThread = null;
+        } else {
+            stopTimeMs = 0;
+        }
+        return stopTimeMs;
+    }
+
+    /**
+     * Compute the duration
+     */
+    private void computeTimelineDuration() {
+        mDurationMs = 0;
+        for (MediaItem mediaItem : mMediaItems) {
+            mDurationMs += mediaItem.getTimelineDuration();
+        }
+
+        // Subtract the transition times
+        for (Transition transition : mTransitions) {
+            if (!(transition instanceof TransitionStartCurtainOpening)
+                    && !(transition instanceof TransitionStartFadeFromBlack)
+                    && !(transition instanceof TransitionEndFadeToBlack)
+                    && !(transition instanceof TransitionEndCurtainClosing)) {
+                mDurationMs -= transition.getDuration();
+            }
+        }
+    }
+
+    /**
+     * Remove transitions associated with the specified media item
+     *
+     * @param mediaItem The media item
+     */
+    private void removeAdjacentTransitions(MediaItem mediaItem) {
+        final Iterator<Transition> it = mTransitions.iterator();
+        while (it.hasNext()) {
+            Transition t = it.next();
+            if (t.getAfterMediaItem() == mediaItem || t.getBeforeMediaItem() == mediaItem) {
+                it.remove();
+                t.invalidate();
+                mediaItem.setBeginTransition(null);
+                mediaItem.setEndTransition(null);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Remove the transition before this media item
+     *
+     * @param index The media item index
+     */
+    private void removeTransitionBefore(int index) {
+        final MediaItem mediaItem = mMediaItems.get(0);
+        final Iterator<Transition> it = mTransitions.iterator();
+        while (it.hasNext()) {
+            Transition t = it.next();
+            if (t.getBeforeMediaItem() == mediaItem) {
+                it.remove();
+                t.invalidate();
+                mediaItem.setBeginTransition(null);
+                if (index > 0) {
+                    mMediaItems.get(index - 1).setEndTransition(null);
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * Remove the transition after this media item
+     *
+     * @param index The media item index
+     */
+    private void removeTransitionAfter(int index) {
+        final MediaItem mediaItem = mMediaItems.get(index);
+        final Iterator<Transition> it = mTransitions.iterator();
+        while (it.hasNext()) {
+            Transition t = it.next();
+            if (t.getAfterMediaItem() == mediaItem) {
+                it.remove();
+                t.invalidate();
+                mediaItem.setEndTransition(null);
+                // Invalidate the reference in the next media item
+                if (index < mMediaItems.size() - 1) {
+                    mMediaItems.get(index + 1).setBeginTransition(null);
+                }
+                break;
+            }
+        }
+    }
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 70ed608..25d243b 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -8,7 +8,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 \
@@ -21,9 +25,12 @@
     libcutils \
     libsurfaceflinger_client \
     libstagefright \
-    libcamera_client
+    libcamera_client \
+	libsqlite
 
-LOCAL_STATIC_LIBRARIES :=
+ifneq ($(TARGET_SIMULATOR),true)
+LOCAL_STATIC_LIBRARIES := libmtp libusbhost
+endif
 
 LOCAL_C_INCLUDES += \
     external/tremor/Tremor \
@@ -32,6 +39,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 6f94e8b..d4aacaf 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);
 extern int register_android_media_AmrInputStream(JNIEnv *env);
 
 jint JNI_OnLoad(JavaVM* vm, void* reserved)
@@ -836,6 +840,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_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp
index cce9fd0..08a6de1 100644
--- a/media/jni/android_media_MediaProfiles.cpp
+++ b/media/jni/android_media_MediaProfiles.cpp
@@ -165,7 +165,9 @@
 android_media_MediaProfiles_native_get_camcorder_profile(JNIEnv *env, jobject thiz, jint id, jint quality)
 {
     LOGV("native_get_camcorder_profile: %d %d", id, quality);
-    if (quality != CAMCORDER_QUALITY_HIGH && quality != CAMCORDER_QUALITY_LOW) {
+    if (!((quality >= CAMCORDER_QUALITY_LOW && quality <= CAMCORDER_QUALITY_1080P) ||
+                (quality >= CAMCORDER_QUALITY_TIME_LAPSE_LOW &&
+                quality <= CAMCORDER_QUALITY_TIME_LAPSE_1080P))) {
         jniThrowException(env, "java/lang/RuntimeException", "Unknown camcorder profile quality");
         return NULL;
     }
@@ -210,6 +212,20 @@
                           audioChannels);
 }
 
+static jboolean
+android_media_MediaProfiles_native_has_camcorder_profile(JNIEnv *env, jobject thiz, jint id, jint quality)
+{
+    LOGV("native_has_camcorder_profile: %d %d", id, quality);
+    if (!((quality >= CAMCORDER_QUALITY_LOW && quality <= CAMCORDER_QUALITY_1080P) ||
+                (quality >= CAMCORDER_QUALITY_TIME_LAPSE_LOW &&
+                quality <= CAMCORDER_QUALITY_TIME_LAPSE_1080P))) {
+        return false;
+    }
+
+    camcorder_quality q = static_cast<camcorder_quality>(quality);
+    return sProfiles->hasCamcorderProfile(id, q);
+}
+
 static jint
 android_media_MediaProfiles_native_get_num_video_decoders(JNIEnv *env, jobject thiz)
 {
@@ -289,6 +305,8 @@
     {"native_init",                            "()V",                    (void *)android_media_MediaProfiles_native_init},
     {"native_get_camcorder_profile",           "(II)Landroid/media/CamcorderProfile;",
                                                                          (void *)android_media_MediaProfiles_native_get_camcorder_profile},
+    {"native_has_camcorder_profile",           "(II)Z",
+                                                                         (void *)android_media_MediaProfiles_native_has_camcorder_profile},
 };
 
 static JNINativeMethod gMethodsForDecoderCapabilitiesClass[] = {
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index efa0813..82b4ac1 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -260,6 +260,20 @@
 }
 
 static void
+android_media_MediaRecorder_setOutputFileAuxFD(JNIEnv *env, jobject thiz, jobject fileDescriptor)
+{
+    LOGV("setOutputFile");
+    if (fileDescriptor == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+    int fd = getParcelFileDescriptorFD(env, fileDescriptor);
+    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+    status_t opStatus = mr->setOutputFileAuxiliary(fd);
+    process_media_recorder_call(env, opStatus, "java/io/IOException", "setOutputFile failed.");
+}
+
+static void
 android_media_MediaRecorder_setVideoSize(JNIEnv *env, jobject thiz, jint width, jint height)
 {
     LOGV("setVideoSize(%d, %d)", width, height);
@@ -466,6 +480,7 @@
     {"setAudioEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setAudioEncoder},
     {"setParameter",         "(Ljava/lang/String;)V",           (void *)android_media_MediaRecorder_setParameter},
     {"_setOutputFile",       "(Ljava/io/FileDescriptor;JJ)V",   (void *)android_media_MediaRecorder_setOutputFileFD},
+    {"_setOutputFileAux",    "(Ljava/io/FileDescriptor;)V",     (void *)android_media_MediaRecorder_setOutputFileAuxFD},
     {"setVideoSize",         "(II)V",                           (void *)android_media_MediaRecorder_setVideoSize},
     {"setVideoFrameRate",    "(I)V",                            (void *)android_media_MediaRecorder_setVideoFrameRate},
     {"setMaxDuration",       "(I)V",                            (void *)android_media_MediaRecorder_setMaxDuration},
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
index 273f1af..fd0b233 100644
--- a/media/jni/android_media_MediaScanner.cpp
+++ b/media/jni/android_media_MediaScanner.cpp
@@ -146,7 +146,7 @@
 }
 
 static void
-android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client)
+android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jobject client)
 {
     MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
 
@@ -154,27 +154,16 @@
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
         return;
     }
-    if (extensions == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return;
-    }
-    
+
     const char *pathStr = env->GetStringUTFChars(path, NULL);
     if (pathStr == NULL) {  // Out of memory
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
         return;
     }
-    const char *extensionsStr = env->GetStringUTFChars(extensions, NULL);
-    if (extensionsStr == NULL) {  // Out of memory
-        env->ReleaseStringUTFChars(path, pathStr);
-        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
-        return;
-    }
 
     MyMediaScannerClient myClient(env, client);
-    mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);
+    mp->processDirectory(pathStr, myClient, ExceptionCheck, env);
     env->ReleaseStringUTFChars(path, pathStr);
-    env->ReleaseStringUTFChars(extensions, extensionsStr);
 }
 
 static void
@@ -309,9 +298,9 @@
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
-    {"processDirectory",  "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",    
+    {"processDirectory",  "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
                                                         (void *)android_media_MediaScanner_processDirectory},
-    {"processFile",       "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",    
+    {"processFile",       "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
                                                         (void *)android_media_MediaScanner_processFile},
     {"setLocale",         "(Ljava/lang/String;)V",      (void *)android_media_MediaScanner_setLocale},
     {"extractAlbumArt",   "(Ljava/io/FileDescriptor;)[B",     (void *)android_media_MediaScanner_extractAlbumArt},
diff --git a/media/jni/android_media_MtpClient.cpp b/media/jni/android_media_MtpClient.cpp
new file mode 100644
index 0000000..d23185b
--- /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, jlong 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 jlong
+android_media_MtpClient_get_parent(JNIEnv *env, jobject thiz,
+        jint device_id, jlong 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 jlong
+android_media_MtpClient_get_storage_id(JNIEnv *env, jobject thiz,
+        jint device_id, jlong 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, jlong 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",   "(IJ)Z", (void *)android_media_MtpClient_delete_object},
+    {"native_get_parent",      "(IJ)J", (void *)android_media_MtpClient_get_parent},
+    {"native_get_storage_id",  "(IJ)J", (void *)android_media_MtpClient_get_storage_id},
+    {"native_open_file",       "(IJ)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..7a0ae8a
--- /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, jlong storageID, jlong objectID, jintArray javaColumns)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("android_media_MtpCursor_setup queryType: %d deviceID: %d storageID: %lld objectID: %lld\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;IIJJ[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..d6bf609
--- /dev/null
+++ b/media/jni/android_media_MtpDatabase.cpp
@@ -0,0 +1,1018 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "MtpProperty.h"
+#include "MtpStringBuffer.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_getSupportedPlaybackFormats;
+static jmethodID method_getSupportedCaptureFormats;
+static jmethodID method_getSupportedObjectProperties;
+static jmethodID method_getSupportedDeviceProperties;
+static jmethodID method_getObjectProperty;
+static jmethodID method_setObjectProperty;
+static jmethodID method_getDeviceProperty;
+static jmethodID method_setDeviceProperty;
+static jmethodID method_getObjectInfo;
+static jmethodID method_getObjectFilePath;
+static jmethodID method_deleteFile;
+static jmethodID method_getObjectReferences;
+static jmethodID method_setObjectReferences;
+static jmethodID method_sessionStarted;
+static jmethodID method_sessionEnded;
+
+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);
+
+    // callee should delete[] the results from these
+    // results can be NULL
+    virtual MtpObjectFormatList*    getSupportedPlaybackFormats();
+    virtual MtpObjectFormatList*    getSupportedCaptureFormats();
+    virtual MtpObjectPropertyList*  getSupportedObjectProperties(MtpObjectFormat format);
+    virtual MtpDevicePropertyList*  getSupportedDeviceProperties();
+
+    virtual MtpResponseCode         getObjectPropertyValue(MtpObjectHandle handle,
+                                            MtpObjectProperty property,
+                                            MtpDataPacket& packet);
+
+    virtual MtpResponseCode         setObjectPropertyValue(MtpObjectHandle handle,
+                                            MtpObjectProperty property,
+                                            MtpDataPacket& packet);
+
+    virtual MtpResponseCode         getDevicePropertyValue(MtpDeviceProperty property,
+                                            MtpDataPacket& packet);
+
+    virtual MtpResponseCode         setDevicePropertyValue(MtpDeviceProperty property,
+                                            MtpDataPacket& packet);
+
+    virtual MtpResponseCode         resetDeviceProperty(MtpDeviceProperty property);
+
+    virtual MtpResponseCode         getObjectInfo(MtpObjectHandle handle,
+                                            MtpDataPacket& packet);
+
+    virtual MtpResponseCode         getObjectFilePath(MtpObjectHandle handle,
+                                            MtpString& filePath,
+                                            int64_t& fileLength);
+    virtual MtpResponseCode         deleteFile(MtpObjectHandle handle);
+
+    bool                            getObjectPropertyInfo(MtpObjectProperty property, int& type);
+    bool                            getDevicePropertyInfo(MtpDeviceProperty property, int& type);
+
+    virtual MtpObjectHandleList*    getObjectReferences(MtpObjectHandle handle);
+
+    virtual MtpResponseCode         setObjectReferences(MtpObjectHandle handle,
+                                            MtpObjectHandleList* references);
+
+    virtual MtpProperty*            getObjectPropertyDesc(MtpObjectProperty property,
+                                            MtpObjectFormat format);
+
+    virtual MtpProperty*            getDevicePropertyDesc(MtpDeviceProperty property);
+
+    virtual void                    sessionStarted();
+
+    virtual void                    sessionEnded();
+};
+
+// ----------------------------------------------------------------------------
+
+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();
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+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();
+    jstring pathStr = env->NewStringUTF(path);
+    MtpObjectHandle result = env->CallIntMethod(mDatabase, method_beginSendObject,
+            pathStr, (jint)format, (jint)parent, (jint)storage,
+            (jlong)size, (jlong)modified);
+
+    if (pathStr)
+        env->DeleteLocalRef(pathStr);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
+void MyMtpDatabase::endSendObject(const char* path, MtpObjectHandle handle,
+                                MtpObjectFormat format, bool succeeded) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jstring pathStr = env->NewStringUTF(path);
+    env->CallVoidMethod(mDatabase, method_endSendObject, pathStr,
+                        (jint)handle, (jint)format, (jboolean)succeeded);
+
+    if (pathStr)
+        env->DeleteLocalRef(pathStr);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+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);
+    env->DeleteLocalRef(array);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return list;
+}
+
+int MyMtpDatabase::getNumObjects(MtpStorageID storageID,
+                                MtpObjectFormat format,
+                                MtpObjectHandle parent) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    int result = env->CallIntMethod(mDatabase, method_getNumObjects,
+                (jint)storageID, (jint)format, (jint)parent);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
+MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
+            method_getSupportedPlaybackFormats);
+    if (!array)
+        return NULL;
+    MtpObjectFormatList* list = new MtpObjectFormatList();
+    jint* formats = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(formats[i]);
+    env->ReleaseIntArrayElements(array, formats, 0);
+    env->DeleteLocalRef(array);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return list;
+}
+
+MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
+            method_getSupportedCaptureFormats);
+    if (!array)
+        return NULL;
+    MtpObjectFormatList* list = new MtpObjectFormatList();
+    jint* formats = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(formats[i]);
+    env->ReleaseIntArrayElements(array, formats, 0);
+    env->DeleteLocalRef(array);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return list;
+}
+
+MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
+            method_getSupportedObjectProperties, (jint)format);
+    if (!array)
+        return NULL;
+    MtpObjectPropertyList* list = new MtpObjectPropertyList();
+    jint* properties = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(properties[i]);
+    env->ReleaseIntArrayElements(array, properties, 0);
+    env->DeleteLocalRef(array);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return list;
+}
+
+MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
+            method_getSupportedDeviceProperties);
+    if (!array)
+        return NULL;
+    MtpDevicePropertyList* list = new MtpDevicePropertyList();
+    jint* properties = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(properties[i]);
+    env->ReleaseIntArrayElements(array, properties, 0);
+    env->DeleteLocalRef(array);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return list;
+}
+
+MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle,
+                                            MtpObjectProperty property,
+                                            MtpDataPacket& packet) {
+    int         type;
+
+    if (!getObjectPropertyInfo(property, type))
+        return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jint result = env->CallIntMethod(mDatabase, method_getObjectProperty,
+                (jint)handle, (jint)property, mLongBuffer, mStringBuffer);
+    if (result != MTP_RESPONSE_OK) {
+        checkAndClearExceptionFromCallback(env, __FUNCTION__);
+        return result;
+    }
+
+    jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
+    jlong longValue = longValues[0];
+    env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
+
+    // special case date properties, which are strings to MTP
+    // but stored internally as a uint64
+    if (property == MTP_PROPERTY_DATE_MODIFIED || property == MTP_PROPERTY_DATE_ADDED) {
+        char    date[20];
+        formatDateTime(longValue, date, sizeof(date));
+        packet.putString(date);
+        return MTP_RESPONSE_OK;
+    }
+    // release date is stored internally as just the year
+    if (property == MTP_PROPERTY_ORIGINAL_RELEASE_DATE) {
+        char    date[20];
+        snprintf(date, sizeof(date), "%04lld0101T000000", longValue);
+        packet.putString(date);
+        return MTP_RESPONSE_OK;
+    }
+
+    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_INT128:
+            packet.putInt128(longValue);
+            break;
+        case MTP_TYPE_UINT128:
+            packet.putInt128(longValue);
+            break;
+        case MTP_TYPE_STR:
+        {
+            jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+            packet.putString(str);
+            env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+            break;
+         }
+        default:
+            LOGE("unsupported type in getObjectPropertyValue\n");
+            return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
+    }
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle,
+                                            MtpObjectProperty property,
+                                            MtpDataPacket& packet) {
+    int         type;
+
+    if (!getObjectPropertyInfo(property, type))
+        return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jlong longValue = 0;
+    jstring stringValue = NULL;
+
+    switch (type) {
+        case MTP_TYPE_INT8:
+            longValue = packet.getInt8();
+            break;
+        case MTP_TYPE_UINT8:
+            longValue = packet.getUInt8();
+            break;
+        case MTP_TYPE_INT16:
+            longValue = packet.getInt16();
+            break;
+        case MTP_TYPE_UINT16:
+            longValue = packet.getUInt16();
+            break;
+        case MTP_TYPE_INT32:
+            longValue = packet.getInt32();
+            break;
+        case MTP_TYPE_UINT32:
+            longValue = packet.getUInt32();
+            break;
+        case MTP_TYPE_INT64:
+            longValue = packet.getInt64();
+            break;
+        case MTP_TYPE_UINT64:
+            longValue = packet.getUInt64();
+            break;
+        case MTP_TYPE_STR:
+        {
+            MtpStringBuffer buffer;
+            packet.getString(buffer);
+            stringValue = env->NewStringUTF((const char *)buffer);
+            break;
+         }
+        default:
+            LOGE("unsupported type in getObjectPropertyValue\n");
+            return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
+    }
+
+    jint result = env->CallIntMethod(mDatabase, method_setObjectProperty,
+                (jint)handle, (jint)property, longValue, stringValue);
+    if (stringValue)
+        env->DeleteLocalRef(stringValue);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
+MtpResponseCode MyMtpDatabase::getDevicePropertyValue(MtpDeviceProperty property,
+                                            MtpDataPacket& packet) {
+    int         type;
+
+    if (!getDevicePropertyInfo(property, type))
+        return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty,
+                (jint)property, mLongBuffer, mStringBuffer);
+    if (result != MTP_RESPONSE_OK) {
+        checkAndClearExceptionFromCallback(env, __FUNCTION__);
+        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_INT128:
+            packet.putInt128(longValue);
+            break;
+        case MTP_TYPE_UINT128:
+            packet.putInt128(longValue);
+            break;
+        case MTP_TYPE_STR:
+        {
+            jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+            packet.putString(str);
+            env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+            break;
+         }
+        default:
+            LOGE("unsupported type in getDevicePropertyValue\n");
+            return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT;
+    }
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MyMtpDatabase::setDevicePropertyValue(MtpDeviceProperty property,
+                                            MtpDataPacket& packet) {
+    int         type;
+
+    if (!getDevicePropertyInfo(property, type))
+        return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jlong longValue = 0;
+    jstring stringValue = NULL;
+
+    switch (type) {
+        case MTP_TYPE_INT8:
+            longValue = packet.getInt8();
+            break;
+        case MTP_TYPE_UINT8:
+            longValue = packet.getUInt8();
+            break;
+        case MTP_TYPE_INT16:
+            longValue = packet.getInt16();
+            break;
+        case MTP_TYPE_UINT16:
+            longValue = packet.getUInt16();
+            break;
+        case MTP_TYPE_INT32:
+            longValue = packet.getInt32();
+            break;
+        case MTP_TYPE_UINT32:
+            longValue = packet.getUInt32();
+            break;
+        case MTP_TYPE_INT64:
+            longValue = packet.getInt64();
+            break;
+        case MTP_TYPE_UINT64:
+            longValue = packet.getUInt64();
+            break;
+        case MTP_TYPE_STR:
+        {
+            MtpStringBuffer buffer;
+            packet.getString(buffer);
+            stringValue = env->NewStringUTF((const char *)buffer);
+            break;
+         }
+        default:
+            LOGE("unsupported type in setDevicePropertyValue\n");
+            return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
+    }
+
+    jint result = env->CallIntMethod(mDatabase, method_setDeviceProperty,
+                (jint)property, longValue, stringValue);
+    if (stringValue)
+        env->DeleteLocalRef(stringValue);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
+MtpResponseCode MyMtpDatabase::resetDeviceProperty(MtpDeviceProperty property) {
+    return -1;
+}
+
+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);
+    int associationType = 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
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    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) {
+        checkAndClearExceptionFromCallback(env, __FUNCTION__);
+        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);
+    
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
+MtpResponseCode MyMtpDatabase::deleteFile(MtpObjectHandle handle) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    MtpResponseCode result = env->CallIntMethod(mDatabase, method_deleteFile, (jint)handle);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
+struct PropertyTableEntry {
+    MtpObjectProperty   property;
+    int                 type;
+};
+
+static const PropertyTableEntry   kObjectPropertyTable[] = {
+    {   MTP_PROPERTY_STORAGE_ID,        MTP_TYPE_UINT32     },
+    {   MTP_PROPERTY_OBJECT_FORMAT,     MTP_TYPE_UINT16     },
+    {   MTP_PROPERTY_PROTECTION_STATUS, MTP_TYPE_UINT16     },
+    {   MTP_PROPERTY_OBJECT_SIZE,       MTP_TYPE_UINT64     },
+    {   MTP_PROPERTY_OBJECT_FILE_NAME,  MTP_TYPE_STR        },
+    {   MTP_PROPERTY_DATE_MODIFIED,     MTP_TYPE_STR        },
+    {   MTP_PROPERTY_PARENT_OBJECT,     MTP_TYPE_UINT32     },
+    {   MTP_PROPERTY_PERSISTENT_UID,    MTP_TYPE_UINT128    },
+    {   MTP_PROPERTY_NAME,              MTP_TYPE_STR        },
+    {   MTP_PROPERTY_DISPLAY_NAME,      MTP_TYPE_STR        },
+    {   MTP_PROPERTY_DATE_ADDED,        MTP_TYPE_STR        },
+    {   MTP_PROPERTY_ARTIST,            MTP_TYPE_STR        },
+    {   MTP_PROPERTY_ALBUM_NAME,        MTP_TYPE_STR        },
+    {   MTP_PROPERTY_ALBUM_ARTIST,      MTP_TYPE_STR        },
+    {   MTP_PROPERTY_TRACK,             MTP_TYPE_UINT16     },
+    {   MTP_PROPERTY_ORIGINAL_RELEASE_DATE, MTP_TYPE_STR    },
+    {   MTP_PROPERTY_GENRE,             MTP_TYPE_STR        },
+    {   MTP_PROPERTY_COMPOSER,          MTP_TYPE_STR        },
+    {   MTP_PROPERTY_DURATION,          MTP_TYPE_UINT32     },
+    {   MTP_PROPERTY_DESCRIPTION,       MTP_TYPE_STR        },
+};
+
+static const PropertyTableEntry   kDevicePropertyTable[] = {
+    {   MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,     MTP_TYPE_STR },
+    {   MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,        MTP_TYPE_STR },
+};
+
+bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
+    int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]);
+    const PropertyTableEntry* entry = kObjectPropertyTable;
+    for (int i = 0; i < count; i++, entry++) {
+        if (entry->property == property) {
+            type = entry->type;
+            return true;
+        }
+    }
+    return false;
+}
+
+bool MyMtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) {
+    int count = sizeof(kDevicePropertyTable) / sizeof(kDevicePropertyTable[0]);
+    const PropertyTableEntry* entry = kDevicePropertyTable;
+    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);
+    env->DeleteLocalRef(array);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    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);
+    MtpResponseCode result = env->CallIntMethod(mDatabase, method_setObjectReferences,
+                (jint)handle, array);
+    env->DeleteLocalRef(array);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
+MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
+                                            MtpObjectFormat format) {
+    MtpProperty* result = NULL;
+    switch (property) {
+        case MTP_PROPERTY_OBJECT_FORMAT:
+        case MTP_PROPERTY_PROTECTION_STATUS:
+        case MTP_PROPERTY_TRACK:
+            result = new MtpProperty(property, MTP_TYPE_UINT16);
+            break;
+        case MTP_PROPERTY_STORAGE_ID:
+        case MTP_PROPERTY_PARENT_OBJECT:
+        case MTP_PROPERTY_DURATION:
+            result = new MtpProperty(property, MTP_TYPE_UINT32);
+            break;
+        case MTP_PROPERTY_OBJECT_SIZE:
+            result = new MtpProperty(property, MTP_TYPE_UINT64);
+            break;
+        case MTP_PROPERTY_PERSISTENT_UID:
+            result = new MtpProperty(property, MTP_TYPE_UINT128);
+            break;
+        case MTP_PROPERTY_NAME:
+        case MTP_PROPERTY_OBJECT_FILE_NAME:
+        case MTP_PROPERTY_DATE_MODIFIED:
+        case MTP_PROPERTY_DISPLAY_NAME:
+        case MTP_PROPERTY_DATE_ADDED:
+        case MTP_PROPERTY_ARTIST:
+        case MTP_PROPERTY_ALBUM_NAME:
+        case MTP_PROPERTY_ALBUM_ARTIST:
+        case MTP_PROPERTY_ORIGINAL_RELEASE_DATE:
+        case MTP_PROPERTY_GENRE:
+        case MTP_PROPERTY_COMPOSER:
+        case MTP_PROPERTY_DESCRIPTION:
+            result = new MtpProperty(property, MTP_TYPE_STR);
+            break;
+    }
+
+    return result;
+}
+
+MtpProperty* MyMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
+    MtpProperty* result = NULL;
+    switch (property) {
+        case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+        case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+        {
+            // writeable string properties
+            result = new MtpProperty(property, MTP_TYPE_STR, true);
+
+            // set current value
+            JNIEnv* env = AndroidRuntime::getJNIEnv();
+            jint ret = env->CallIntMethod(mDatabase, method_getDeviceProperty,
+                        (jint)property, mLongBuffer, mStringBuffer);
+            if (ret == MTP_RESPONSE_OK) {
+                jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+                result->setCurrentValue(str);
+                env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+            } else {
+                LOGE("unable to read device property, response: %04X", ret);
+            }
+
+            checkAndClearExceptionFromCallback(env, __FUNCTION__);
+            break;
+        }
+    }
+
+    return result;
+}
+
+void MyMtpDatabase::sessionStarted() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->CallVoidMethod(mDatabase, method_sessionStarted);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void MyMtpDatabase::sessionEnded() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->CallVoidMethod(mDatabase, method_sessionEnded);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+#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_getSupportedPlaybackFormats = env->GetMethodID(clazz, "getSupportedPlaybackFormats", "()[I");
+    if (method_getSupportedPlaybackFormats == NULL) {
+        LOGE("Can't find getSupportedPlaybackFormats");
+        return -1;
+    }
+    method_getSupportedCaptureFormats = env->GetMethodID(clazz, "getSupportedCaptureFormats", "()[I");
+    if (method_getSupportedCaptureFormats == NULL) {
+        LOGE("Can't find getSupportedCaptureFormats");
+        return -1;
+    }
+    method_getSupportedObjectProperties = env->GetMethodID(clazz, "getSupportedObjectProperties", "(I)[I");
+    if (method_getSupportedObjectProperties == NULL) {
+        LOGE("Can't find getSupportedObjectProperties");
+        return -1;
+    }
+    method_getSupportedDeviceProperties = env->GetMethodID(clazz, "getSupportedDeviceProperties", "()[I");
+    if (method_getSupportedDeviceProperties == NULL) {
+        LOGE("Can't find getSupportedDeviceProperties");
+        return -1;
+    }
+    method_getObjectProperty = env->GetMethodID(clazz, "getObjectProperty", "(II[J[C)I");
+    if (method_getObjectProperty == NULL) {
+        LOGE("Can't find getObjectProperty");
+        return -1;
+    }
+    method_setObjectProperty = env->GetMethodID(clazz, "setObjectProperty", "(IIJLjava/lang/String;)I");
+    if (method_setObjectProperty == NULL) {
+        LOGE("Can't find setObjectProperty");
+        return -1;
+    }
+    method_getDeviceProperty = env->GetMethodID(clazz, "getDeviceProperty", "(I[J[C)I");
+    if (method_getDeviceProperty == NULL) {
+        LOGE("Can't find getDeviceProperty");
+        return -1;
+    }
+    method_setDeviceProperty = env->GetMethodID(clazz, "setDeviceProperty", "(IJLjava/lang/String;)I");
+    if (method_setDeviceProperty == NULL) {
+        LOGE("Can't find setDeviceProperty");
+        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;
+    }
+    method_sessionStarted = env->GetMethodID(clazz, "sessionStarted", "()V");
+    if (method_sessionStarted == NULL) {
+        LOGE("Can't find sessionStarted");
+        return -1;
+    }
+    method_sessionEnded = env->GetMethodID(clazz, "sessionEnded", "()V");
+    if (method_sessionEnded == NULL) {
+        LOGE("Can't find sessionEnded");
+        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..4567965
--- /dev/null
+++ b/media/jni/android_media_MtpServer.cpp
@@ -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.
+ */
+
+#define LOG_TAG "MtpServerJNI"
+#include "utils/Log.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <utils/threads.h>
+
+#ifdef HAVE_ANDROID_OS
+#include <linux/usb/f_mtp.h>
+#endif
+
+#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;
+    jobject         mJavaServer;
+    int             mFd;
+
+public:
+    MtpThread(MtpDatabase* database, const char* storagePath, jobject javaServer)
+        :   mDatabase(database),
+            mServer(NULL),
+            mStoragePath(storagePath),
+            mJavaServer(javaServer),
+            mFd(-1)
+    {
+    }
+
+    void setPtpMode(bool usePtp) {
+        sMutex.lock();
+        if (mFd >= 0) {
+            ioctl(mFd, MTP_SET_INTERFACE_MODE,
+                    (usePtp ? MTP_INTERFACE_MODE_PTP : MTP_INTERFACE_MODE_MTP));
+        } else {
+            int fd = open("/dev/mtp_usb", O_RDWR);
+            if (fd >= 0) {
+                ioctl(fd, MTP_SET_INTERFACE_MODE,
+                        (usePtp ? MTP_INTERFACE_MODE_PTP : MTP_INTERFACE_MODE_MTP));
+                close(fd);
+            }
+        }
+        sMutex.unlock();
+    }
+
+    virtual bool threadLoop() {
+        sMutex.lock();
+        mFd = open("/dev/mtp_usb", O_RDWR);
+        printf("open returned %d\n", mFd);
+        if (mFd < 0) {
+            LOGE("could not open MTP driver\n");
+            sMutex.unlock();
+            return false;
+        }
+
+        mServer = new MtpServer(mFd, mDatabase, AID_SDCARD_RW, 0664, 0775);
+        mServer->addStorage(mStoragePath);
+        sMutex.unlock();
+
+        LOGD("MtpThread mServer->run");
+        mServer->run();
+
+        sMutex.lock();
+        close(mFd);
+        mFd = -1;
+        delete mServer;
+        mServer = NULL;
+
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        env->SetIntField(mJavaServer, field_context, 0);
+        env->DeleteGlobalRef(mJavaServer);
+        sMutex.unlock();
+
+        LOGD("threadLoop returning");
+        return false;
+    }
+
+    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");
+#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 void
+android_media_MtpServer_set_ptp_mode(JNIEnv *env, jobject thiz, jboolean usePtp)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("set_ptp_mode\n");
+    MtpThread *thread = (MtpThread *)env->GetIntField(thiz, field_context);
+    if (thread)
+        thread->setPtpMode(usePtp);
+ #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},
+    {"native_set_ptp_mode",         "(Z)V", (void *)android_media_MtpServer_set_ptp_mode},
+};
+
+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/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index 0f55b19d..9dfdcb0 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -22,12 +22,14 @@
 
 #include <media/IMediaPlayer.h>
 #include <surfaceflinger/ISurface.h>
+#include <surfaceflinger/Surface.h>
 
 namespace android {
 
 enum {
     DISCONNECT = IBinder::FIRST_CALL_TRANSACTION,
     SET_VIDEO_SURFACE,
+    SET_VIDEO_ISURFACE,
     PREPARE_ASYNC,
     START,
     STOP,
@@ -65,11 +67,20 @@
         remote()->transact(DISCONNECT, data, &reply);
     }
 
-    status_t setVideoSurface(const sp<ISurface>& surface)
+    status_t setVideoISurface(const sp<ISurface>& surface)
     {
         Parcel data, reply;
         data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
         data.writeStrongBinder(surface->asBinder());
+        remote()->transact(SET_VIDEO_ISURFACE, data, &reply);
+        return reply.readInt32();
+    }
+
+    status_t setVideoSurface(const sp<Surface>& surface)
+    {
+        Parcel data, reply;
+        data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
+        Surface::writeToParcel(surface, &data);
         remote()->transact(SET_VIDEO_SURFACE, data, &reply);
         return reply.readInt32();
     }
@@ -256,9 +267,15 @@
             disconnect();
             return NO_ERROR;
         } break;
-        case SET_VIDEO_SURFACE: {
+        case SET_VIDEO_ISURFACE: {
             CHECK_INTERFACE(IMediaPlayer, data, reply);
             sp<ISurface> surface = interface_cast<ISurface>(data.readStrongBinder());
+            reply->writeInt32(setVideoISurface(surface));
+            return NO_ERROR;
+        } break;
+        case SET_VIDEO_SURFACE: {
+            CHECK_INTERFACE(IMediaPlayer, data, reply);
+            sp<Surface> surface = Surface::readFromParcel(data);
             reply->writeInt32(setVideoSurface(surface));
             return NO_ERROR;
         } break;
diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp
index 947ff34..59cd1b7 100644
--- a/media/libmedia/IMediaRecorder.cpp
+++ b/media/libmedia/IMediaRecorder.cpp
@@ -19,7 +19,7 @@
 #define LOG_TAG "IMediaRecorder"
 #include <utils/Log.h>
 #include <binder/Parcel.h>
-#include <surfaceflinger/ISurface.h>
+#include <surfaceflinger/Surface.h>
 #include <camera/ICamera.h>
 #include <media/IMediaRecorderClient.h>
 #include <media/IMediaRecorder.h>
@@ -43,6 +43,7 @@
     SET_AUDIO_ENCODER,
     SET_OUTPUT_FILE_PATH,
     SET_OUTPUT_FILE_FD,
+    SET_OUTPUT_FILE_AUXILIARY_FD,
     SET_VIDEO_SIZE,
     SET_VIDEO_FRAMERATE,
     SET_PARAMETERS,
@@ -69,12 +70,12 @@
         return reply.readInt32();
     }
 
-    status_t setPreviewSurface(const sp<ISurface>& surface)
+    status_t setPreviewSurface(const sp<Surface>& surface)
     {
         LOGV("setPreviewSurface(%p)", surface.get());
         Parcel data, reply;
         data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor());
-        data.writeStrongBinder(surface->asBinder());
+        Surface::writeToParcel(surface, &data);
         remote()->transact(SET_PREVIEW_SURFACE, data, &reply);
         return reply.readInt32();
     }
@@ -159,6 +160,15 @@
         return reply.readInt32();
     }
 
+    status_t setOutputFileAuxiliary(int fd) {
+        LOGV("setOutputFileAuxiliary(%d)", fd);
+        Parcel data, reply;
+        data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor());
+        data.writeFileDescriptor(fd);
+        remote()->transact(SET_OUTPUT_FILE_AUXILIARY_FD, data, &reply);
+        return reply.readInt32();
+    }
+
     status_t setVideoSize(int width, int height)
     {
         LOGV("setVideoSize(%dx%d)", width, height);
@@ -377,6 +387,13 @@
             ::close(fd);
             return NO_ERROR;
         } break;
+        case SET_OUTPUT_FILE_AUXILIARY_FD: {
+            LOGV("SET_OUTPUT_FILE_AUXILIARY_FD");
+            CHECK_INTERFACE(IMediaRecorder, data, reply);
+            int fd = dup(data.readFileDescriptor());
+            reply->writeInt32(setOutputFileAuxiliary(fd));
+            return NO_ERROR;
+        } break;
         case SET_VIDEO_SIZE: {
             LOGV("SET_VIDEO_SIZE");
             CHECK_INTERFACE(IMediaRecorder, data, reply);
@@ -409,7 +426,7 @@
         case SET_PREVIEW_SURFACE: {
             LOGV("SET_PREVIEW_SURFACE");
             CHECK_INTERFACE(IMediaRecorder, data, reply);
-            sp<ISurface> surface = interface_cast<ISurface>(data.readStrongBinder());
+            sp<Surface> surface = Surface::readFromParcel(data);
             reply->writeInt32(setPreviewSurface(surface));
             return NO_ERROR;
         } break;
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index f3804b8..40801a2 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -21,7 +21,9 @@
     SET_PARAMETER,
     GET_CONFIG,
     SET_CONFIG,
+    ENABLE_GRAPHIC_BUFFERS,
     USE_BUFFER,
+    USE_GRAPHIC_BUFFER,
     ALLOC_BUFFER,
     ALLOC_BUFFER_WITH_BACKUP,
     FREE_BUFFER,
@@ -216,6 +218,19 @@
         return reply.readInt32();
     }
 
+    virtual status_t enableGraphicBuffers(
+            node_id node, OMX_U32 port_index, OMX_BOOL enable) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+        data.writeIntPtr((intptr_t)node);
+        data.writeInt32(port_index);
+        data.writeInt32((uint32_t)enable);
+        remote()->transact(ENABLE_GRAPHIC_BUFFERS, data, &reply);
+
+        status_t err = reply.readInt32();
+        return err;
+    }
+
     virtual status_t useBuffer(
             node_id node, OMX_U32 port_index, const sp<IMemory> &params,
             buffer_id *buffer) {
@@ -238,6 +253,29 @@
         return err;
     }
 
+
+    virtual status_t useGraphicBuffer(
+            node_id node, OMX_U32 port_index,
+            const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+        data.writeIntPtr((intptr_t)node);
+        data.writeInt32(port_index);
+        data.write(*graphicBuffer);
+        remote()->transact(USE_GRAPHIC_BUFFER, data, &reply);
+
+        status_t err = reply.readInt32();
+        if (err != OK) {
+            *buffer = 0;
+
+            return err;
+        }
+
+        *buffer = (void*)reply.readIntPtr();
+
+        return err;
+    }
+
     virtual status_t allocateBuffer(
             node_id node, OMX_U32 port_index, size_t size,
             buffer_id *buffer, void **buffer_data) {
@@ -541,6 +579,20 @@
             return NO_ERROR;
         }
 
+        case ENABLE_GRAPHIC_BUFFERS:
+        {
+            CHECK_INTERFACE(IOMX, data, reply);
+
+            node_id node = (void*)data.readIntPtr();
+            OMX_U32 port_index = data.readInt32();
+            OMX_BOOL enable = (OMX_BOOL)data.readInt32();
+
+            status_t err = enableGraphicBuffers(node, port_index, enable);
+            reply->writeInt32(err);
+
+            return NO_ERROR;
+        }
+
         case USE_BUFFER:
         {
             CHECK_INTERFACE(IOMX, data, reply);
@@ -561,6 +613,27 @@
             return NO_ERROR;
         }
 
+        case USE_GRAPHIC_BUFFER:
+        {
+            CHECK_INTERFACE(IOMX, data, reply);
+
+            node_id node = (void*)data.readIntPtr();
+            OMX_U32 port_index = data.readInt32();
+            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer();
+            data.read(*graphicBuffer);
+
+            buffer_id buffer;
+            status_t err = useGraphicBuffer(
+                    node, port_index, graphicBuffer, &buffer);
+            reply->writeInt32(err);
+
+            if (err == OK) {
+                reply->writeIntPtr((intptr_t)buffer);
+            }
+
+            return NO_ERROR;
+        }
+
         case ALLOC_BUFFER:
         {
             CHECK_INTERFACE(IOMX, data, reply);
diff --git a/media/libmedia/MediaProfiles.cpp b/media/libmedia/MediaProfiles.cpp
index 3869389..9ad63f0 100644
--- a/media/libmedia/MediaProfiles.cpp
+++ b/media/libmedia/MediaProfiles.cpp
@@ -59,8 +59,21 @@
 };
 
 const MediaProfiles::NameToTagMap MediaProfiles::sCamcorderQualityNameMap[] = {
+    {"low", CAMCORDER_QUALITY_LOW},
     {"high", CAMCORDER_QUALITY_HIGH},
-    {"low",  CAMCORDER_QUALITY_LOW}
+    {"qcif", CAMCORDER_QUALITY_QCIF},
+    {"cif", CAMCORDER_QUALITY_CIF},
+    {"480p", CAMCORDER_QUALITY_480P},
+    {"720p", CAMCORDER_QUALITY_720P},
+    {"1080p", CAMCORDER_QUALITY_1080P},
+
+    {"timelapselow",  CAMCORDER_QUALITY_TIME_LAPSE_LOW},
+    {"timelapsehigh", CAMCORDER_QUALITY_TIME_LAPSE_HIGH},
+    {"timelapseqcif", CAMCORDER_QUALITY_TIME_LAPSE_QCIF},
+    {"timelapsecif", CAMCORDER_QUALITY_TIME_LAPSE_CIF},
+    {"timelapse480p", CAMCORDER_QUALITY_TIME_LAPSE_480P},
+    {"timelapse720p", CAMCORDER_QUALITY_TIME_LAPSE_720P},
+    {"timelapse1080p", CAMCORDER_QUALITY_TIME_LAPSE_1080P}
 };
 
 /*static*/ void
@@ -411,16 +424,16 @@
 }
 
 /*static*/ MediaProfiles::CamcorderProfile*
-MediaProfiles::createDefaultCamcorderHighProfile()
+MediaProfiles::createDefaultCamcorderTimeLapseQcifProfile(camcorder_quality quality)
 {
     MediaProfiles::VideoCodec *videoCodec =
-        new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 360000, 352, 288, 20);
+        new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 1000000, 176, 144, 20);
 
     AudioCodec *audioCodec = new AudioCodec(AUDIO_ENCODER_AMR_NB, 12200, 8000, 1);
     CamcorderProfile *profile = new MediaProfiles::CamcorderProfile;
     profile->mCameraId = 0;
     profile->mFileFormat = OUTPUT_FORMAT_THREE_GPP;
-    profile->mQuality = CAMCORDER_QUALITY_HIGH;
+    profile->mQuality = quality;
     profile->mDuration = 60;
     profile->mVideoCodec = videoCodec;
     profile->mAudioCodec = audioCodec;
@@ -428,7 +441,40 @@
 }
 
 /*static*/ MediaProfiles::CamcorderProfile*
-MediaProfiles::createDefaultCamcorderLowProfile()
+MediaProfiles::createDefaultCamcorderTimeLapse480pProfile(camcorder_quality quality)
+{
+    MediaProfiles::VideoCodec *videoCodec =
+        new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 20000000, 720, 480, 20);
+
+    AudioCodec *audioCodec = new AudioCodec(AUDIO_ENCODER_AMR_NB, 12200, 8000, 1);
+    CamcorderProfile *profile = new MediaProfiles::CamcorderProfile;
+    profile->mCameraId = 0;
+    profile->mFileFormat = OUTPUT_FORMAT_THREE_GPP;
+    profile->mQuality = quality;
+    profile->mDuration = 60;
+    profile->mVideoCodec = videoCodec;
+    profile->mAudioCodec = audioCodec;
+    return profile;
+}
+
+/*static*/ void
+MediaProfiles::createDefaultCamcorderTimeLapseLowProfiles(
+        MediaProfiles::CamcorderProfile **lowTimeLapseProfile,
+        MediaProfiles::CamcorderProfile **lowSpecificTimeLapseProfile) {
+    *lowTimeLapseProfile = createDefaultCamcorderTimeLapseQcifProfile(CAMCORDER_QUALITY_TIME_LAPSE_LOW);
+    *lowSpecificTimeLapseProfile = createDefaultCamcorderTimeLapseQcifProfile(CAMCORDER_QUALITY_TIME_LAPSE_QCIF);
+}
+
+/*static*/ void
+MediaProfiles::createDefaultCamcorderTimeLapseHighProfiles(
+        MediaProfiles::CamcorderProfile **highTimeLapseProfile,
+        MediaProfiles::CamcorderProfile **highSpecificTimeLapseProfile) {
+    *highTimeLapseProfile = createDefaultCamcorderTimeLapse480pProfile(CAMCORDER_QUALITY_TIME_LAPSE_HIGH);
+    *highSpecificTimeLapseProfile = createDefaultCamcorderTimeLapse480pProfile(CAMCORDER_QUALITY_TIME_LAPSE_480P);
+}
+
+/*static*/ MediaProfiles::CamcorderProfile*
+MediaProfiles::createDefaultCamcorderQcifProfile(camcorder_quality quality)
 {
     MediaProfiles::VideoCodec *videoCodec =
         new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 192000, 176, 144, 20);
@@ -439,18 +485,72 @@
     MediaProfiles::CamcorderProfile *profile = new MediaProfiles::CamcorderProfile;
     profile->mCameraId = 0;
     profile->mFileFormat = OUTPUT_FORMAT_THREE_GPP;
-    profile->mQuality = CAMCORDER_QUALITY_LOW;
+    profile->mQuality = quality;
     profile->mDuration = 30;
     profile->mVideoCodec = videoCodec;
     profile->mAudioCodec = audioCodec;
     return profile;
 }
 
+/*static*/ MediaProfiles::CamcorderProfile*
+MediaProfiles::createDefaultCamcorderCifProfile(camcorder_quality quality)
+{
+    MediaProfiles::VideoCodec *videoCodec =
+        new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 360000, 352, 288, 20);
+
+    AudioCodec *audioCodec = new AudioCodec(AUDIO_ENCODER_AMR_NB, 12200, 8000, 1);
+    CamcorderProfile *profile = new MediaProfiles::CamcorderProfile;
+    profile->mCameraId = 0;
+    profile->mFileFormat = OUTPUT_FORMAT_THREE_GPP;
+    profile->mQuality = quality;
+    profile->mDuration = 60;
+    profile->mVideoCodec = videoCodec;
+    profile->mAudioCodec = audioCodec;
+    return profile;
+}
+
+/*static*/ void
+MediaProfiles::createDefaultCamcorderLowProfiles(
+        MediaProfiles::CamcorderProfile **lowProfile,
+        MediaProfiles::CamcorderProfile **lowSpecificProfile) {
+    *lowProfile = createDefaultCamcorderQcifProfile(CAMCORDER_QUALITY_LOW);
+    *lowSpecificProfile = createDefaultCamcorderQcifProfile(CAMCORDER_QUALITY_QCIF);
+}
+
+/*static*/ void
+MediaProfiles::createDefaultCamcorderHighProfiles(
+        MediaProfiles::CamcorderProfile **highProfile,
+        MediaProfiles::CamcorderProfile **highSpecificProfile) {
+    *highProfile = createDefaultCamcorderCifProfile(CAMCORDER_QUALITY_HIGH);
+    *highSpecificProfile = createDefaultCamcorderCifProfile(CAMCORDER_QUALITY_CIF);
+}
+
 /*static*/ void
 MediaProfiles::createDefaultCamcorderProfiles(MediaProfiles *profiles)
 {
-    profiles->mCamcorderProfiles.add(createDefaultCamcorderHighProfile());
-    profiles->mCamcorderProfiles.add(createDefaultCamcorderLowProfile());
+    // low camcorder profiles.
+    MediaProfiles::CamcorderProfile *lowProfile, *lowSpecificProfile;
+    createDefaultCamcorderLowProfiles(&lowProfile, &lowSpecificProfile);
+    profiles->mCamcorderProfiles.add(lowProfile);
+    profiles->mCamcorderProfiles.add(lowSpecificProfile);
+
+    // high camcorder profiles.
+    MediaProfiles::CamcorderProfile* highProfile, *highSpecificProfile;
+    createDefaultCamcorderHighProfiles(&highProfile, &highSpecificProfile);
+    profiles->mCamcorderProfiles.add(highProfile);
+    profiles->mCamcorderProfiles.add(highSpecificProfile);
+
+    // low camcorder time lapse profiles.
+    MediaProfiles::CamcorderProfile *lowTimeLapseProfile, *lowSpecificTimeLapseProfile;
+    createDefaultCamcorderTimeLapseLowProfiles(&lowTimeLapseProfile, &lowSpecificTimeLapseProfile);
+    profiles->mCamcorderProfiles.add(lowTimeLapseProfile);
+    profiles->mCamcorderProfiles.add(lowSpecificTimeLapseProfile);
+
+    // high camcorder time lapse profiles.
+    MediaProfiles::CamcorderProfile *highTimeLapseProfile, *highSpecificTimeLapseProfile;
+    createDefaultCamcorderTimeLapseHighProfiles(&highTimeLapseProfile, &highSpecificTimeLapseProfile);
+    profiles->mCamcorderProfiles.add(highTimeLapseProfile);
+    profiles->mCamcorderProfiles.add(highSpecificTimeLapseProfile);
 }
 
 /*static*/ void
@@ -668,13 +768,8 @@
     return decoders;  // copy out
 }
 
-int MediaProfiles::getCamcorderProfileParamByName(const char *name,
-                                                  int cameraId,
-                                                  camcorder_quality quality) const
+int MediaProfiles::getCamcorderProfileIndex(int cameraId, camcorder_quality quality) const
 {
-    LOGV("getCamcorderProfileParamByName: %s for camera %d, quality %d",
-         name, cameraId, quality);
-
     int index = -1;
     for (size_t i = 0, n = mCamcorderProfiles.size(); i < n; ++i) {
         if (mCamcorderProfiles[i]->mCameraId == cameraId &&
@@ -683,6 +778,17 @@
             break;
         }
     }
+    return index;
+}
+
+int MediaProfiles::getCamcorderProfileParamByName(const char *name,
+                                                  int cameraId,
+                                                  camcorder_quality quality) const
+{
+    LOGV("getCamcorderProfileParamByName: %s for camera %d, quality %d",
+         name, cameraId, quality);
+
+    int index = getCamcorderProfileIndex(cameraId, quality);
     if (index == -1) {
         LOGE("The given camcorder profile camera %d quality %d is not found",
              cameraId, quality);
@@ -705,6 +811,11 @@
     return -1;
 }
 
+bool MediaProfiles::hasCamcorderProfile(int cameraId, camcorder_quality quality) const
+{
+    return (getCamcorderProfileIndex(cameraId, quality) != -1);
+}
+
 Vector<int> MediaProfiles::getImageEncodingQualityLevels(int cameraId) const
 {
     Vector<int> result;
diff --git a/media/libmedia/MediaScanner.cpp b/media/libmedia/MediaScanner.cpp
index c5112a5..c31b622 100644
--- a/media/libmedia/MediaScanner.cpp
+++ b/media/libmedia/MediaScanner.cpp
@@ -48,8 +48,7 @@
 }
 
 status_t MediaScanner::processDirectory(
-        const char *path, const char *extensions,
-        MediaScannerClient &client,
+        const char *path, MediaScannerClient &client,
         ExceptionCheck exceptionCheck, void *exceptionEnv) {
     int pathLength = strlen(path);
     if (pathLength >= PATH_MAX) {
@@ -72,35 +71,16 @@
 
     status_t result =
         doProcessDirectory(
-                pathBuffer, pathRemaining, extensions, client,
-                exceptionCheck, exceptionEnv);
+                pathBuffer, pathRemaining, client, exceptionCheck, exceptionEnv);
 
     free(pathBuffer);
 
     return result;
 }
 
-static bool fileMatchesExtension(const char* path, const char* extensions) {
-    const char* extension = strrchr(path, '.');
-    if (!extension) return false;
-    ++extension;    // skip the dot
-    if (extension[0] == 0) return false;
-
-    while (extensions[0]) {
-        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;
-        if (extensions[0] == ',') ++extensions;
-    }
-
-    return false;
-}
-
 status_t MediaScanner::doProcessDirectory(
-        char *path, int pathRemaining, const char *extensions,
-        MediaScannerClient &client, ExceptionCheck exceptionCheck,
-        void *exceptionEnv) {
+        char *path, int pathRemaining, MediaScannerClient &client,
+        ExceptionCheck exceptionCheck, void *exceptionEnv) {
     // place to copy file or directory name
     char* fileSpot = path + strlen(path);
     struct dirent* entry;
@@ -133,6 +113,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,29 +137,20 @@
             }
         }
         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;
 
                 strcat(fileSpot, "/");
-                int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);
+                int err = doProcessDirectory(path, pathRemaining - nameLength - 1, client, exceptionCheck, exceptionEnv);
                 if (err) {
                     // pass exceptions up - ignore other errors
                     if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
                     LOGE("Error processing '%s' - skipping\n", path);
                     continue;
                 }
-            } else if (fileMatchesExtension(path, extensions)) {
+            } else {
                 struct stat statbuf;
                 stat(path, &statbuf);
                 if (statbuf.st_size > 0) {
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index c6b2efb..da1f7e5 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -206,10 +206,15 @@
     LOGV("setVideoSurface");
     Mutex::Autolock _l(mLock);
     if (mPlayer == 0) return NO_INIT;
-    if (surface != NULL)
-        return  mPlayer->setVideoSurface(surface->getISurface());
-    else
-        return  mPlayer->setVideoSurface(NULL);
+
+    status_t err = mPlayer->setVideoISurface(
+            surface == NULL ? NULL : surface->getISurface());
+
+    if (err != OK) {
+        return err;
+    }
+
+    return mPlayer->setVideoSurface(surface);
 }
 
 // must call with lock held
diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 9d53c25..51d91fe 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -65,7 +65,7 @@
         return INVALID_OPERATION;
     }
 
-    status_t ret = mMediaRecorder->setPreviewSurface(surface->getISurface());
+    status_t ret = mMediaRecorder->setPreviewSurface(surface);
     if (OK != ret) {
         LOGV("setPreviewSurface failed: %d", ret);
         mCurrentState = MEDIA_RECORDER_ERROR;
@@ -308,6 +308,32 @@
     return ret;
 }
 
+status_t MediaRecorder::setOutputFileAuxiliary(int fd)
+{
+    LOGV("setOutputFileAuxiliary(%d)", fd);
+    if(mMediaRecorder == NULL) {
+        LOGE("media recorder is not initialized yet");
+        return INVALID_OPERATION;
+    }
+    if (mIsAuxiliaryOutputFileSet) {
+        LOGE("output file has already been set");
+        return INVALID_OPERATION;
+    }
+    if (!(mCurrentState & MEDIA_RECORDER_DATASOURCE_CONFIGURED)) {
+        LOGE("setOutputFile called in an invalid state(%d)", mCurrentState);
+        return INVALID_OPERATION;
+    }
+
+    status_t ret = mMediaRecorder->setOutputFileAuxiliary(fd);
+    if (OK != ret) {
+        LOGV("setOutputFileAuxiliary failed: %d", ret);
+        mCurrentState = MEDIA_RECORDER_ERROR;
+        return ret;
+    }
+    mIsAuxiliaryOutputFileSet = true;
+    return ret;
+}
+
 status_t MediaRecorder::setVideoSize(int width, int height)
 {
     LOGV("setVideoSize(%d, %d)", width, height);
@@ -571,6 +597,7 @@
     mIsAudioEncoderSet = false;
     mIsVideoEncoderSet = false;
     mIsOutputFileSet   = false;
+    mIsAuxiliaryOutputFileSet = false;
 }
 
 // Release should be OK in any state
@@ -643,4 +670,3 @@
 }
 
 }; // namespace android
-
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index d975cb9..877e787 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -891,7 +891,15 @@
     return mStatus;
 }
 
-status_t MediaPlayerService::Client::setVideoSurface(const sp<ISurface>& surface)
+status_t MediaPlayerService::Client::setVideoISurface(const sp<ISurface>& surface)
+{
+    LOGV("[%d] setVideoISurface(%p)", mConnId, surface.get());
+    sp<MediaPlayerBase> p = getPlayer();
+    if (p == 0) return UNKNOWN_ERROR;
+    return p->setVideoISurface(surface);
+}
+
+status_t MediaPlayerService::Client::setVideoSurface(const sp<Surface>& surface)
 {
     LOGV("[%d] setVideoSurface(%p)", mConnId, surface.get());
     sp<MediaPlayerBase> p = getPlayer();
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index a967ee2..deb458c 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -204,7 +204,8 @@
 
         // IMediaPlayer interface
         virtual void            disconnect();
-        virtual status_t        setVideoSurface(const sp<ISurface>& surface);
+        virtual status_t        setVideoISurface(const sp<ISurface>& surface);
+        virtual status_t        setVideoSurface(const sp<Surface>& surface);
         virtual status_t        prepareAsync();
         virtual status_t        start();
         virtual status_t        stop();
diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp
index 19915f1..be6a8be 100644
--- a/media/libmediaplayerservice/MediaRecorderClient.cpp
+++ b/media/libmediaplayerservice/MediaRecorderClient.cpp
@@ -70,7 +70,7 @@
     return mRecorder->setCamera(camera);
 }
 
-status_t MediaRecorderClient::setPreviewSurface(const sp<ISurface>& surface)
+status_t MediaRecorderClient::setPreviewSurface(const sp<Surface>& surface)
 {
     LOGV("setPreviewSurface");
     Mutex::Autolock lock(mLock);
@@ -164,6 +164,17 @@
     return mRecorder->setOutputFile(fd, offset, length);
 }
 
+status_t MediaRecorderClient::setOutputFileAuxiliary(int fd)
+{
+    LOGV("setOutputFileAuxiliary(%d)", fd);
+    Mutex::Autolock lock(mLock);
+    if (mRecorder == NULL) {
+        LOGE("recorder is not initialized");
+        return NO_INIT;
+    }
+    return mRecorder->setOutputFileAuxiliary(fd);
+}
+
 status_t MediaRecorderClient::setVideoSize(int width, int height)
 {
     LOGV("setVideoSize(%dx%d)", width, height);
@@ -337,4 +348,3 @@
 }
 
 }; // namespace android
-
diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h
index 1d1913d..fded98e 100644
--- a/media/libmediaplayerservice/MediaRecorderClient.h
+++ b/media/libmediaplayerservice/MediaRecorderClient.h
@@ -29,7 +29,7 @@
 {
 public:
     virtual     status_t        setCamera(const sp<ICamera>& camera);
-    virtual     status_t        setPreviewSurface(const sp<ISurface>& surface);
+    virtual     status_t        setPreviewSurface(const sp<Surface>& surface);
     virtual     status_t        setVideoSource(int vs);
     virtual     status_t        setAudioSource(int as);
     virtual     status_t        setOutputFormat(int of);
@@ -37,6 +37,7 @@
     virtual     status_t        setAudioEncoder(int ae);
     virtual     status_t        setOutputFile(const char* path);
     virtual     status_t        setOutputFile(int fd, int64_t offset, int64_t length);
+    virtual     status_t        setOutputFileAuxiliary(int fd);
     virtual     status_t        setVideoSize(int width, int height);
     virtual     status_t        setVideoFrameRate(int frames_per_second);
     virtual     status_t        setParameters(const String8& params);
@@ -66,4 +67,3 @@
 }; // namespace android
 
 #endif // ANDROID_MEDIARECORDERCLIENT_H
-
diff --git a/media/libmediaplayerservice/MidiFile.h b/media/libmediaplayerservice/MidiFile.h
index 4a60ece..06e4b70 100644
--- a/media/libmediaplayerservice/MidiFile.h
+++ b/media/libmediaplayerservice/MidiFile.h
@@ -35,7 +35,8 @@
             const char* path, const KeyedVector<String8, String8> *headers);
 
     virtual status_t    setDataSource(int fd, int64_t offset, int64_t length);
-    virtual status_t    setVideoSurface(const sp<ISurface>& surface) { return UNKNOWN_ERROR; }
+    virtual status_t    setVideoISurface(const sp<ISurface>& surface) { return UNKNOWN_ERROR; }
+    virtual status_t    setVideoSurface(const sp<Surface>& surface) { return UNKNOWN_ERROR; }
     virtual status_t    prepare();
     virtual status_t    prepareAsync();
     virtual status_t    start();
diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp
index 2c96d6d..6b9bf85 100644
--- a/media/libmediaplayerservice/StagefrightPlayer.cpp
+++ b/media/libmediaplayerservice/StagefrightPlayer.cpp
@@ -44,13 +44,20 @@
     return mPlayer->setDataSource(dup(fd), offset, length);
 }
 
-status_t StagefrightPlayer::setVideoSurface(const sp<ISurface> &surface) {
-    LOGV("setVideoSurface");
+status_t StagefrightPlayer::setVideoISurface(const sp<ISurface> &surface) {
+    LOGV("setVideoISurface");
 
     mPlayer->setISurface(surface);
     return OK;
 }
 
+status_t StagefrightPlayer::setVideoSurface(const sp<Surface> &surface) {
+    LOGV("setVideoSurface");
+
+    mPlayer->setSurface(surface);
+    return OK;
+}
+
 status_t StagefrightPlayer::prepare() {
     return mPlayer->prepare();
 }
diff --git a/media/libmediaplayerservice/StagefrightPlayer.h b/media/libmediaplayerservice/StagefrightPlayer.h
index 781eb44..dd37102 100644
--- a/media/libmediaplayerservice/StagefrightPlayer.h
+++ b/media/libmediaplayerservice/StagefrightPlayer.h
@@ -35,7 +35,8 @@
             const char *url, const KeyedVector<String8, String8> *headers);
 
     virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
-    virtual status_t setVideoSurface(const sp<ISurface> &surface);
+    virtual status_t setVideoISurface(const sp<ISurface> &surface);
+    virtual status_t setVideoSurface(const sp<Surface> &surface);
     virtual status_t prepare();
     virtual status_t prepareAsync();
     virtual status_t start();
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index cf01ff6..7a78185 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -24,6 +24,9 @@
 #include <media/stagefright/AudioSource.h>
 #include <media/stagefright/AMRWriter.h>
 #include <media/stagefright/CameraSource.h>
+#include <media/stagefright/VideoSourceDownSampler.h>
+#include <media/stagefright/CameraSourceTimeLapse.h>
+#include <media/stagefright/MediaSourceSplitter.h>
 #include <media/stagefright/MPEG4Writer.h>
 #include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaDefs.h>
@@ -34,19 +37,19 @@
 #include <camera/ICamera.h>
 #include <camera/Camera.h>
 #include <camera/CameraParameters.h>
-#include <surfaceflinger/ISurface.h>
+#include <surfaceflinger/Surface.h>
 #include <utils/Errors.h>
 #include <sys/types.h>
-#include <unistd.h>
 #include <ctype.h>
+#include <unistd.h>
 
 #include "ARTPWriter.h"
 
 namespace android {
 
 StagefrightRecorder::StagefrightRecorder()
-    : mWriter(NULL),
-      mOutputFd(-1) {
+    : mWriter(NULL), mWriterAux(NULL),
+      mOutputFd(-1), mOutputFdAux(-1) {
 
     LOGV("Constructor");
     reset();
@@ -200,7 +203,7 @@
     return OK;
 }
 
-status_t StagefrightRecorder::setPreviewSurface(const sp<ISurface> &surface) {
+status_t StagefrightRecorder::setPreviewSurface(const sp<Surface> &surface) {
     LOGV("setPreviewSurface: %p", surface.get());
     mPreviewSurface = surface;
 
@@ -234,6 +237,24 @@
     return OK;
 }
 
+status_t StagefrightRecorder::setOutputFileAuxiliary(int fd) {
+    LOGV("setOutputFileAuxiliary: %d", fd);
+
+    if (fd < 0) {
+        LOGE("Invalid file descriptor: %d", fd);
+        return -EBADF;
+    }
+
+    mCaptureAuxVideo = true;
+
+    if (mOutputFdAux >= 0) {
+        ::close(mOutputFdAux);
+    }
+    mOutputFdAux = dup(fd);
+
+    return OK;
+}
+
 // Attempt to parse an int64 literal optionally surrounded by whitespace,
 // returns true on success, false otherwise.
 static bool safe_strtoi64(const char *s, int64_t *val) {
@@ -473,6 +494,68 @@
     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::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::setParamAuxVideoWidth(int32_t width) {
+    LOGV("setParamAuxVideoWidth : %d", width);
+
+    if (width <= 0) {
+        LOGE("Width (%d) is not positive", width);
+        return BAD_VALUE;
+    }
+
+    mAuxVideoWidth = width;
+    return OK;
+}
+
+status_t StagefrightRecorder::setParamAuxVideoHeight(int32_t height) {
+    LOGV("setParamAuxVideoHeight : %d", height);
+
+    if (height <= 0) {
+        LOGE("Height (%d) is not positive", height);
+        return BAD_VALUE;
+    }
+
+    mAuxVideoHeight = height;
+    return OK;
+}
+
+status_t StagefrightRecorder::setParamAuxVideoEncodingBitRate(int32_t bitRate) {
+    LOGV("StagefrightRecorder::setParamAuxVideoEncodingBitRate: %d", bitRate);
+
+    if (bitRate <= 0) {
+        LOGE("Invalid video encoding bit rate: %d", bitRate);
+        return BAD_VALUE;
+    }
+
+    mAuxVideoBitRate = bitRate;
+    return OK;
+}
+
 status_t StagefrightRecorder::setParameter(
         const String8 &key, const String8 &value) {
     LOGV("setParameter: key (%s) => value (%s)", key.string(), value.string());
@@ -556,6 +639,32 @@
         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 == "time-between-time-lapse-frame-capture") {
+        int64_t timeBetweenTimeLapseFrameCaptureMs;
+        if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureMs)) {
+            return setParamTimeBetweenTimeLapseFrameCapture(
+                    1000LL * timeBetweenTimeLapseFrameCaptureMs);
+        }
+    } else if (key == "video-aux-param-width") {
+        int32_t auxWidth;
+        if (safe_strtoi32(value.string(), &auxWidth)) {
+            return setParamAuxVideoWidth(auxWidth);
+        }
+    } else if (key == "video-aux-param-height") {
+        int32_t auxHeight;
+        if (safe_strtoi32(value.string(), &auxHeight)) {
+            return setParamAuxVideoHeight(auxHeight);
+        }
+    } else if (key == "video-aux-param-encoding-bitrate") {
+        int32_t auxVideoBitRate;
+        if (safe_strtoi32(value.string(), &auxVideoBitRate)) {
+            return setParamAuxVideoEncodingBitRate(auxVideoBitRate);
+        }
     } else {
         LOGE("setParameter: failed to find key %s", key.string());
     }
@@ -786,7 +895,14 @@
     if (mAudioSource != AUDIO_SOURCE_LIST_END) {
         source = createAudioSource();
     } else {
-        status_t err = setupVideoEncoder(&source);
+
+        sp<CameraSource> cameraSource;
+        status_t err = setupCameraSource(&cameraSource);
+        if (err != OK) {
+            return err;
+        }
+
+        err = setupVideoEncoder(cameraSource, mVideoBitRate, &source);
         if (err != OK) {
             return err;
         }
@@ -850,11 +966,74 @@
     }
 }
 
-status_t StagefrightRecorder::setupCameraSource() {
-    clipVideoBitRate();
-    clipVideoFrameRate();
-    clipVideoFrameWidth();
-    clipVideoFrameHeight();
+/*
+ * Check to see whether the requested video width and height is one
+ * of the supported sizes. It returns true if so; otherwise, it
+ * returns false.
+ */
+bool StagefrightRecorder::isVideoSizeSupported(
+    const Vector<Size>& supportedSizes) const {
+
+    LOGV("isVideoSizeSupported");
+    for (size_t i = 0; i < supportedSizes.size(); ++i) {
+        if (mVideoWidth  == supportedSizes[i].width &&
+            mVideoHeight == supportedSizes[i].height) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/*
+ * If the preview and video output is separate, we only set the
+ * the video size, and applications should set the preview size
+ * to some proper value, and the recording framework will not
+ * change the preview size; otherwise, if the video and preview
+ * output is the same, we need to set the preview to be the same
+ * as the requested video size.
+ *
+ * On return, it also returns whether the setVideoSize() is
+ * supported.
+ */
+status_t StagefrightRecorder::setCameraVideoSize(
+    CameraParameters* params,
+    bool* isSetVideoSizeSupported) {
+    LOGV("setCameraVideoSize: %dx%d", mVideoWidth, mVideoHeight);
+
+    // Check whether the requested video size is supported
+    Vector<Size> sizes;
+    params->getSupportedVideoSizes(sizes);
+    *isSetVideoSizeSupported = true;
+    if (sizes.size() == 0) {
+        LOGD("Camera does not support setVideoSize()");
+        params->getSupportedPreviewSizes(sizes);
+        *isSetVideoSizeSupported = false;
+    }
+    if (!isVideoSizeSupported(sizes)) {
+        LOGE("Camera does not support video size (%dx%d)!",
+            mVideoWidth, mVideoHeight);
+        return BAD_VALUE;
+    }
+
+    // Actually set the video size
+    if (isSetVideoSizeSupported) {
+        params->setVideoSize(mVideoWidth, mVideoHeight);
+    } else {
+        params->setPreviewSize(mVideoWidth, mVideoHeight);
+    }
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setupCamera() {
+    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) {
@@ -869,7 +1048,17 @@
 
     // Set the actual video recording frame size
     CameraParameters params(mCamera->getParameters());
-    params.setPreviewSize(mVideoWidth, mVideoHeight);
+
+    // dont change the preview size because time lapse may be using still camera
+    // as mVideoWidth, mVideoHeight may correspond to HD resolution not
+    // supported by the video camera.
+    bool isSetVideoSizeSupported = false;
+    if (!mCaptureTimeLapse) {
+        if (OK != setCameraVideoSize(&params, &isSetVideoSizeSupported)) {
+            return BAD_VALUE;
+        }
+    }
+
     params.setPreviewFrameRate(mFrameRate);
     String8 s = params.flatten();
     if (OK != mCamera->setParameters(s)) {
@@ -881,9 +1070,14 @@
 
     // Check on video frame size
     int frameWidth = 0, frameHeight = 0;
-    newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
-    if (frameWidth  < 0 || frameWidth  != mVideoWidth ||
-        frameHeight < 0 || frameHeight != mVideoHeight) {
+    if (isSetVideoSizeSupported) {
+        newCameraParams.getVideoSize(&frameWidth, &frameHeight);
+    } else {
+        newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
+    }
+    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);
@@ -921,17 +1115,30 @@
     }
 }
 
-status_t StagefrightRecorder::setupVideoEncoder(sp<MediaSource> *source) {
-    source->clear();
-
-    status_t err = setupCameraSource();
+status_t StagefrightRecorder::setupCameraSource(sp<CameraSource> *cameraSource) {
+    status_t err = setupCamera();
     if (err != OK) return err;
 
-    sp<CameraSource> cameraSource = CameraSource::CreateFromCamera(mCamera);
-    CHECK(cameraSource != NULL);
+    if (mCaptureTimeLapse) {
+        mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera(mCamera,
+                mTimeBetweenTimeLapseFrameCaptureUs, mVideoWidth, mVideoHeight, mFrameRate);
+        *cameraSource = mCameraSourceTimeLapse;
+    } else {
+        *cameraSource = CameraSource::CreateFromCamera(mCamera);
+    }
+    CHECK(*cameraSource != NULL);
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setupVideoEncoder(
+        sp<MediaSource> cameraSource,
+        int32_t videoBitRate,
+        sp<MediaSource> *source) {
+    source->clear();
 
     sp<MetaData> enc_meta = new MetaData;
-    enc_meta->setInt32(kKeyBitRate, mVideoBitRate);
+    enc_meta->setInt32(kKeyBitRate, videoBitRate);
     enc_meta->setInt32(kKeySampleRate, mFrameRate);
 
     switch (mVideoEncoder) {
@@ -975,14 +1182,24 @@
     }
     if (mVideoEncoderLevel != -1) {
         enc_meta->setInt32(kKeyVideoLevel, mVideoEncoderLevel);
+    } else if (mCaptureTimeLapse) {
+        // Check if we are using high resolution and/or high bitrate and
+        // set appropriate level for the software AVCEncoder.
+        if ((width * height >= 921600) // 720p
+                || (videoBitRate >= 20000000)) {
+            enc_meta->setInt32(kKeyVideoLevel, 50);
+        }
     }
 
     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;
     }
@@ -1013,51 +1230,147 @@
     return OK;
 }
 
-status_t StagefrightRecorder::startMPEG4Recording() {
-    int32_t totalBitRate = 0;
+status_t StagefrightRecorder::setupMPEG4Recording(
+        bool useSplitCameraSource,
+        int outputFd,
+        int32_t videoWidth, int32_t videoHeight,
+        int32_t videoBitRate,
+        int32_t *totalBitRate,
+        sp<MediaWriter> *mediaWriter) {
+    mediaWriter->clear();
+    *totalBitRate = 0;
     status_t err = OK;
-    sp<MediaWriter> writer = new MPEG4Writer(dup(mOutputFd));
+    sp<MediaWriter> writer = new MPEG4Writer(dup(outputFd));
 
     // 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;
+        *totalBitRate += mAudioBitRate;
     }
     if (mVideoSource == VIDEO_SOURCE_DEFAULT
             || mVideoSource == VIDEO_SOURCE_CAMERA) {
+
+        sp<MediaSource> cameraMediaSource;
+        if (useSplitCameraSource) {
+            LOGV("Using Split camera source");
+            cameraMediaSource = mCameraSourceSplitter->createClient();
+        } else {
+            sp<CameraSource> cameraSource;
+            err = setupCameraSource(&cameraSource);
+            cameraMediaSource = cameraSource;
+        }
+        if ((videoWidth != mVideoWidth) || (videoHeight != mVideoHeight)) {
+            // Use downsampling from the original source.
+            cameraMediaSource =
+                new VideoSourceDownSampler(cameraMediaSource, videoWidth, videoHeight);
+        }
+        if (err != OK) {
+            return err;
+        }
+
         sp<MediaSource> encoder;
-        err = setupVideoEncoder(&encoder);
-        if (err != OK) return err;
+        err = setupVideoEncoder(cameraMediaSource, videoBitRate, &encoder);
+        if (err != OK) {
+            return err;
+        }
+
         writer->addSource(encoder);
-        totalBitRate += mVideoBitRate;
+        *totalBitRate += videoBitRate;
     }
 
     if (mInterleaveDurationUs > 0) {
         reinterpret_cast<MPEG4Writer *>(writer.get())->
             setInterleaveDuration(mInterleaveDurationUs);
     }
-
     if (mMaxFileDurationUs != 0) {
         writer->setMaxFileDuration(mMaxFileDurationUs);
     }
     if (mMaxFileSizeBytes != 0) {
         writer->setMaxFileSize(mMaxFileSizeBytes);
     }
-    sp<MetaData> meta = new MetaData;
-    meta->setInt64(kKeyTime, systemTime() / 1000);
-    meta->setInt32(kKeyFileType, mOutputFormat);
-    meta->setInt32(kKeyBitRate, totalBitRate);
-    meta->setInt32(kKey64BitFileOffset, mUse64BitFileOffset);
+
+    writer->setListener(mListener);
+    *mediaWriter = writer;
+    return OK;
+}
+
+void StagefrightRecorder::setupMPEG4MetaData(int64_t startTimeUs, int32_t totalBitRate,
+        sp<MetaData> *meta) {
+    (*meta)->setInt64(kKeyTime, startTimeUs);
+    (*meta)->setInt32(kKeyFileType, mOutputFormat);
+    (*meta)->setInt32(kKeyBitRate, totalBitRate);
+    (*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset);
     if (mMovieTimeScale > 0) {
-        meta->setInt32(kKeyTimeScale, mMovieTimeScale);
+        (*meta)->setInt32(kKeyTimeScale, mMovieTimeScale);
     }
     if (mTrackEveryTimeDurationUs > 0) {
-        meta->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs);
+        (*meta)->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs);
     }
-    writer->setListener(mListener);
-    mWriter = writer;
-    return mWriter->start(meta.get());
+}
+
+status_t StagefrightRecorder::startMPEG4Recording() {
+    if (mCaptureAuxVideo) {
+        if (!mCaptureTimeLapse) {
+            LOGE("Auxiliary video can be captured only in time lapse mode");
+            return UNKNOWN_ERROR;
+        }
+        LOGV("Creating MediaSourceSplitter");
+        sp<CameraSource> cameraSource;
+        status_t err = setupCameraSource(&cameraSource);
+        if (err != OK) {
+            return err;
+        }
+        mCameraSourceSplitter = new MediaSourceSplitter(cameraSource);
+    } else {
+        mCameraSourceSplitter = NULL;
+    }
+
+    int32_t totalBitRate;
+    status_t err = setupMPEG4Recording(mCaptureAuxVideo,
+            mOutputFd, mVideoWidth, mVideoHeight,
+            mVideoBitRate, &totalBitRate, &mWriter);
+    if (err != OK) {
+        return err;
+    }
+
+    int64_t startTimeUs = systemTime() / 1000;
+    sp<MetaData> meta = new MetaData;
+    setupMPEG4MetaData(startTimeUs, totalBitRate, &meta);
+
+    err = mWriter->start(meta.get());
+    if (err != OK) {
+        return err;
+    }
+
+    if (mCaptureAuxVideo) {
+        CHECK(mOutputFdAux >= 0);
+        if (mWriterAux != NULL) {
+            LOGE("Auxiliary File writer is not avaialble");
+            return UNKNOWN_ERROR;
+        }
+        if ((mAuxVideoWidth > mVideoWidth) || (mAuxVideoHeight > mVideoHeight) ||
+                ((mAuxVideoWidth == mVideoWidth) && mAuxVideoHeight == mVideoHeight)) {
+            LOGE("Auxiliary video size (%d x %d) same or larger than the main video size (%d x %d)",
+                    mAuxVideoWidth, mAuxVideoHeight, mVideoWidth, mVideoHeight);
+            return UNKNOWN_ERROR;
+        }
+
+        int32_t totalBitrateAux;
+        err = setupMPEG4Recording(mCaptureAuxVideo,
+                mOutputFdAux, mAuxVideoWidth, mAuxVideoHeight,
+                mAuxVideoBitRate, &totalBitrateAux, &mWriterAux);
+        if (err != OK) {
+            return err;
+        }
+
+        sp<MetaData> metaAux = new MetaData;
+        setupMPEG4MetaData(startTimeUs, totalBitrateAux, &metaAux);
+
+        return mWriterAux->start(metaAux.get());
+    }
+
+    return OK;
 }
 
 status_t StagefrightRecorder::pause() {
@@ -1066,12 +1379,33 @@
         return UNKNOWN_ERROR;
     }
     mWriter->pause();
+
+    if (mCaptureAuxVideo) {
+        if (mWriterAux == NULL) {
+            return UNKNOWN_ERROR;
+        }
+        mWriterAux->pause();
+    }
+
     return OK;
 }
 
 status_t StagefrightRecorder::stop() {
     LOGV("stop");
     status_t err = OK;
+
+    if (mCaptureTimeLapse && mCameraSourceTimeLapse != NULL) {
+        mCameraSourceTimeLapse->startQuickReadReturns();
+        mCameraSourceTimeLapse = NULL;
+    }
+
+    if (mCaptureAuxVideo) {
+        if (mWriterAux != NULL) {
+            mWriterAux->stop();
+            mWriterAux.clear();
+        }
+    }
+
     if (mWriter != NULL) {
         err = mWriter->stop();
         mWriter.clear();
@@ -1095,6 +1429,13 @@
         mOutputFd = -1;
     }
 
+    if (mCaptureAuxVideo) {
+        if (mOutputFdAux >= 0) {
+            ::close(mOutputFdAux);
+            mOutputFdAux = -1;
+        }
+    }
+
     return err;
 }
 
@@ -1119,8 +1460,11 @@
     mVideoEncoder  = VIDEO_ENCODER_H263;
     mVideoWidth    = 176;
     mVideoHeight   = 144;
+    mAuxVideoWidth    = 176;
+    mAuxVideoHeight   = 144;
     mFrameRate     = 20;
     mVideoBitRate  = 192000;
+    mAuxVideoBitRate = 192000;
     mSampleRate    = 8000;
     mAudioChannels = 1;
     mAudioBitRate  = 12200;
@@ -1137,9 +1481,15 @@
     mMaxFileDurationUs = 0;
     mMaxFileSizeBytes = 0;
     mTrackEveryTimeDurationUs = 0;
+    mCaptureTimeLapse = false;
+    mTimeBetweenTimeLapseFrameCaptureUs = -1;
+    mCaptureAuxVideo = false;
+    mCameraSourceSplitter = NULL;
+    mCameraSourceTimeLapse = NULL;
     mEncoderProfiles = MediaProfiles::getInstance();
 
     mOutputFd = -1;
+    mOutputFdAux = -1;
     mFlags = 0;
 
     return OK;
@@ -1177,6 +1527,8 @@
     snprintf(buffer, SIZE, "   Recorder: %p\n", this);
     snprintf(buffer, SIZE, "   Output file (fd %d):\n", mOutputFd);
     result.append(buffer);
+    snprintf(buffer, SIZE, "   Output file Auxiliary (fd %d):\n", mOutputFdAux);
+    result.append(buffer);
     snprintf(buffer, SIZE, "     File format: %d\n", mOutputFormat);
     result.append(buffer);
     snprintf(buffer, SIZE, "     Max file size (bytes): %lld\n", mMaxFileSizeBytes);
@@ -1221,10 +1573,14 @@
     result.append(buffer);
     snprintf(buffer, SIZE, "     Frame size (pixels): %dx%d\n", mVideoWidth, mVideoHeight);
     result.append(buffer);
+    snprintf(buffer, SIZE, "     Aux Frame size (pixels): %dx%d\n", mAuxVideoWidth, mAuxVideoHeight);
+    result.append(buffer);
     snprintf(buffer, SIZE, "     Frame rate (fps): %d\n", mFrameRate);
     result.append(buffer);
     snprintf(buffer, SIZE, "     Bit rate (bps): %d\n", mVideoBitRate);
     result.append(buffer);
+    snprintf(buffer, SIZE, "     Aux Bit rate (bps): %d\n", mAuxVideoBitRate);
+    result.append(buffer);
     ::write(fd, result.string(), result.size());
     return OK;
 }
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 216f6bc..f14c704 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -19,13 +19,18 @@
 #define STAGEFRIGHT_RECORDER_H_
 
 #include <media/MediaRecorderBase.h>
+#include <camera/CameraParameters.h>
 #include <utils/String8.h>
 
 namespace android {
 
 class Camera;
+class CameraSource;
+class CameraSourceTimeLapse;
+class MediaSourceSplitter;
 struct MediaSource;
 struct MediaWriter;
+class MetaData;
 struct AudioSource;
 class MediaProfiles;
 
@@ -42,9 +47,10 @@
     virtual status_t setVideoSize(int width, int height);
     virtual status_t setVideoFrameRate(int frames_per_second);
     virtual status_t setCamera(const sp<ICamera>& camera);
-    virtual status_t setPreviewSurface(const sp<ISurface>& surface);
+    virtual status_t setPreviewSurface(const sp<Surface>& surface);
     virtual status_t setOutputFile(const char *path);
     virtual status_t setOutputFile(int fd, int64_t offset, int64_t length);
+    virtual status_t setOutputFileAuxiliary(int fd);
     virtual status_t setParameters(const String8& params);
     virtual status_t setListener(const sp<IMediaRecorderClient>& listener);
     virtual status_t prepare();
@@ -63,9 +69,9 @@
     };
 
     sp<Camera> mCamera;
-    sp<ISurface> mPreviewSurface;
+    sp<Surface> mPreviewSurface;
     sp<IMediaRecorderClient> mListener;
-    sp<MediaWriter> mWriter;
+    sp<MediaWriter> mWriter, mWriterAux;
     sp<AudioSource> mAudioSourceNode;
 
     audio_source mAudioSource;
@@ -75,8 +81,9 @@
     video_encoder mVideoEncoder;
     bool mUse64BitFileOffset;
     int32_t mVideoWidth, mVideoHeight;
+    int32_t mAuxVideoWidth, mAuxVideoHeight;
     int32_t mFrameRate;
-    int32_t mVideoBitRate;
+    int32_t mVideoBitRate, mAuxVideoBitRate;
     int32_t mAudioBitRate;
     int32_t mAudioChannels;
     int32_t mSampleRate;
@@ -92,20 +99,42 @@
     int64_t mMaxFileDurationUs;
     int64_t mTrackEveryTimeDurationUs;
 
+    bool mCaptureTimeLapse;
+    int64_t mTimeBetweenTimeLapseFrameCaptureUs;
+    bool mCaptureAuxVideo;
+    sp<MediaSourceSplitter> mCameraSourceSplitter;
+    sp<CameraSourceTimeLapse> mCameraSourceTimeLapse;
+
     String8 mParams;
-    int mOutputFd;
+    int mOutputFd, mOutputFdAux;
     int32_t mFlags;
 
     MediaProfiles *mEncoderProfiles;
 
+    status_t setupMPEG4Recording(
+        bool useSplitCameraSource,
+        int outputFd,
+        int32_t videoWidth, int32_t videoHeight,
+        int32_t videoBitRate,
+        int32_t *totalBitRate,
+        sp<MediaWriter> *mediaWriter);
+    void setupMPEG4MetaData(int64_t startTimeUs, int32_t totalBitRate,
+        sp<MetaData> *meta);
     status_t startMPEG4Recording();
     status_t startAMRRecording();
     status_t startAACRecording();
     status_t startRTPRecording();
     sp<MediaSource> createAudioSource();
-    status_t setupCameraSource();
+    status_t setupCamera();
+    bool     isVideoSizeSupported(const Vector<Size>& supportedSizes) const;
+    status_t setCameraVideoSize(CameraParameters* params,
+                bool *isSetVideoSizeSupported);
+    status_t setupCameraSource(sp<CameraSource> *cameraSource);
     status_t setupAudioEncoder(const sp<MediaWriter>& writer);
-    status_t setupVideoEncoder(sp<MediaSource> *source);
+    status_t setupVideoEncoder(
+            sp<MediaSource> cameraSource,
+            int32_t videoBitRate,
+            sp<MediaSource> *source);
 
     // Encoding parameter handling utilities
     status_t setParameter(const String8 &key, const String8 &value);
@@ -113,6 +142,11 @@
     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 setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs);
+    status_t setParamAuxVideoHeight(int32_t height);
+    status_t setParamAuxVideoWidth(int32_t width);
+    status_t setParamAuxVideoEncodingBitRate(int32_t bitRate);
     status_t setParamVideoEncodingBitRate(int32_t bitRate);
     status_t setParamVideoIFramesInterval(int32_t seconds);
     status_t setParamVideoEncoderProfile(int32_t profile);
@@ -137,4 +171,3 @@
 }  // namespace android
 
 #endif  // STAGEFRIGHT_RECORDER_H_
-
diff --git a/media/libmediaplayerservice/TestPlayerStub.h b/media/libmediaplayerservice/TestPlayerStub.h
index 6e6c3cd..5eaf592 100644
--- a/media/libmediaplayerservice/TestPlayerStub.h
+++ b/media/libmediaplayerservice/TestPlayerStub.h
@@ -75,7 +75,10 @@
 
 
     // All the methods below wrap the mPlayer instance.
-    virtual status_t setVideoSurface(const android::sp<android::ISurface>& s)  {
+    virtual status_t setVideoISurface(const android::sp<android::ISurface>& s)  {
+        return mPlayer->setVideoISurface(s);
+    }
+    virtual status_t setVideoSurface(const android::sp<android::Surface>& s)  {
         return mPlayer->setVideoSurface(s);
     }
     virtual status_t prepare() {return mPlayer->prepare();}
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 3e17a7e..472bfcf 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -10,6 +10,8 @@
         AudioSource.cpp                   \
         AwesomePlayer.cpp                 \
         CameraSource.cpp                  \
+        CameraSourceTimeLapse.cpp         \
+        VideoSourceDownSampler.cpp        \
         DataSource.cpp                    \
         ESDS.cpp                          \
         FileSource.cpp                    \
@@ -24,6 +26,7 @@
         MediaDefs.cpp                     \
         MediaExtractor.cpp                \
         MediaSource.cpp                   \
+        MediaSourceSplitter.cpp           \
         MetaData.cpp                      \
         NuCachedSource2.cpp               \
         NuHTTPDataSource.cpp              \
@@ -60,6 +63,7 @@
         libsonivox        \
         libvorbisidec     \
         libsurfaceflinger_client \
+        libstagefright_yuv \
         libcamera_client
 
 LOCAL_STATIC_LIBRARIES := \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 040e27d..31c03ad 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -44,7 +44,7 @@
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/OMXCodec.h>
 
-#include <surfaceflinger/ISurface.h>
+#include <surfaceflinger/Surface.h>
 
 #include <media/stagefright/foundation/ALooper.h>
 
@@ -100,13 +100,14 @@
             bool previewOnly,
             const char *componentName,
             OMX_COLOR_FORMATTYPE colorFormat,
-            const sp<ISurface> &surface,
+            const sp<ISurface> &isurface,
+            const sp<Surface> &surface,
             size_t displayWidth, size_t displayHeight,
             size_t decodedWidth, size_t decodedHeight)
         : mTarget(NULL),
           mLibHandle(NULL) {
             init(previewOnly, componentName,
-                 colorFormat, surface, displayWidth,
+                 colorFormat, isurface, surface, displayWidth,
                  displayHeight, decodedWidth, decodedHeight);
     }
 
@@ -138,7 +139,8 @@
             bool previewOnly,
             const char *componentName,
             OMX_COLOR_FORMATTYPE colorFormat,
-            const sp<ISurface> &surface,
+            const sp<ISurface> &isurface,
+            const sp<Surface> &surface,
             size_t displayWidth, size_t displayHeight,
             size_t decodedWidth, size_t decodedHeight);
 
@@ -150,7 +152,8 @@
         bool previewOnly,
         const char *componentName,
         OMX_COLOR_FORMATTYPE colorFormat,
-        const sp<ISurface> &surface,
+        const sp<ISurface> &isurface,
+        const sp<Surface> &surface,
         size_t displayWidth, size_t displayHeight,
         size_t decodedWidth, size_t decodedHeight) {
     if (!previewOnly) {
@@ -176,7 +179,7 @@
 
             if (func) {
                 mTarget =
-                    (*func)(surface, componentName, colorFormat,
+                    (*func)(isurface, componentName, colorFormat,
                         displayWidth, displayHeight,
                         decodedWidth, decodedHeight);
             }
@@ -688,8 +691,18 @@
     return OK;
 }
 
+void AwesomePlayer::notifyVideoSize_l() {
+    sp<MetaData> meta = mVideoSource->getFormat();
+
+    int32_t decodedWidth, decodedHeight;
+    CHECK(meta->findInt32(kKeyWidth, &decodedWidth));
+    CHECK(meta->findInt32(kKeyHeight, &decodedHeight));
+
+    notifyListener_l(MEDIA_SET_VIDEO_SIZE, decodedWidth, decodedHeight);
+}
+
 void AwesomePlayer::initRenderer_l() {
-    if (mISurface != NULL) {
+    if (mSurface != NULL || mISurface != NULL) {
         sp<MetaData> meta = mVideoSource->getFormat();
 
         int32_t format;
@@ -706,7 +719,18 @@
         // before creating a new one.
         IPCThreadState::self()->flushCommands();
 
-        if (!strncmp("OMX.", component, 4)) {
+        if (mSurface != NULL) {
+            // Other decoders are instantiated locally and as a consequence
+            // allocate their buffers in local address space.
+            mVideoRenderer = new AwesomeLocalRenderer(
+                false,  // previewOnly
+                component,
+                (OMX_COLOR_FORMATTYPE)format,
+                mISurface,
+                mSurface,
+                mVideoWidth, mVideoHeight,
+                decodedWidth, decodedHeight);
+        } else {
             // Our OMX codecs allocate buffers on the media_server side
             // therefore they require a remote IOMXRenderer that knows how
             // to display them.
@@ -716,16 +740,6 @@
                         (OMX_COLOR_FORMATTYPE)format,
                         decodedWidth, decodedHeight,
                         mVideoWidth, mVideoHeight));
-        } else {
-            // Other decoders are instantiated locally and as a consequence
-            // allocate their buffers in local address space.
-            mVideoRenderer = new AwesomeLocalRenderer(
-                false,  // previewOnly
-                component,
-                (OMX_COLOR_FORMATTYPE)format,
-                mISurface,
-                mVideoWidth, mVideoHeight,
-                decodedWidth, decodedHeight);
         }
     }
 }
@@ -764,6 +778,12 @@
     mISurface = isurface;
 }
 
+void AwesomePlayer::setSurface(const sp<Surface> &surface) {
+    Mutex::Autolock autoLock(mLock);
+
+    mSurface = surface;
+}
+
 void AwesomePlayer::setAudioSink(
         const sp<MediaPlayerBase::AudioSink> &audioSink) {
     Mutex::Autolock autoLock(mLock);
@@ -1017,6 +1037,8 @@
                 if (err == INFO_FORMAT_CHANGED) {
                     LOGV("VideoSource signalled format change.");
 
+                    notifyVideoSize_l();
+
                     if (mVideoRenderer != NULL) {
                         mVideoRendererIsPreview = false;
                         initRenderer_l();
@@ -1506,10 +1528,10 @@
 
 void AwesomePlayer::finishAsyncPrepare_l() {
     if (mIsAsyncPrepare) {
-        if (mVideoWidth < 0 || mVideoHeight < 0) {
+        if (mVideoSource == NULL) {
             notifyListener_l(MEDIA_SET_VIDEO_SIZE, 0, 0);
         } else {
-            notifyListener_l(MEDIA_SET_VIDEO_SIZE, mVideoWidth, mVideoHeight);
+            notifyVideoSize_l();
         }
 
         notifyListener_l(MEDIA_PREPARED);
@@ -1622,13 +1644,14 @@
 
     mFlags = state->mFlags & (AUTO_LOOPING | LOOPING | AT_EOS);
 
-    if (state->mLastVideoFrame && mISurface != NULL) {
+    if (state->mLastVideoFrame && (mSurface != NULL || mISurface != NULL)) {
         mVideoRenderer =
             new AwesomeLocalRenderer(
                     true,  // previewOnly
                     "",
                     (OMX_COLOR_FORMATTYPE)state->mColorFormat,
                     mISurface,
+                    mSurface,
                     state->mVideoWidth,
                     state->mVideoHeight,
                     state->mDecodedWidth,
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 89cb135..0c9eef4 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(
@@ -77,6 +82,10 @@
 }
 
 static int32_t getColorFormat(const char* colorFormat) {
+    if (!strcmp(colorFormat, CameraParameters::PIXEL_FORMAT_YUV420P)) {
+       return OMX_COLOR_FormatYUV420Planar;
+    }
+
     if (!strcmp(colorFormat, CameraParameters::PIXEL_FORMAT_YUV422SP)) {
        return OMX_COLOR_FormatYUV422SemiPlanar;
     }
@@ -121,15 +130,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();
@@ -164,7 +173,6 @@
     mMeta->setInt32(kKeyHeight, height);
     mMeta->setInt32(kKeyStride, stride);
     mMeta->setInt32(kKeySliceHeight, sliceHeight);
-
 }
 
 CameraSource::~CameraSource() {
@@ -173,6 +181,10 @@
     }
 }
 
+void CameraSource::startCameraRecording() {
+    CHECK_EQ(OK, mCamera->startRecording());
+}
+
 status_t CameraSource::start(MetaData *meta) {
     CHECK(!mStarted);
 
@@ -190,13 +202,18 @@
 
     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->setListener(NULL);
+    mCamera->stopRecording();
+}
+
 status_t CameraSource::stop() {
     LOGV("stop");
     Mutex::Autolock autoLock(mLock);
@@ -204,8 +221,7 @@
     mFrameAvailableCondition.signal();
 
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
-    mCamera->setListener(NULL);
-    mCamera->stopRecording();
+    stopCameraRecording();
     releaseQueuedFrames();
     while (!mFramesBeingEncoded.empty()) {
         LOGI("Waiting for outstanding frames being encoded: %d",
@@ -225,11 +241,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;
     }
@@ -241,7 +261,7 @@
 
 void CameraSource::releaseOneRecordingFrame(const sp<IMemory>& frame) {
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
-    mCamera->releaseRecordingFrame(frame);
+    releaseRecordingFrame(frame);
     IPCThreadState::self()->restoreCallingIdentity(token);
 }
 
@@ -251,7 +271,6 @@
     for (List<sp<IMemory> >::iterator it = mFramesBeingEncoded.begin();
          it != mFramesBeingEncoded.end(); ++it) {
         if ((*it)->pointer() ==  buffer->data()) {
-
             releaseOneRecordingFrame((*it));
             mFramesBeingEncoded.erase(it);
             ++mNumFramesEncoded;
@@ -343,6 +362,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..c1bc433
--- /dev/null
+++ b/media/libstagefright/CameraSourceTimeLapse.cpp
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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(
+        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, timeBetweenTimeLapseFrameCaptureUs,
+            width, height, videoFrameRate);
+}
+
+// static
+CameraSourceTimeLapse *CameraSourceTimeLapse::CreateFromCamera(const sp<Camera> &camera,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate) {
+    if (camera.get() == NULL) {
+        return NULL;
+    }
+
+    return new CameraSourceTimeLapse(camera, timeBetweenTimeLapseFrameCaptureUs,
+            width, height, videoFrameRate);
+}
+
+CameraSourceTimeLapse::CameraSourceTimeLapse(const sp<Camera> &camera,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate)
+    : CameraSource(camera),
+      mTimeBetweenTimeLapseFrameCaptureUs(timeBetweenTimeLapseFrameCaptureUs),
+      mTimeBetweenTimeLapseVideoFramesUs(1E6/videoFrameRate),
+      mLastTimeLapseFrameRealTimestampUs(0),
+      mSkipCurrentFrame(false) {
+
+    LOGV("starting time lapse mode");
+    mVideoWidth = width;
+    mVideoHeight = height;
+
+    if (trySettingPreviewSize(width, height)) {
+        mUseStillCameraForTimeLapse = false;
+    } else {
+        // TODO: Add a check to see that mTimeBetweenTimeLapseFrameCaptureUs is greater
+        // than the fastest rate at which the still camera can take pictures.
+        mUseStillCameraForTimeLapse = true;
+        CHECK(setPictureSizeToClosestSupported(width, height));
+        mNeedCropping = computeCropRectangleOffset();
+        mMeta->setInt32(kKeyWidth, width);
+        mMeta->setInt32(kKeyHeight, height);
+    }
+
+    // Initialize quick stop variables.
+    mQuickStop = false;
+    mForceRead = false;
+    mLastReadBufferCopy = NULL;
+    mStopWaitingForIdleCamera = false;
+}
+
+CameraSourceTimeLapse::~CameraSourceTimeLapse() {
+}
+
+void CameraSourceTimeLapse::startQuickReadReturns() {
+    Mutex::Autolock autoLock(mQuickStopLock);
+    LOGV("Enabling quick read returns");
+
+    // Enable quick stop mode.
+    mQuickStop = true;
+
+    if (mUseStillCameraForTimeLapse) {
+        // wake up the thread right away.
+        mTakePictureCondition.signal();
+    } else {
+        // Force dataCallbackTimestamp() coming from the video camera to not skip the
+        // next frame as we want read() to get a get a frame right away.
+        mForceRead = true;
+    }
+}
+
+bool CameraSourceTimeLapse::trySettingPreviewSize(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.getSupportedPreviewSizes(supportedSizes);
+
+    bool previewSizeSupported = false;
+    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)) {
+            previewSizeSupported = true;
+        }
+    }
+
+    if (previewSizeSupported) {
+        LOGV("Video size (%d, %d) is a supported preview size", width, height);
+        params.setPreviewSize(width, height);
+        CHECK(mCamera->setParameters(params.flatten()));
+        return true;
+    }
+
+    return false;
+}
+
+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;
+}
+
+void CameraSourceTimeLapse::signalBufferReturned(MediaBuffer* buffer) {
+    Mutex::Autolock autoLock(mQuickStopLock);
+    if (mQuickStop && (buffer == mLastReadBufferCopy)) {
+        buffer->setObserver(NULL);
+        buffer->release();
+    } else {
+        return CameraSource::signalBufferReturned(buffer);
+    }
+}
+
+void createMediaBufferCopy(const MediaBuffer& sourceBuffer, int64_t frameTime, MediaBuffer **newBuffer) {
+    size_t sourceSize = sourceBuffer.size();
+    void* sourcePointer = sourceBuffer.data();
+
+    (*newBuffer) = new MediaBuffer(sourceSize);
+    memcpy((*newBuffer)->data(), sourcePointer, sourceSize);
+
+    (*newBuffer)->meta_data()->setInt64(kKeyTime, frameTime);
+}
+
+void CameraSourceTimeLapse::fillLastReadBufferCopy(MediaBuffer& sourceBuffer) {
+    int64_t frameTime;
+    CHECK(sourceBuffer.meta_data()->findInt64(kKeyTime, &frameTime));
+    createMediaBufferCopy(sourceBuffer, frameTime, &mLastReadBufferCopy);
+    mLastReadBufferCopy->add_ref();
+    mLastReadBufferCopy->setObserver(this);
+}
+
+status_t CameraSourceTimeLapse::read(
+        MediaBuffer **buffer, const ReadOptions *options) {
+    if (mLastReadBufferCopy == NULL) {
+        mLastReadStatus = CameraSource::read(buffer, options);
+
+        // mQuickStop may have turned to true while read was blocked. Make a copy of
+        // the buffer in that case.
+        Mutex::Autolock autoLock(mQuickStopLock);
+        if (mQuickStop && *buffer) {
+            fillLastReadBufferCopy(**buffer);
+        }
+        return mLastReadStatus;
+    } else {
+        (*buffer) = mLastReadBufferCopy;
+        (*buffer)->add_ref();
+        return mLastReadStatus;
+    }
+}
+
+// static
+void *CameraSourceTimeLapse::ThreadTimeLapseWrapper(void *me) {
+    CameraSourceTimeLapse *source = static_cast<CameraSourceTimeLapse *>(me);
+    source->threadTimeLapseEntry();
+    return NULL;
+}
+
+void CameraSourceTimeLapse::threadTimeLapseEntry() {
+    while (mStarted) {
+        {
+            Mutex::Autolock autoLock(mCameraIdleLock);
+            if (!mCameraIdle) {
+                mCameraIdleCondition.wait(mCameraIdleLock);
+            }
+            CHECK(mCameraIdle);
+            mCameraIdle = false;
+        }
+
+        // Even if mQuickStop == true we need to take one more picture
+        // as a read() may be blocked, waiting for a frame to get available.
+        // After this takePicture, if mQuickStop == true, we can safely exit
+        // this thread as read() will make a copy of this last frame and keep
+        // returning it in the quick stop mode.
+        Mutex::Autolock autoLock(mQuickStopLock);
+        CHECK_EQ(OK, mCamera->takePicture());
+        if (mQuickStop) {
+            LOGV("threadTimeLapseEntry: Exiting due to mQuickStop = true");
+            return;
+        }
+        mTakePictureCondition.waitRelative(mQuickStopLock,
+                mTimeBetweenTimeLapseFrameCaptureUs * 1000);
+    }
+    LOGV("threadTimeLapseEntry: Exiting due to mStarted = false");
+}
+
+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;
+        mStopWaitingForIdleCamera = false;
+
+        // disable shutter sound and play the recording sound.
+        mCamera->sendCommand(CAMERA_CMD_ENABLE_SHUTTER_SOUND, 0, 0);
+        mCamera->sendCommand(CAMERA_CMD_PLAY_RECORDING_SOUND, 0, 0);
+
+        // 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);
+
+        // Last takePicture may still be underway. Wait for the camera to get
+        // idle.
+        Mutex::Autolock autoLock(mCameraIdleLock);
+        mStopWaitingForIdleCamera = true;
+        if (!mCameraIdle) {
+            mCameraIdleCondition.wait(mCameraIdleLock);
+        }
+        CHECK(mCameraIdle);
+        mCamera->setListener(NULL);
+
+        // play the recording sound.
+        mCamera->sendCommand(CAMERA_CMD_PLAY_RECORDING_SOUND, 0, 0);
+    } else {
+        mCamera->setListener(NULL);
+        mCamera->stopRecording();
+    }
+    if (mLastReadBufferCopy) {
+        mLastReadBufferCopy->release();
+        mLastReadBufferCopy = NULL;
+    }
+}
+
+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());
+    Mutex::Autolock autoLock(mCameraIdleLock);
+    mCameraIdle = true;
+    mCameraIdleCondition.signal();
+}
+
+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 {
+        CHECK_EQ(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;
+            return false;
+        }
+
+        {
+            Mutex::Autolock autoLock(mQuickStopLock);
+
+            // mForceRead may be set to true by startQuickReadReturns(). In that
+            // case don't skip this frame.
+            if (mForceRead) {
+                LOGV("dataCallbackTimestamp timelapse: forced read");
+                mForceRead = false;
+                *timestampUs = mLastFrameTimestampUs;
+                return false;
+            }
+        }
+
+        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;
+        }
+    }
+    return false;
+}
+
+void CameraSourceTimeLapse::dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
+            const sp<IMemory> &data) {
+    if (!mUseStillCameraForTimeLapse) {
+        mSkipCurrentFrame = skipFrameAndModifyTimeStamp(&timestampUs);
+    } else {
+        Mutex::Autolock autoLock(mCameraIdleLock);
+        // If we are using the still camera and stop() has been called, it may
+        // be waiting for the camera to get idle. In that case return
+        // immediately. Calling CameraSource::dataCallbackTimestamp() will lead
+        // to a deadlock since it tries to access CameraSource::mLock which in
+        // this case is held by CameraSource::stop() currently waiting for the
+        // camera to get idle. And camera will not get idle until this call
+        // returns.
+        if (mStopWaitingForIdleCamera) {
+            return;
+        }
+    }
+    CameraSource::dataCallbackTimestamp(timestampUs, msgType, data);
+}
+
+}  // namespace android
diff --git a/media/libstagefright/MediaSourceSplitter.cpp b/media/libstagefright/MediaSourceSplitter.cpp
new file mode 100644
index 0000000..abc7012
--- /dev/null
+++ b/media/libstagefright/MediaSourceSplitter.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "MediaSourceSplitter"
+#include <utils/Log.h>
+
+#include <media/stagefright/MediaSourceSplitter.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+MediaSourceSplitter::MediaSourceSplitter(sp<MediaSource> mediaSource) {
+    mNumberOfClients = 0;
+    mSource = mediaSource;
+    mSourceStarted = false;
+
+    mNumberOfClientsStarted = 0;
+    mNumberOfCurrentReads = 0;
+    mCurrentReadBit = 0;
+    mLastReadCompleted = true;
+}
+
+MediaSourceSplitter::~MediaSourceSplitter() {
+}
+
+sp<MediaSource> MediaSourceSplitter::createClient() {
+    Mutex::Autolock autoLock(mLock);
+
+    sp<MediaSource> client = new Client(this, mNumberOfClients++);
+    mClientsStarted.push(false);
+    mClientsDesiredReadBit.push(0);
+    return client;
+}
+
+status_t MediaSourceSplitter::start(int clientId, MetaData *params) {
+    Mutex::Autolock autoLock(mLock);
+
+    LOGV("start client (%d)", clientId);
+    if (mClientsStarted[clientId]) {
+        return OK;
+    }
+
+    mNumberOfClientsStarted++;
+
+    if (!mSourceStarted) {
+        LOGV("Starting real source from client (%d)", clientId);
+        status_t err = mSource->start(params);
+
+        if (err == OK) {
+            mSourceStarted = true;
+            mClientsStarted.editItemAt(clientId) = true;
+            mClientsDesiredReadBit.editItemAt(clientId) = !mCurrentReadBit;
+        }
+
+        return err;
+    } else {
+        mClientsStarted.editItemAt(clientId) = true;
+        if (mLastReadCompleted) {
+            // Last read was completed. So join in the threads for the next read.
+            mClientsDesiredReadBit.editItemAt(clientId) = !mCurrentReadBit;
+        } else {
+            // Last read is ongoing. So join in the threads for the current read.
+            mClientsDesiredReadBit.editItemAt(clientId) = mCurrentReadBit;
+        }
+        return OK;
+    }
+}
+
+status_t MediaSourceSplitter::stop(int clientId) {
+    Mutex::Autolock autoLock(mLock);
+
+    LOGV("stop client (%d)", clientId);
+    CHECK(clientId >= 0 && clientId < mNumberOfClients);
+    CHECK(mClientsStarted[clientId]);
+
+    if (--mNumberOfClientsStarted == 0) {
+        LOGV("Stopping real source from client (%d)", clientId);
+        status_t err = mSource->stop();
+        mSourceStarted = false;
+        mClientsStarted.editItemAt(clientId) = false;
+        return err;
+    } else {
+        mClientsStarted.editItemAt(clientId) = false;
+        if (!mLastReadCompleted && (mClientsDesiredReadBit[clientId] == mCurrentReadBit)) {
+            // !mLastReadCompleted implies that buffer has been read from source, but all
+            // clients haven't read it.
+            // mClientsDesiredReadBit[clientId] == mCurrentReadBit implies that this
+            // client would have wanted to read from this buffer. (i.e. it has not yet
+            // called read() for the current read buffer.)
+            // Since other threads may be waiting for all the clients' reads to complete,
+            // signal that this read has been aborted.
+            signalReadComplete_lock(true);
+        }
+        return OK;
+    }
+}
+
+sp<MetaData> MediaSourceSplitter::getFormat(int clientId) {
+    Mutex::Autolock autoLock(mLock);
+
+    LOGV("getFormat client (%d)", clientId);
+    return mSource->getFormat();
+}
+
+status_t MediaSourceSplitter::read(int clientId,
+        MediaBuffer **buffer, const MediaSource::ReadOptions *options) {
+    Mutex::Autolock autoLock(mLock);
+
+    CHECK(clientId >= 0 && clientId < mNumberOfClients);
+
+    LOGV("read client (%d)", clientId);
+    *buffer = NULL;
+
+    if (!mClientsStarted[clientId]) {
+        return OK;
+    }
+
+    if (mCurrentReadBit != mClientsDesiredReadBit[clientId]) {
+        // Desired buffer has not been read from source yet.
+
+        // If the current client is the special client with clientId = 0
+        // then read from source, else wait until the client 0 has finished
+        // reading from source.
+        if (clientId == 0) {
+            // Wait for all client's last read to complete first so as to not
+            // corrupt the buffer at mLastReadMediaBuffer.
+            waitForAllClientsLastRead_lock(clientId);
+
+            readFromSource_lock(options);
+            *buffer = mLastReadMediaBuffer;
+        } else {
+            waitForReadFromSource_lock(clientId);
+
+            *buffer = mLastReadMediaBuffer;
+            (*buffer)->add_ref();
+        }
+        CHECK(mCurrentReadBit == mClientsDesiredReadBit[clientId]);
+    } else {
+        // Desired buffer has already been read from source. Use the cached data.
+        CHECK(clientId != 0);
+
+        *buffer = mLastReadMediaBuffer;
+        (*buffer)->add_ref();
+    }
+
+    mClientsDesiredReadBit.editItemAt(clientId) = !mClientsDesiredReadBit[clientId];
+    signalReadComplete_lock(false);
+
+    return mLastReadStatus;
+}
+
+void MediaSourceSplitter::readFromSource_lock(const MediaSource::ReadOptions *options) {
+    mLastReadStatus = mSource->read(&mLastReadMediaBuffer , options);
+
+    mCurrentReadBit = !mCurrentReadBit;
+    mLastReadCompleted = false;
+    mReadFromSourceCondition.broadcast();
+}
+
+void MediaSourceSplitter::waitForReadFromSource_lock(int32_t clientId) {
+    mReadFromSourceCondition.wait(mLock);
+}
+
+void MediaSourceSplitter::waitForAllClientsLastRead_lock(int32_t clientId) {
+    if (mLastReadCompleted) {
+        return;
+    }
+    mAllReadsCompleteCondition.wait(mLock);
+    CHECK(mLastReadCompleted);
+}
+
+void MediaSourceSplitter::signalReadComplete_lock(bool readAborted) {
+    if (!readAborted) {
+        mNumberOfCurrentReads++;
+    }
+
+    if (mNumberOfCurrentReads == mNumberOfClientsStarted) {
+        mLastReadCompleted = true;
+        mNumberOfCurrentReads = 0;
+        mAllReadsCompleteCondition.broadcast();
+    }
+}
+
+status_t MediaSourceSplitter::pause(int clientId) {
+    return ERROR_UNSUPPORTED;
+}
+
+// Client
+
+MediaSourceSplitter::Client::Client(
+        sp<MediaSourceSplitter> splitter,
+        int32_t clientId) {
+    mSplitter = splitter;
+    mClientId = clientId;
+}
+
+status_t MediaSourceSplitter::Client::start(MetaData *params) {
+    return mSplitter->start(mClientId, params);
+}
+
+status_t MediaSourceSplitter::Client::stop() {
+    return mSplitter->stop(mClientId);
+}
+
+sp<MetaData> MediaSourceSplitter::Client::getFormat() {
+    return mSplitter->getFormat(mClientId);
+}
+
+status_t MediaSourceSplitter::Client::read(
+        MediaBuffer **buffer, const ReadOptions *options) {
+    return mSplitter->read(mClientId, buffer, options);
+}
+
+status_t MediaSourceSplitter::Client::pause() {
+    return mSplitter->pause(mClientId);
+}
+
+}  // namespace android
diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp
index fcbfdac..2743b2f 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/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 76c8870..4d69dd3 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -152,31 +152,38 @@
 
 static const CodecInfo kDecoderInfo[] = {
     { MEDIA_MIMETYPE_IMAGE_JPEG, "OMX.TI.JPEG.decode" },
+//    { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.Nvidia.mp3.decoder" },
 //    { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.TI.MP3.decode" },
     { MEDIA_MIMETYPE_AUDIO_MPEG, "MP3Decoder" },
 //    { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.PV.mp3dec" },
 //    { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.TI.AMR.decode" },
+//    { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.Nvidia.amr.decoder" },
     { MEDIA_MIMETYPE_AUDIO_AMR_NB, "AMRNBDecoder" },
 //    { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.PV.amrdec" },
+//    { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.Nvidia.amrwb.decoder" },
     { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.decode" },
     { MEDIA_MIMETYPE_AUDIO_AMR_WB, "AMRWBDecoder" },
 //    { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.PV.amrdec" },
+//    { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.Nvidia.aac.decoder" },
     { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.decode" },
     { MEDIA_MIMETYPE_AUDIO_AAC, "AACDecoder" },
 //    { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.PV.aacdec" },
     { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "G711Decoder" },
     { MEDIA_MIMETYPE_AUDIO_G711_MLAW, "G711Decoder" },
+//    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.Nvidia.mp4.decode" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.decoder.mpeg4" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.decoder.mpeg4" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.Decoder" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.SEC.MPEG4.Decoder" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "M4vH263Decoder" },
 //    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.PV.mpeg4dec" },
+//    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.Nvidia.h263.decode" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.7x30.video.decoder.h263" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.decoder.h263" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.SEC.H263.Decoder" },
     { MEDIA_MIMETYPE_VIDEO_H263, "M4vH263Decoder" },
 //    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.PV.h263dec" },
+    { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.Nvidia.h264.decode" },
     { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.7x30.video.decoder.avc" },
     { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.decoder.avc" },
     { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.Decoder" },
@@ -198,18 +205,21 @@
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.encoder.mpeg4" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.encoder.mpeg4" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.encoder" },
+    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.Nvidia.mp4.encoder" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.SEC.MPEG4.Encoder" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "M4vH263Encoder" },
 //    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.PV.mpeg4enc" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.7x30.video.encoder.h263" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.encoder.h263" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.TI.Video.encoder" },
+    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.Nvidia.h263.encoder" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.SEC.H263.Encoder" },
     { MEDIA_MIMETYPE_VIDEO_H263, "M4vH263Encoder" },
 //    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.PV.h263enc" },
     { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.7x30.video.encoder.avc" },
     { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.encoder.avc" },
     { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.encoder" },
+    { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.Nvidia.h264.encoder" },
     { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.SEC.AVC.Encoder" },
     { MEDIA_MIMETYPE_VIDEO_AVC, "AVCEncoder" },
 //    { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.PV.avcenc" },
@@ -349,6 +359,13 @@
 uint32_t OMXCodec::getComponentQuirks(const char *componentName) {
     uint32_t quirks = 0;
 
+    if (!strcmp(componentName, "OMX.Nvidia.amr.decoder") ||
+         !strcmp(componentName, "OMX.Nvidia.amrwb.decoder") ||
+         !strcmp(componentName, "OMX.Nvidia.aac.decoder") ||
+         !strcmp(componentName, "OMX.Nvidia.mp3.decoder")) {
+        quirks |= kDecoderLiesAboutNumberOfChannels;
+    }
+
     if (!strcmp(componentName, "OMX.PV.avcdec")) {
         quirks |= kWantsNALFragments;
     }
@@ -876,6 +893,10 @@
     OMX_COLOR_FORMATTYPE colorFormat;
     CHECK_EQ(OK, findTargetColorFormat(meta, &colorFormat));
 
+    if (!strcasecmp("OMX.Nvidia.h264.encoder", mComponentName)) {
+        colorFormat = OMX_COLOR_FormatYUV420Planar;
+    }
+
     status_t err;
     OMX_PARAM_PORTDEFINITIONTYPE def;
     OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
@@ -1229,6 +1250,10 @@
     h264type.bMBAFF = OMX_FALSE;
     h264type.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterEnable;
 
+    if (!strcasecmp("OMX.Nvidia.h264.encoder", mComponentName)) {
+        h264type.eLevel = OMX_VIDEO_AVCLevelMax;
+    }
+
     err = mOMX->setParameter(
             mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type));
     CHECK_EQ(err, OK);
@@ -1813,7 +1838,9 @@
 
         case OMX_EventPortSettingsChanged:
         {
-            onPortSettingsChanged(data1);
+            if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) {
+                onPortSettingsChanged(data1);
+            }
             break;
         }
 
@@ -2203,6 +2230,7 @@
     CHECK_EQ(mPortStatus[portIndex], ENABLED);
     mPortStatus[portIndex] = DISABLING;
 
+    CODEC_LOGV("sending OMX_CommandPortDisable(%ld)", portIndex);
     status_t err =
         mOMX->sendCommand(mNode, OMX_CommandPortDisable, portIndex);
     CHECK_EQ(err, OK);
diff --git a/media/libstagefright/VideoSourceDownSampler.cpp b/media/libstagefright/VideoSourceDownSampler.cpp
new file mode 100644
index 0000000..ea7b09a
--- /dev/null
+++ b/media/libstagefright/VideoSourceDownSampler.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "VideoSourceDownSampler"
+
+#include <media/stagefright/VideoSourceDownSampler.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/YUVImage.h>
+#include <media/stagefright/YUVCanvas.h>
+#include "OMX_Video.h"
+
+namespace android {
+
+VideoSourceDownSampler::VideoSourceDownSampler(const sp<MediaSource> &videoSource,
+        int32_t width, int32_t height) {
+    LOGV("Construct VideoSourceDownSampler");
+    CHECK(width > 0);
+    CHECK(height > 0);
+
+    mRealVideoSource = videoSource;
+    mWidth = width;
+    mHeight = height;
+
+    mMeta = new MetaData(*(mRealVideoSource->getFormat()));
+    CHECK(mMeta->findInt32(kKeyWidth, &mRealSourceWidth));
+    CHECK(mMeta->findInt32(kKeyHeight, &mRealSourceHeight));
+
+    if ((mWidth != mRealSourceWidth) || (mHeight != mRealSourceHeight)) {
+        // Change meta data for width and height.
+        CHECK(mWidth <= mRealSourceWidth);
+        CHECK(mHeight <= mRealSourceHeight);
+
+        mNeedDownSampling = true;
+        computeDownSamplingParameters();
+        mMeta->setInt32(kKeyWidth, mWidth);
+        mMeta->setInt32(kKeyHeight, mHeight);
+    } else {
+        mNeedDownSampling = false;
+    }
+}
+
+VideoSourceDownSampler::~VideoSourceDownSampler() {
+}
+
+void VideoSourceDownSampler::computeDownSamplingParameters() {
+    mDownSampleSkipX = mRealSourceWidth / mWidth;
+    mDownSampleSkipY = mRealSourceHeight / mHeight;
+
+    mDownSampleOffsetX = mRealSourceWidth - mDownSampleSkipX * mWidth;
+    mDownSampleOffsetY = mRealSourceHeight - mDownSampleSkipY * mHeight;
+}
+
+void VideoSourceDownSampler::downSampleYUVImage(
+        const MediaBuffer &sourceBuffer, MediaBuffer **buffer) const {
+    // 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 mediaBuffer for down sampled image and setup a canvas.
+    *buffer = new MediaBuffer(YUVImage::bufferSize(yuvFormat, mWidth, mHeight));
+    YUVImage yuvDownSampledImage(yuvFormat,
+            mWidth, mHeight,
+            (uint8_t *)(*buffer)->data());
+    YUVCanvas yuvCanvasDownSample(yuvDownSampledImage);
+
+    YUVImage yuvImageSource(yuvFormat,
+            mRealSourceWidth, mRealSourceHeight,
+            (uint8_t *)sourceBuffer.data());
+    yuvCanvasDownSample.downsample(mDownSampleOffsetX, mDownSampleOffsetY,
+            mDownSampleSkipX, mDownSampleSkipY,
+            yuvImageSource);
+}
+
+status_t VideoSourceDownSampler::start(MetaData *params) {
+    LOGV("start");
+    return mRealVideoSource->start();
+}
+
+status_t VideoSourceDownSampler::stop() {
+    LOGV("stop");
+    return mRealVideoSource->stop();
+}
+
+sp<MetaData> VideoSourceDownSampler::getFormat() {
+    LOGV("getFormat");
+    return mMeta;
+}
+
+status_t VideoSourceDownSampler::read(
+        MediaBuffer **buffer, const ReadOptions *options) {
+    LOGV("read");
+    MediaBuffer *realBuffer;
+    status_t err = mRealVideoSource->read(&realBuffer, options);
+
+    if (mNeedDownSampling) {
+        downSampleYUVImage(*realBuffer, buffer);
+
+        int64_t frameTime;
+        realBuffer->meta_data()->findInt64(kKeyTime, &frameTime);
+        (*buffer)->meta_data()->setInt64(kKeyTime, frameTime);
+
+        // We just want this buffer to be deleted when the encoder releases it.
+        // So don't add a reference to it and set the observer to NULL.
+        (*buffer)->setObserver(NULL);
+
+        // The original buffer is no longer required. Release it.
+        realBuffer->release();
+    } else {
+        *buffer = realBuffer;
+    }
+
+    return err;
+}
+
+status_t VideoSourceDownSampler::pause() {
+    LOGV("pause");
+    return mRealVideoSource->pause();
+}
+
+}  // namespace android
diff --git a/media/libstagefright/colorconversion/Android.mk b/media/libstagefright/colorconversion/Android.mk
index b9ba1be..2b63235 100644
--- a/media/libstagefright/colorconversion/Android.mk
+++ b/media/libstagefright/colorconversion/Android.mk
@@ -6,7 +6,8 @@
         SoftwareRenderer.cpp
 
 LOCAL_C_INCLUDES := \
-        $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
+        $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
+        $(TOP)/hardware/msm7k
 
 LOCAL_SHARED_LIBRARIES :=       \
         libbinder               \
@@ -17,6 +18,11 @@
         libsurfaceflinger_client\
         libcamera_client
 
+# ifeq ($(TARGET_BOARD_PLATFORM),msm7k)
+ifeq ($(TARGET_PRODUCT),passion)
+	LOCAL_CFLAGS += -DHAS_YCBCR420_SP_ADRENO
+endif
+
 LOCAL_MODULE:= libstagefright_color_conversion
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
index a6dbf69..c204a94 100644
--- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp
+++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
@@ -22,65 +22,179 @@
 #include <binder/MemoryHeapBase.h>
 #include <binder/MemoryHeapPmem.h>
 #include <media/stagefright/MediaDebug.h>
-#include <surfaceflinger/ISurface.h>
+#include <surfaceflinger/Surface.h>
+#include <ui/android_native_buffer.h>
+#include <ui/GraphicBufferMapper.h>
+
+// XXX: Temporary hack to allow referencing the _ADRENO pixel format here.
+#include <libgralloc-qsd8k/gralloc_priv.h>
 
 namespace android {
 
 SoftwareRenderer::SoftwareRenderer(
         OMX_COLOR_FORMATTYPE colorFormat,
-        const sp<ISurface> &surface,
+        const sp<Surface> &surface,
         size_t displayWidth, size_t displayHeight,
         size_t decodedWidth, size_t decodedHeight)
     : mColorFormat(colorFormat),
-      mConverter(colorFormat, OMX_COLOR_Format16bitRGB565),
-      mISurface(surface),
+      mConverter(NULL),
+      mYUVMode(None),
+      mSurface(surface),
       mDisplayWidth(displayWidth),
       mDisplayHeight(displayHeight),
       mDecodedWidth(decodedWidth),
-      mDecodedHeight(decodedHeight),
-      mFrameSize(mDecodedWidth * mDecodedHeight * 2),  // RGB565
-      mIndex(0) {
-    mMemoryHeap = new MemoryHeapBase("/dev/pmem_adsp", 2 * mFrameSize);
-    if (mMemoryHeap->heapID() < 0) {
-        LOGI("Creating physical memory heap failed, reverting to regular heap.");
-        mMemoryHeap = new MemoryHeapBase(2 * mFrameSize);
-    } else {
-        sp<MemoryHeapPmem> pmemHeap = new MemoryHeapPmem(mMemoryHeap);
-        pmemHeap->slap();
-        mMemoryHeap = pmemHeap;
+      mDecodedHeight(decodedHeight) {
+    LOGI("input format = %d", mColorFormat);
+    LOGI("display = %d x %d, decoded = %d x %d",
+            mDisplayWidth, mDisplayHeight, mDecodedWidth, mDecodedHeight);
+
+    int halFormat;
+    switch (mColorFormat) {
+#if HAS_YCBCR420_SP_ADRENO
+        case OMX_COLOR_FormatYUV420Planar:
+        {
+            halFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO;
+            mYUVMode = YUV420ToYUV420sp;
+            break;
+        }
+
+        case 0x7fa30c00:
+        {
+            halFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO;
+            mYUVMode = YUV420spToYUV420sp;
+            break;
+        }
+#endif
+
+        default:
+            halFormat = HAL_PIXEL_FORMAT_RGB_565;
+
+            mConverter = new ColorConverter(
+                    mColorFormat, OMX_COLOR_Format16bitRGB565);
+            CHECK(mConverter->isValid());
+            break;
     }
 
-    CHECK(mISurface.get() != NULL);
+    CHECK(mSurface.get() != NULL);
     CHECK(mDecodedWidth > 0);
     CHECK(mDecodedHeight > 0);
-    CHECK(mMemoryHeap->heapID() >= 0);
-    CHECK(mConverter.isValid());
+    CHECK(mConverter == NULL || mConverter->isValid());
 
-    ISurface::BufferHeap bufferHeap(
-            mDisplayWidth, mDisplayHeight,
-            mDecodedWidth, mDecodedHeight,
-            PIXEL_FORMAT_RGB_565,
-            mMemoryHeap);
+    CHECK_EQ(0,
+            native_window_set_usage(
+            mSurface.get(),
+            GRALLOC_USAGE_SW_READ_NEVER | GRALLOC_USAGE_SW_WRITE_OFTEN
+            | GRALLOC_USAGE_HW_TEXTURE));
 
-    status_t err = mISurface->registerBuffers(bufferHeap);
-    CHECK_EQ(err, OK);
+    CHECK_EQ(0, native_window_set_buffer_count(mSurface.get(), 2));
+
+    // Width must be multiple of 32???
+    CHECK_EQ(0, native_window_set_buffers_geometry(
+                mSurface.get(), mDecodedWidth, mDecodedHeight,
+                halFormat));
 }
 
 SoftwareRenderer::~SoftwareRenderer() {
-    mISurface->unregisterBuffers();
+    delete mConverter;
+    mConverter = NULL;
+}
+
+static inline size_t ALIGN(size_t x, size_t alignment) {
+    return (x + alignment - 1) & ~(alignment - 1);
 }
 
 void SoftwareRenderer::render(
         const void *data, size_t size, void *platformPrivate) {
-    size_t offset = mIndex * mFrameSize;
-    void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
+    android_native_buffer_t *buf;
+    int err;
+    if ((err = mSurface->dequeueBuffer(mSurface.get(), &buf)) != 0) {
+        LOGW("Surface::dequeueBuffer returned error %d", err);
+        return;
+    }
 
-    mConverter.convert(
-            mDecodedWidth, mDecodedHeight,
-            data, 0, dst, 2 * mDecodedWidth);
+    CHECK_EQ(0, mSurface->lockBuffer(mSurface.get(), buf));
 
-    mISurface->postBuffer(offset);
-    mIndex = 1 - mIndex;
+    GraphicBufferMapper &mapper = GraphicBufferMapper::get();
+
+    Rect bounds(mDecodedWidth, mDecodedHeight);
+
+    void *dst;
+    CHECK_EQ(0, mapper.lock(
+                buf->handle, GRALLOC_USAGE_SW_WRITE_OFTEN, bounds, &dst));
+
+    if (mConverter) {
+        mConverter->convert(
+                mDecodedWidth, mDecodedHeight,
+                data, 0, dst, buf->stride * 2);
+    } else if (mYUVMode == YUV420spToYUV420sp) {
+        // Input and output are both YUV420sp, but the alignment requirements
+        // are different.
+        size_t srcYStride = mDecodedWidth;
+        const uint8_t *srcY = (const uint8_t *)data;
+        uint8_t *dstY = (uint8_t *)dst;
+        for (size_t i = 0; i < mDecodedHeight; ++i) {
+            memcpy(dstY, srcY, mDecodedWidth);
+            srcY += srcYStride;
+            dstY += buf->stride;
+        }
+
+        size_t srcUVStride = (mDecodedWidth + 1) & ~1;
+        size_t dstUVStride = ALIGN(mDecodedWidth / 2, 32) * 2;
+
+        const uint8_t *srcUV = (const uint8_t *)data
+            + mDecodedHeight * mDecodedWidth;
+
+        size_t dstUVOffset = ALIGN(ALIGN(mDecodedHeight, 32) * buf->stride, 4096);
+        uint8_t *dstUV = (uint8_t *)dst + dstUVOffset;
+
+        for (size_t i = 0; i < (mDecodedHeight + 1) / 2; ++i) {
+            memcpy(dstUV, srcUV, (mDecodedWidth + 1) & ~1);
+            srcUV += srcUVStride;
+            dstUV += dstUVStride;
+        }
+    } else if (mYUVMode == YUV420ToYUV420sp) {
+        // Input is YUV420 planar, output is YUV420sp, adhere to proper
+        // alignment requirements.
+        size_t srcYStride = mDecodedWidth;
+        const uint8_t *srcY = (const uint8_t *)data;
+        uint8_t *dstY = (uint8_t *)dst;
+        for (size_t i = 0; i < mDecodedHeight; ++i) {
+            memcpy(dstY, srcY, mDecodedWidth);
+            srcY += srcYStride;
+            dstY += buf->stride;
+        }
+
+        size_t srcUVStride = (mDecodedWidth + 1) / 2;
+        size_t dstUVStride = ALIGN(mDecodedWidth / 2, 32) * 2;
+
+        const uint8_t *srcU = (const uint8_t *)data
+            + mDecodedHeight * mDecodedWidth;
+
+        const uint8_t *srcV =
+            srcU + ((mDecodedWidth + 1) / 2) * ((mDecodedHeight + 1) / 2);
+
+        size_t dstUVOffset = ALIGN(ALIGN(mDecodedHeight, 32) * buf->stride, 4096);
+        uint8_t *dstUV = (uint8_t *)dst + dstUVOffset;
+
+        for (size_t i = 0; i < (mDecodedHeight + 1) / 2; ++i) {
+            for (size_t j = 0; j < (mDecodedWidth + 1) / 2; ++j) {
+                dstUV[2 * j + 1] = srcU[j];
+                dstUV[2 * j] = srcV[j];
+            }
+            srcU += srcUVStride;
+            srcV += srcUVStride;
+            dstUV += dstUVStride;
+        }
+    } else {
+        memcpy(dst, data, size);
+    }
+
+    CHECK_EQ(0, mapper.unlock(buf->handle));
+
+    if ((err = mSurface->queueBuffer(mSurface.get(), buf)) != 0) {
+        LOGW("Surface::queueBuffer returned error %d", err);
+    }
+    buf = NULL;
 }
 
 }  // namespace android
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/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 600faca..db98253 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -76,6 +76,7 @@
     bool isPlaying() const;
 
     void setISurface(const sp<ISurface> &isurface);
+    void setSurface(const sp<Surface> &surface);
     void setAudioSink(const sp<MediaPlayerBase::AudioSink> &audioSink);
     status_t setLooping(bool shouldLoop);
 
@@ -121,6 +122,7 @@
     wp<MediaPlayerBase> mListener;
 
     sp<ISurface> mISurface;
+    sp<Surface> mSurface;
     sp<MediaPlayerBase::AudioSink> mAudioSink;
 
     SystemTimeSource mSystemTimeSource;
@@ -223,6 +225,7 @@
     status_t seekTo_l(int64_t timeUs);
     status_t pause_l();
     void initRenderer_l();
+    void notifyVideoSize_l();
     void seekAudioIfNecessary_l();
 
     void cancelPlayerEvents(bool keepBufferingGoing = false);
diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h
index c99da59..83b75ad 100644
--- a/media/libstagefright/include/OMX.h
+++ b/media/libstagefright/include/OMX.h
@@ -59,10 +59,17 @@
             node_id node, OMX_INDEXTYPE index,
             const void *params, size_t size);
 
+    virtual status_t enableGraphicBuffers(
+            node_id node, OMX_U32 port_index, OMX_BOOL enable);
+
     virtual status_t useBuffer(
             node_id node, OMX_U32 port_index, const sp<IMemory> &params,
             buffer_id *buffer);
 
+    virtual status_t useGraphicBuffer(
+            node_id node, OMX_U32 port_index,
+            const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer);
+
     virtual status_t allocateBuffer(
             node_id node, OMX_U32 port_index, size_t size,
             buffer_id *buffer, void **buffer_data);
diff --git a/media/libstagefright/include/OMXNodeInstance.h b/media/libstagefright/include/OMXNodeInstance.h
index b5b31ac..8c7c562 100644
--- a/media/libstagefright/include/OMXNodeInstance.h
+++ b/media/libstagefright/include/OMXNodeInstance.h
@@ -49,10 +49,16 @@
     status_t getConfig(OMX_INDEXTYPE index, void *params, size_t size);
     status_t setConfig(OMX_INDEXTYPE index, const void *params, size_t size);
 
+    status_t enableGraphicBuffers(OMX_U32 portIndex, OMX_BOOL enable);
+
     status_t useBuffer(
             OMX_U32 portIndex, const sp<IMemory> &params,
             OMX::buffer_id *buffer);
 
+    status_t useGraphicBuffer(
+            OMX_U32 portIndex, const sp<GraphicBuffer> &graphicBuffer,
+            OMX::buffer_id *buffer);
+
     status_t allocateBuffer(
             OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer,
             void **buffer_data);
@@ -125,4 +131,3 @@
 }  // namespace android
 
 #endif  // OMX_NODE_INSTANCE_H_
-
diff --git a/media/libstagefright/include/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h
index 9eed089..8d58056 100644
--- a/media/libstagefright/include/SoftwareRenderer.h
+++ b/media/libstagefright/include/SoftwareRenderer.h
@@ -24,14 +24,14 @@
 
 namespace android {
 
-class ISurface;
+class Surface;
 class MemoryHeapBase;
 
 class SoftwareRenderer : public VideoRenderer {
 public:
     SoftwareRenderer(
             OMX_COLOR_FORMATTYPE colorFormat,
-            const sp<ISurface> &surface,
+            const sp<Surface> &surface,
             size_t displayWidth, size_t displayHeight,
             size_t decodedWidth, size_t decodedHeight);
 
@@ -41,14 +41,18 @@
             const void *data, size_t size, void *platformPrivate);
 
 private:
+    enum YUVMode {
+        None,
+        YUV420ToYUV420sp,
+        YUV420spToYUV420sp,
+    };
+
     OMX_COLOR_FORMATTYPE mColorFormat;
-    ColorConverter mConverter;
-    sp<ISurface> mISurface;
+    ColorConverter *mConverter;
+    YUVMode mYUVMode;
+    sp<Surface> mSurface;
     size_t mDisplayWidth, mDisplayHeight;
     size_t mDecodedWidth, mDecodedHeight;
-    size_t mFrameSize;
-    sp<MemoryHeapBase> mMemoryHeap;
-    int mIndex;
 
     SoftwareRenderer(const SoftwareRenderer &);
     SoftwareRenderer &operator=(const SoftwareRenderer &);
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index c927da1..d89f54b 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -289,6 +289,11 @@
             index, params, size);
 }
 
+status_t OMX::enableGraphicBuffers(
+        node_id node, OMX_U32 port_index, OMX_BOOL enable) {
+    return findInstance(node)->enableGraphicBuffers(port_index, enable);
+}
+
 status_t OMX::useBuffer(
         node_id node, OMX_U32 port_index, const sp<IMemory> &params,
         buffer_id *buffer) {
@@ -296,6 +301,13 @@
             port_index, params, buffer);
 }
 
+status_t OMX::useGraphicBuffer(
+        node_id node, OMX_U32 port_index,
+        const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer) {
+    return findInstance(node)->useGraphicBuffer(
+            port_index, graphicBuffer, buffer);
+}
+
 status_t OMX::allocateBuffer(
         node_id node, OMX_U32 port_index, size_t size,
         buffer_id *buffer, void **buffer_data) {
@@ -497,12 +509,17 @@
     }
 
     if (!impl) {
+#if 0
         LOGW("Using software renderer.");
         impl = new SoftwareRenderer(
                 colorFormat,
                 surface,
                 displayWidth, displayHeight,
                 encodedWidth, encodedHeight);
+#else
+        CHECK(!"Should not be here.");
+        return NULL;
+#endif
     }
 
     return new OMXRenderer(impl);
@@ -527,4 +544,3 @@
 }
 
 }  // namespace android
-
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index 5db516e..ba4d765 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -24,6 +24,7 @@
 #include <OMX_Component.h>
 
 #include <binder/IMemory.h>
+#include <media/stagefright/HardwareAPI.h>
 #include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaErrors.h>
 
@@ -40,6 +41,11 @@
           mIsBackup(false) {
     }
 
+    BufferMeta(const sp<GraphicBuffer> &graphicBuffer)
+        : mGraphicBuffer(graphicBuffer),
+          mIsBackup(false) {
+    }
+
     void CopyFromOMX(const OMX_BUFFERHEADERTYPE *header) {
         if (!mIsBackup) {
             return;
@@ -61,6 +67,7 @@
     }
 
 private:
+    sp<GraphicBuffer> mGraphicBuffer;
     sp<IMemory> mMem;
     size_t mSize;
     bool mIsBackup;
@@ -240,6 +247,43 @@
     return StatusFromOMXError(err);
 }
 
+status_t OMXNodeInstance::enableGraphicBuffers(
+        OMX_U32 portIndex, OMX_BOOL enable) {
+    Mutex::Autolock autoLock(mLock);
+
+    OMX_INDEXTYPE index;
+    OMX_ERRORTYPE err = OMX_GetExtensionIndex(
+            mHandle,
+            const_cast<OMX_STRING>("OMX.google.android.index.enableAndroidNativeBuffers"),
+            &index);
+
+    if (err != OMX_ErrorNone) {
+        LOGE("OMX_GetExtensionIndex failed");
+
+        return StatusFromOMXError(err);
+    }
+
+    OMX_VERSIONTYPE ver;
+    ver.s.nVersionMajor = 1;
+    ver.s.nVersionMinor = 0;
+    ver.s.nRevision = 0;
+    ver.s.nStep = 0;
+    EnableAndroidNativeBuffersParams params = {
+        sizeof(EnableAndroidNativeBuffersParams), ver, portIndex, enable,
+    };
+
+    err = OMX_SetParameter(mHandle, index, &params);
+
+    if (err != OMX_ErrorNone) {
+        LOGE("OMX_EnableAndroidNativeBuffers failed with error %d (0x%08x)",
+                err, err);
+
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
 status_t OMXNodeInstance::useBuffer(
         OMX_U32 portIndex, const sp<IMemory> &params,
         OMX::buffer_id *buffer) {
@@ -273,6 +317,60 @@
     return OK;
 }
 
+status_t OMXNodeInstance::useGraphicBuffer(
+        OMX_U32 portIndex, const sp<GraphicBuffer>& graphicBuffer,
+        OMX::buffer_id *buffer) {
+    Mutex::Autolock autoLock(mLock);
+
+    OMX_INDEXTYPE index;
+    OMX_ERRORTYPE err = OMX_GetExtensionIndex(
+            mHandle,
+            const_cast<OMX_STRING>("OMX.google.android.index.useAndroidNativeBuffer"),
+            &index);
+
+    if (err != OMX_ErrorNone) {
+        LOGE("OMX_GetExtensionIndex failed");
+
+        return StatusFromOMXError(err);
+    }
+
+    BufferMeta *bufferMeta = new BufferMeta(graphicBuffer);
+
+    OMX_BUFFERHEADERTYPE *header;
+
+    OMX_VERSIONTYPE ver;
+    ver.s.nVersionMajor = 1;
+    ver.s.nVersionMinor = 0;
+    ver.s.nRevision = 0;
+    ver.s.nStep = 0;
+    UseAndroidNativeBufferParams params = {
+        sizeof(UseAndroidNativeBufferParams), ver, portIndex, bufferMeta,
+        &header, graphicBuffer,
+    };
+
+    err = OMX_SetParameter(mHandle, index, &params);
+
+    if (err != OMX_ErrorNone) {
+        LOGE("OMX_UseAndroidNativeBuffer failed with error %d (0x%08x)", err,
+                err);
+
+        delete bufferMeta;
+        bufferMeta = NULL;
+
+        *buffer = 0;
+
+        return UNKNOWN_ERROR;
+    }
+
+    CHECK_EQ(header->pAppPrivate, bufferMeta);
+
+    *buffer = header;
+
+    addActiveBuffer(portIndex, *buffer);
+
+    return OK;
+}
+
 status_t OMXNodeInstance::allocateBuffer(
         OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer,
         void **buffer_data) {
@@ -498,4 +596,3 @@
 }
 
 }  // namespace android
-
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index 5ec03b2..f928c06 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -133,7 +133,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 0db3595..612caff 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -187,7 +187,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 a31b2b2..09dc156 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -890,7 +890,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..38aa779
--- /dev/null
+++ b/media/libstagefright/yuv/YUVCanvas.cpp
@@ -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.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "YUVCanvas"
+
+#include <media/stagefright/MediaDebug.h>
+#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);
+        }
+    }
+}
+
+void YUVCanvas::downsample(
+        int32_t srcOffsetX, int32_t srcOffsetY,
+        int32_t skipX, int32_t skipY,
+        const YUVImage &srcImage) {
+    // TODO: Add a low pass filter for downsampling.
+
+    // Check that srcImage is big enough to fill mYUVImage.
+    CHECK((srcOffsetX + (mYUVImage.width() - 1) * skipX) < srcImage.width());
+    CHECK((srcOffsetY + (mYUVImage.height() - 1) * skipY) < srcImage.height());
+
+    uint8_t yValue;
+    uint8_t uValue;
+    uint8_t vValue;
+
+    int32_t srcY = srcOffsetY;
+    for (int32_t y = 0; y < mYUVImage.height(); ++y) {
+        int32_t srcX = srcOffsetX;
+        for (int32_t x = 0; x < mYUVImage.width(); ++x) {
+            srcImage.getPixelValue(srcX, srcY, &yValue, &uValue, &vValue);
+            mYUVImage.setPixelValue(x, y, yValue, uValue, vValue);
+
+            srcX += skipX;
+        }
+        srcY += skipY;
+    }
+}
+
+}  // namespace android
diff --git a/media/libstagefright/yuv/YUVImage.cpp b/media/libstagefright/yuv/YUVImage.cpp
new file mode 100644
index 0000000..b712062
--- /dev/null
+++ b/media/libstagefright/yuv/YUVImage.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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::validPixel(int32_t x, int32_t y) const {
+    return (x >= 0 && x < mWidth &&
+            y >= 0 && y < mHeight);
+}
+
+bool YUVImage::getPixelValue(int32_t x, int32_t y,
+        uint8_t *yPtr, uint8_t *uPtr, uint8_t *vPtr) const {
+    CHECK(validPixel(x, y));
+
+    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) {
+    CHECK(validPixel(x, y));
+
+    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..35d90dc
--- /dev/null
+++ b/media/mtp/MtpCursor.cpp
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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,
+                MtpStorageID storageID, MtpObjectHandle 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("fillObject: 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, int64_t 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, MtpObjectHandle objectID,
+                            MtpObjectFormat 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..2e03c29
--- /dev/null
+++ b/media/mtp/MtpCursor.h
@@ -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.
+ */
+
+#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;
+    MtpStorageID    mStorageID;
+    MtpObjectHandle mQbjectID;
+    int             mColumnCount;
+    int*            mColumns;
+
+public:
+                MtpCursor(MtpClient* client, int queryType, int deviceID,
+                        MtpStorageID storageID, MtpObjectHandle 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, int64_t value, int row, int column);
+    bool        putString(CursorWindow* window, const char* text, int row, int column);
+    bool        putThumbnail(CursorWindow* window, MtpObjectHandle objectID,
+                            MtpObjectFormat 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..ec78ff0
--- /dev/null
+++ b/media/mtp/MtpDataPacket.cpp
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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::putInt128(int64_t value) {
+    putInt64(value);
+    putInt64(value < 0 ? -1 : 0);
+}
+
+void MtpDataPacket::putUInt128(uint64_t value) {
+    putUInt64(value);
+    putUInt64(0);
+}
+
+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::putAUInt16(const UInt16List* values) {
+    size_t count = (values ? values->size() : 0);
+    putUInt32(count);
+    for (size_t i = 0; i < count; i++)
+        putUInt16((*values)[i]);
+}
+
+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 > 0 ? count + 1 : 0);
+    for (int i = 0; i < count; i++)
+        putUInt16(string[i]);
+    // only terminate with zero if string is not empty
+    if (count > 0)
+        putUInt16(0);
+}
+
+#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);
+    dump();
+    // 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..fab6a07
--- /dev/null
+++ b/media/mtp/MtpDataPacket.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 _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                putInt128(int64_t value);
+    void                putUInt128(uint64_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                putAUInt16(const UInt16List* values);
+    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() { putUInt8(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..c8cb016
--- /dev/null
+++ b/media/mtp/MtpDatabase.h
@@ -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.
+ */
+
+#ifndef _MTP_DATABASE_H
+#define _MTP_DATABASE_H
+
+#include "MtpTypes.h"
+
+namespace android {
+
+class MtpDataPacket;
+class MtpProperty;
+
+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;
+
+    // callee should delete[] the results from these
+    // results can be NULL
+    virtual MtpObjectFormatList*    getSupportedPlaybackFormats() = 0;
+    virtual MtpObjectFormatList*    getSupportedCaptureFormats() = 0;
+    virtual MtpObjectPropertyList*  getSupportedObjectProperties(MtpObjectFormat format) = 0;
+    virtual MtpDevicePropertyList*  getSupportedDeviceProperties() = 0;
+
+    virtual MtpResponseCode         getObjectPropertyValue(MtpObjectHandle handle,
+                                            MtpObjectProperty property,
+                                            MtpDataPacket& packet) = 0;
+
+    virtual MtpResponseCode         setObjectPropertyValue(MtpObjectHandle handle,
+                                            MtpObjectProperty property,
+                                            MtpDataPacket& packet) = 0;
+
+    virtual MtpResponseCode         getDevicePropertyValue(MtpDeviceProperty property,
+                                            MtpDataPacket& packet) = 0;
+
+    virtual MtpResponseCode         setDevicePropertyValue(MtpDeviceProperty property,
+                                            MtpDataPacket& packet) = 0;
+
+    virtual MtpResponseCode         resetDeviceProperty(MtpDeviceProperty property) = 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;
+
+    virtual MtpProperty*            getObjectPropertyDesc(MtpObjectProperty property,
+                                            MtpObjectFormat format) = 0;
+
+    virtual MtpProperty*            getDevicePropertyDesc(MtpDeviceProperty property) = 0;
+
+    virtual void                    sessionStarted() = 0;
+
+    virtual void                    sessionEnded() = 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..d6b107d
--- /dev/null
+++ b/media/mtp/MtpDebug.cpp
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 CodeEntry {
+    const char* name;
+    uint16_t code;
+};
+
+static const CodeEntry 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      },
+};
+
+static const CodeEntry sFormatCodes[] = {
+    { "MTP_OPERATION_GET_DEVICE_INFO",              0x1001 },
+    { "MTP_FORMAT_UNDEFINED",                       0x3000 },
+    { "MTP_FORMAT_ASSOCIATION",                     0x3001 },
+    { "MTP_FORMAT_SCRIPT",                          0x3002 },
+    { "MTP_FORMAT_EXECUTABLE",                      0x3003 },
+    { "MTP_FORMAT_TEXT",                            0x3004 },
+    { "MTP_FORMAT_HTML",                            0x3005 },
+    { "MTP_FORMAT_DPOF",                            0x3006 },
+    { "MTP_FORMAT_AIFF",                            0x3007 },
+    { "MTP_FORMAT_WAV",                             0x3008 },
+    { "MTP_FORMAT_MP3",                             0x3009 },
+    { "MTP_FORMAT_AVI",                             0x300A },
+    { "MTP_FORMAT_MPEG",                            0x300B },
+    { "MTP_FORMAT_ASF",                             0x300C },
+    { "MTP_FORMAT_DEFINED",                         0x3800 },
+    { "MTP_FORMAT_EXIF_JPEG",                       0x3801 },
+    { "MTP_FORMAT_TIFF_EP",                         0x3802 },
+    { "MTP_FORMAT_FLASHPIX",                        0x3803 },
+    { "MTP_FORMAT_BMP",                             0x3804 },
+    { "MTP_FORMAT_CIFF",                            0x3805 },
+    { "MTP_FORMAT_GIF",                             0x3807 },
+    { "MTP_FORMAT_JFIF",                            0x3808 },
+    { "MTP_FORMAT_CD",                              0x3809 },
+    { "MTP_FORMAT_PICT",                            0x380A },
+    { "MTP_FORMAT_PNG",                             0x380B },
+    { "MTP_FORMAT_TIFF",                            0x380D },
+    { "MTP_FORMAT_TIFF_IT",                         0x380E },
+    { "MTP_FORMAT_JP2",                             0x380F },
+    { "MTP_FORMAT_JPX",                             0x3810 },
+    { "MTP_FORMAT_UNDEFINED_FIRMWARE",              0xB802 },
+    { "MTP_FORMAT_WINDOWS_IMAGE_FORMAT",            0xB881 },
+    { "MTP_FORMAT_UNDEFINED_AUDIO",                 0xB900 },
+    { "MTP_FORMAT_WMA",                             0xB901 },
+    { "MTP_FORMAT_OGG",                             0xB902 },
+    { "MTP_FORMAT_AAC",                             0xB903 },
+    { "MTP_FORMAT_AUDIBLE",                         0xB904 },
+    { "MTP_FORMAT_FLAC",                            0xB906 },
+    { "MTP_FORMAT_UNDEFINED_VIDEO",                 0xB980 },
+    { "MTP_FORMAT_WMV",                             0xB981 },
+    { "MTP_FORMAT_MP4_CONTAINER",                   0xB982 },
+    { "MTP_FORMAT_MP2",                             0xB983 },
+    { "MTP_FORMAT_3GP_CONTAINER",                   0xB984 },
+    { "MTP_FORMAT_UNDEFINED_COLLECTION",            0xBA00 },
+    { "MTP_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM",       0xBA01 },
+    { "MTP_FORMAT_ABSTRACT_IMAGE_ALBUM",            0xBA02 },
+    { "MTP_FORMAT_ABSTRACT_AUDIO_ALBUM",            0xBA03 },
+    { "MTP_FORMAT_ABSTRACT_VIDEO_ALBUM",            0xBA04 },
+    { "MTP_FORMAT_ABSTRACT_AV_PLAYLIST",            0xBA05 },
+    { "MTP_FORMAT_ABSTRACT_CONTACT_GROUP",          0xBA06 },
+    { "MTP_FORMAT_ABSTRACT_MESSAGE_FOLDER",         0xBA07 },
+    { "MTP_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION",   0xBA08 },
+    { "MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST",         0xBA09 },
+    { "MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST",         0xBA0A },
+    { "MTP_FORMAT_ABSTRACT_MEDIACAST",              0xBA0B },
+    { "MTP_FORMAT_WPL_PLAYLIST",                    0xBA10 },
+    { "MTP_FORMAT_M3U_PLAYLIST",                    0xBA11 },
+    { "MTP_FORMAT_MPL_PLAYLIST",                    0xBA12 },
+    { "MTP_FORMAT_ASX_PLAYLIST",                    0xBA13 },
+    { "MTP_FORMAT_PLS_PLAYLIST",                    0xBA14 },
+    { "MTP_FORMAT_UNDEFINED_DOCUMENT",              0xBA80 },
+    { "MTP_FORMAT_ABSTRACT_DOCUMENT",               0xBA81 },
+    { "MTP_FORMAT_XML_DOCUMENT",                    0xBA82 },
+    { "MTP_FORMAT_MS_WORD_DOCUMENT",                0xBA83 },
+    { "MTP_FORMAT_MHT_COMPILED_HTML_DOCUMENT",      0xBA84 },
+    { "MTP_FORMAT_MS_EXCEL_SPREADSHEET",            0xBA85 },
+    { "MTP_FORMAT_MS_POWERPOINT_PRESENTATION",      0xBA86 },
+    { "MTP_FORMAT_UNDEFINED_MESSAGE",               0xBB00 },
+    { "MTP_FORMAT_ABSTRACT_MESSSAGE",               0xBB01 },
+    { "MTP_FORMAT_UNDEFINED_CONTACT",               0xBB80 },
+    { "MTP_FORMAT_ABSTRACT_CONTACT",                0xBB81 },
+    { "MTP_FORMAT_VCARD_2",                         0xBB82 },
+    { 0,                                            0      },
+};
+
+static const CodeEntry sObjectPropCodes[] = {
+    { "MTP_PROPERTY_STORAGE_ID",                             0xDC01 },
+    { "MTP_PROPERTY_OBJECT_FORMAT",                          0xDC02 },
+    { "MTP_PROPERTY_PROTECTION_STATUS",                      0xDC03 },
+    { "MTP_PROPERTY_OBJECT_SIZE",                            0xDC04 },
+    { "MTP_PROPERTY_ASSOCIATION_TYPE",                       0xDC05 },
+    { "MTP_PROPERTY_ASSOCIATION_DESC",                       0xDC06 },
+    { "MTP_PROPERTY_OBJECT_FILE_NAME",                       0xDC07 },
+    { "MTP_PROPERTY_DATE_CREATED",                           0xDC08 },
+    { "MTP_PROPERTY_DATE_MODIFIED",                          0xDC09 },
+    { "MTP_PROPERTY_KEYWORDS",                               0xDC0A },
+    { "MTP_PROPERTY_PARENT_OBJECT",                          0xDC0B },
+    { "MTP_PROPERTY_ALLOWED_FOLDER_CONTENTS",                0xDC0C },
+    { "MTP_PROPERTY_HIDDEN",                                 0xDC0D },
+    { "MTP_PROPERTY_SYSTEM_OBJECT",                          0xDC0E },
+    { "MTP_PROPERTY_PERSISTENT_UID",                         0xDC41 },
+    { "MTP_PROPERTY_SYNC_ID",                                0xDC42 },
+    { "MTP_PROPERTY_PROPERTY_BAG",                           0xDC43 },
+    { "MTP_PROPERTY_NAME",                                   0xDC44 },
+    { "MTP_PROPERTY_CREATED_BY",                             0xDC45 },
+    { "MTP_PROPERTY_ARTIST",                                 0xDC46 },
+    { "MTP_PROPERTY_DATE_AUTHORED",                          0xDC47 },
+    { "MTP_PROPERTY_DESCRIPTION",                            0xDC48 },
+    { "MTP_PROPERTY_URL_REFERENCE",                          0xDC49 },
+    { "MTP_PROPERTY_LANGUAGE_LOCALE",                        0xDC4A },
+    { "MTP_PROPERTY_COPYRIGHT_INFORMATION",                  0xDC4B },
+    { "MTP_PROPERTY_SOURCE",                                 0xDC4C },
+    { "MTP_PROPERTY_ORIGIN_LOCATION",                        0xDC4D },
+    { "MTP_PROPERTY_DATE_ADDED",                             0xDC4E },
+    { "MTP_PROPERTY_NON_CONSUMABLE",                         0xDC4F },
+    { "MTP_PROPERTY_CORRUPT_UNPLAYABLE",                     0xDC50 },
+    { "MTP_PROPERTY_PRODUCER_SERIAL_NUMBER",                 0xDC51 },
+    { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT",           0xDC81 },
+    { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_SIZE",             0xDC82 },
+    { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT",           0xDC83 },
+    { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH",            0xDC84 },
+    { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DURATION",         0xDC85 },
+    { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DATA",             0xDC86 },
+    { "MTP_PROPERTY_WIDTH",                                  0xDC87 },
+    { "MTP_PROPERTY_HEIGHT",                                 0xDC88 },
+    { "MTP_PROPERTY_DURATION",                               0xDC89 },
+    { "MTP_PROPERTY_RATING",                                 0xDC8A },
+    { "MTP_PROPERTY_TRACK",                                  0xDC8B },
+    { "MTP_PROPERTY_GENRE",                                  0xDC8C },
+    { "MTP_PROPERTY_CREDITS",                                0xDC8D },
+    { "MTP_PROPERTY_LYRICS",                                 0xDC8E },
+    { "MTP_PROPERTY_SUBSCRIPTION_CONTENT_ID",                0xDC8F },
+    { "MTP_PROPERTY_PRODUCED_BY",                            0xDC90 },
+    { "MTP_PROPERTY_USE_COUNT",                              0xDC91 },
+    { "MTP_PROPERTY_SKIP_COUNT",                             0xDC92 },
+    { "MTP_PROPERTY_LAST_ACCESSED",                          0xDC93 },
+    { "MTP_PROPERTY_PARENTAL_RATING",                        0xDC94 },
+    { "MTP_PROPERTY_META_GENRE",                             0xDC95 },
+    { "MTP_PROPERTY_COMPOSER",                               0xDC96 },
+    { "MTP_PROPERTY_EFFECTIVE_RATING",                       0xDC97 },
+    { "MTP_PROPERTY_SUBTITLE",                               0xDC98 },
+    { "MTP_PROPERTY_ORIGINAL_RELEASE_DATE",                  0xDC99 },
+    { "MTP_PROPERTY_ALBUM_NAME",                             0xDC9A },
+    { "MTP_PROPERTY_ALBUM_ARTIST",                           0xDC9B },
+    { "MTP_PROPERTY_MOOD",                                   0xDC9C },
+    { "MTP_PROPERTY_DRM_STATUS",                             0xDC9D },
+    { "MTP_PROPERTY_SUB_DESCRIPTION",                        0xDC9E },
+    { "MTP_PROPERTY_IS_CROPPED",                             0xDCD1 },
+    { "MTP_PROPERTY_IS_COLOUR_CORRECTED",                    0xDCD2 },
+    { "MTP_PROPERTY_IMAGE_BIT_DEPTH",                        0xDCD3 },
+    { "MTP_PROPERTY_F_NUMBER",                               0xDCD4 },
+    { "MTP_PROPERTY_EXPOSURE_TIME",                          0xDCD5 },
+    { "MTP_PROPERTY_EXPOSURE_INDEX",                         0xDCD6 },
+    { "MTP_PROPERTY_TOTAL_BITRATE",                          0xDE91 },
+    { "MTP_PROPERTY_BITRATE_TYPE",                           0xDE92 },
+    { "MTP_PROPERTY_SAMPLE_RATE",                            0xDE93 },
+    { "MTP_PROPERTY_NUMBER_OF_CHANNELS",                     0xDE94 },
+    { "MTP_PROPERTY_AUDIO_BIT_DEPTH",                        0xDE95 },
+    { "MTP_PROPERTY_SCAN_TYPE",                              0xDE97 },
+    { "MTP_PROPERTY_AUDIO_WAVE_CODEC",                       0xDE99 },
+    { "MTP_PROPERTY_AUDIO_BITRATE",                          0xDE9A },
+    { "MTP_PROPERTY_VIDEO_FOURCC_CODEC",                     0xDE9B },
+    { "MTP_PROPERTY_VIDEO_BITRATE",                          0xDE9C },
+    { "MTP_PROPERTY_FRAMES_PER_THOUSAND_SECONDS",            0xDE9D },
+    { "MTP_PROPERTY_KEYFRAME_DISTANCE",                      0xDE9E },
+    { "MTP_PROPERTY_BUFFER_SIZE",                            0xDE9F },
+    { "MTP_PROPERTY_ENCODING_QUALITY",                       0xDEA0 },
+    { "MTP_PROPERTY_ENCODING_PROFILE",                       0xDEA1 },
+    { "MTP_PROPERTY_DISPLAY_NAME",                           0xDCE0 },
+    { "MTP_PROPERTY_BODY_TEXT",                              0xDCE1 },
+    { "MTP_PROPERTY_SUBJECT",                                0xDCE2 },
+    { "MTP_PROPERTY_PRIORITY",                               0xDCE3 },
+    { "MTP_PROPERTY_GIVEN_NAME",                             0xDD00 },
+    { "MTP_PROPERTY_MIDDLE_NAMES",                           0xDD01 },
+    { "MTP_PROPERTY_FAMILY_NAME",                            0xDD02 },
+    { "MTP_PROPERTY_PREFIX",                                 0xDD03 },
+    { "MTP_PROPERTY_SUFFIX",                                 0xDD04 },
+    { "MTP_PROPERTY_PHONETIC_GIVEN_NAME",                    0xDD05 },
+    { "MTP_PROPERTY_PHONETIC_FAMILY_NAME",                   0xDD06 },
+    { "MTP_PROPERTY_EMAIL_PRIMARY",                          0xDD07 },
+    { "MTP_PROPERTY_EMAIL_PERSONAL_1",                       0xDD08 },
+    { "MTP_PROPERTY_EMAIL_PERSONAL_2",                       0xDD09 },
+    { "MTP_PROPERTY_EMAIL_BUSINESS_1",                       0xDD0A },
+    { "MTP_PROPERTY_EMAIL_BUSINESS_2",                       0xDD0B },
+    { "MTP_PROPERTY_EMAIL_OTHERS",                           0xDD0C },
+    { "MTP_PROPERTY_PHONE_NUMBER_PRIMARY",                   0xDD0D },
+    { "MTP_PROPERTY_PHONE_NUMBER_PERSONAL",                  0xDD0E },
+    { "MTP_PROPERTY_PHONE_NUMBER_PERSONAL_2",                0xDD0F },
+    { "MTP_PROPERTY_PHONE_NUMBER_BUSINESS",                  0xDD10 },
+    { "MTP_PROPERTY_PHONE_NUMBER_BUSINESS_2",                0xDD11 },
+    { "MTP_PROPERTY_PHONE_NUMBER_MOBILE",                    0xDD12 },
+    { "MTP_PROPERTY_PHONE_NUMBER_MOBILE_2",                  0xDD13 },
+    { "MTP_PROPERTY_FAX_NUMBER_PRIMARY",                     0xDD14 },
+    { "MTP_PROPERTY_FAX_NUMBER_PERSONAL",                    0xDD15 },
+    { "MTP_PROPERTY_FAX_NUMBER_BUSINESS",                    0xDD16 },
+    { "MTP_PROPERTY_PAGER_NUMBER",                           0xDD17 },
+    { "MTP_PROPERTY_PHONE_NUMBER_OTHERS",                    0xDD18 },
+    { "MTP_PROPERTY_PRIMARY_WEB_ADDRESS",                    0xDD19 },
+    { "MTP_PROPERTY_PERSONAL_WEB_ADDRESS",                   0xDD1A },
+    { "MTP_PROPERTY_BUSINESS_WEB_ADDRESS",                   0xDD1B },
+    { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS",              0xDD1C },
+    { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_2",            0xDD1D },
+    { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_3",            0xDD1E },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL",           0xDD1F },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1",         0xDD20 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2",         0xDD21 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY",           0xDD22 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION",         0xDD23 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE",    0xDD24 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY",        0xDD25 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL",           0xDD26 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1",         0xDD27 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2",         0xDD28 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY",           0xDD29 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION",         0xDD2A },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE",    0xDD2B },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY",        0xDD2C },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_FULL",              0xDD2D },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1",            0xDD2E },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2",            0xDD2F },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_CITY",              0xDD30 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_REGION",            0xDD31 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE",       0xDD32 },
+    { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY",           0xDD33 },
+    { "MTP_PROPERTY_ORGANIZATION_NAME",                      0xDD34 },
+    { "MTP_PROPERTY_PHONETIC_ORGANIZATION_NAME",             0xDD35 },
+    { "MTP_PROPERTY_ROLE",                                   0xDD36 },
+    { "MTP_PROPERTY_BIRTHDATE",                              0xDD37 },
+    { "MTP_PROPERTY_MESSAGE_TO",                             0xDD40 },
+    { "MTP_PROPERTY_MESSAGE_CC",                             0xDD41 },
+    { "MTP_PROPERTY_MESSAGE_BCC",                            0xDD42 },
+    { "MTP_PROPERTY_MESSAGE_READ",                           0xDD43 },
+    { "MTP_PROPERTY_MESSAGE_RECEIVED_TIME",                  0xDD44 },
+    { "MTP_PROPERTY_MESSAGE_SENDER",                         0xDD45 },
+    { "MTP_PROPERTY_ACTIVITY_BEGIN_TIME",                    0xDD50 },
+    { "MTP_PROPERTY_ACTIVITY_END_TIME",                      0xDD51 },
+    { "MTP_PROPERTY_ACTIVITY_LOCATION",                      0xDD52 },
+    { "MTP_PROPERTY_ACTIVITY_REQUIRED_ATTENDEES",            0xDD54 },
+    { "MTP_PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES",            0xDD55 },
+    { "MTP_PROPERTY_ACTIVITY_RESOURCES",                     0xDD56 },
+    { "MTP_PROPERTY_ACTIVITY_ACCEPTED",                      0xDD57 },
+    { "MTP_PROPERTY_ACTIVITY_TENTATIVE",                     0xDD58 },
+    { "MTP_PROPERTY_ACTIVITY_DECLINED",                      0xDD59 },
+    { "MTP_PROPERTY_ACTIVITY_REMAINDER_TIME",                0xDD5A },
+    { "MTP_PROPERTY_ACTIVITY_OWNER",                         0xDD5B },
+    { "MTP_PROPERTY_ACTIVITY_STATUS",                        0xDD5C },
+    { "MTP_PROPERTY_OWNER",                                  0xDD5D },
+    { "MTP_PROPERTY_EDITOR",                                 0xDD5E },
+    { "MTP_PROPERTY_WEBMASTER",                              0xDD5F },
+    { "MTP_PROPERTY_URL_SOURCE",                             0xDD60 },
+    { "MTP_PROPERTY_URL_DESTINATION",                        0xDD61 },
+    { "MTP_PROPERTY_TIME_BOOKMARK",                          0xDD62 },
+    { "MTP_PROPERTY_OBJECT_BOOKMARK",                        0xDD63 },
+    { "MTP_PROPERTY_BYTE_BOOKMARK",                          0xDD64 },
+    { "MTP_PROPERTY_LAST_BUILD_DATE",                        0xDD70 },
+    { "MTP_PROPERTY_TIME_TO_LIVE",                           0xDD71 },
+    { "MTP_PROPERTY_MEDIA_GUID",                             0xDD72 },
+    { 0,                                                     0      },
+};
+
+static const CodeEntry sDevicePropCodes[] = {
+    { "MTP_DEVICE_PROPERTY_UNDEFINED",                       0x5000 },
+    { "MTP_DEVICE_PROPERTY_BATTERY_LEVEL",                   0x5001 },
+    { "MTP_DEVICE_PROPERTY_FUNCTIONAL_MODE",                 0x5002 },
+    { "MTP_DEVICE_PROPERTY_IMAGE_SIZE",                      0x5003 },
+    { "MTP_DEVICE_PROPERTY_COMPRESSION_SETTING",             0x5004 },
+    { "MTP_DEVICE_PROPERTY_WHITE_BALANCE",                   0x5005 },
+    { "MTP_DEVICE_PROPERTY_RGB_GAIN",                        0x5006 },
+    { "MTP_DEVICE_PROPERTY_F_NUMBER",                        0x5007 },
+    { "MTP_DEVICE_PROPERTY_FOCAL_LENGTH",                    0x5008 },
+    { "MTP_DEVICE_PROPERTY_FOCUS_DISTANCE",                  0x5009 },
+    { "MTP_DEVICE_PROPERTY_FOCUS_MODE",                      0x500A },
+    { "MTP_DEVICE_PROPERTY_EXPOSURE_METERING_MODE",          0x500B },
+    { "MTP_DEVICE_PROPERTY_FLASH_MODE",                      0x500C },
+    { "MTP_DEVICE_PROPERTY_EXPOSURE_TIME",                   0x500D },
+    { "MTP_DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE",           0x500E },
+    { "MTP_DEVICE_PROPERTY_EXPOSURE_INDEX",                  0x500F },
+    { "MTP_DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION",      0x5010 },
+    { "MTP_DEVICE_PROPERTY_DATETIME",                        0x5011 },
+    { "MTP_DEVICE_PROPERTY_CAPTURE_DELAY",                   0x5012 },
+    { "MTP_DEVICE_PROPERTY_STILL_CAPTURE_MODE",              0x5013 },
+    { "MTP_DEVICE_PROPERTY_CONTRAST",                        0x5014 },
+    { "MTP_DEVICE_PROPERTY_SHARPNESS",                       0x5015 },
+    { "MTP_DEVICE_PROPERTY_DIGITAL_ZOOM",                    0x5016 },
+    { "MTP_DEVICE_PROPERTY_EFFECT_MODE",                     0x5017 },
+    { "MTP_DEVICE_PROPERTY_BURST_NUMBER",                    0x5018 },
+    { "MTP_DEVICE_PROPERTY_BURST_INTERVAL",                  0x5019 },
+    { "MTP_DEVICE_PROPERTY_TIMELAPSE_NUMBER",                0x501A },
+    { "MTP_DEVICE_PROPERTY_TIMELAPSE_INTERVAL",              0x501B },
+    { "MTP_DEVICE_PROPERTY_FOCUS_METERING_MODE",             0x501C },
+    { "MTP_DEVICE_PROPERTY_UPLOAD_URL",                      0x501D },
+    { "MTP_DEVICE_PROPERTY_ARTIST",                          0x501E },
+    { "MTP_DEVICE_PROPERTY_COPYRIGHT_INFO",                  0x501F },
+    { "MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER",         0xD401 },
+    { "MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME",            0xD402 },
+    { "MTP_DEVICE_PROPERTY_VOLUME",                          0xD403 },
+    { "MTP_DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED",       0xD404 },
+    { "MTP_DEVICE_PROPERTY_DEVICE_ICON",                     0xD405 },
+    { "MTP_DEVICE_PROPERTY_PLAYBACK_RATE",                   0xD410 },
+    { "MTP_DEVICE_PROPERTY_PLAYBACK_OBJECT",                 0xD411 },
+    { "MTP_DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX",        0xD412 },
+    { "MTP_DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO",  0xD406 },
+    { "MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE",           0xD407 },
+    { 0,                                                     0      },
+};
+
+static const char* getCodeName(uint16_t code, const CodeEntry* table) {
+    const CodeEntry* entry = table;
+    while (entry->name) {
+        if (entry->code == code)
+            return entry->name;
+        entry++;
+    }
+    return "UNKNOWN";
+}
+
+const char* MtpDebug::getOperationCodeName(MtpOperationCode code) {
+    return getCodeName(code, sOperationCodes);
+}
+
+const char* MtpDebug::getFormatCodeName(MtpOperationCode code) {
+    return getCodeName(code, sFormatCodes);
+}
+
+const char* MtpDebug::getObjectPropCodeName(MtpPropertyCode code) {
+    return getCodeName(code, sObjectPropCodes);
+}
+
+const char* MtpDebug::getDevicePropCodeName(MtpPropertyCode code) {
+    return getCodeName(code, sDevicePropCodes);
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpDebug.h b/media/mtp/MtpDebug.h
new file mode 100644
index 0000000..5b53e31
--- /dev/null
+++ b/media/mtp/MtpDebug.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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);
+    static const char* getFormatCodeName(MtpObjectFormat code);
+    static const char* getObjectPropCodeName(MtpPropertyCode code);
+    static const char* getDevicePropCodeName(MtpPropertyCode 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..fca0142
--- /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);
+        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..ea68c3b
--- /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: %ld mDateModified: %ld 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..bbd0237
--- /dev/null
+++ b/media/mtp/MtpProperty.cpp
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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),
+        mGroupCode(0),
+        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));
+}
+
+MtpProperty::MtpProperty(MtpPropertyCode propCode,
+                         MtpDataType type,
+                         bool writeable,
+                         int defaultValue)
+    :   mCode(propCode),
+        mType(type),
+        mWriteable(writeable),
+        mDefaultArrayLength(0),
+        mDefaultArrayValues(NULL),
+        mCurrentArrayLength(0),
+        mCurrentArrayValues(NULL),
+        mGroupCode(0),
+        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.u.i8 = defaultValue;
+                break;
+            case MTP_TYPE_UINT8:
+                mDefaultValue.u.u8 = defaultValue;
+                break;
+            case MTP_TYPE_INT16:
+                mDefaultValue.u.i16 = defaultValue;
+                break;
+            case MTP_TYPE_UINT16:
+                mDefaultValue.u.u16 = defaultValue;
+                break;
+            case MTP_TYPE_INT32:
+                mDefaultValue.u.i32 = defaultValue;
+                break;
+            case MTP_TYPE_UINT32:
+                mDefaultValue.u.u32 = defaultValue;
+                break;
+            case MTP_TYPE_INT64:
+                mDefaultValue.u.i64 = defaultValue;
+                break;
+            case MTP_TYPE_UINT64:
+                mDefaultValue.u.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 = isDeviceProperty();
+
+    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);
+            if (deviceProp)
+                mCurrentArrayValues = readArrayValues(packet, mCurrentArrayLength);
+            break;
+        default:
+            readValue(packet, mDefaultValue);
+            if (deviceProp)
+                readValue(packet, mCurrentValue);
+    }
+    if (!deviceProp)
+        mGroupCode = packet.getUInt32();
+    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]);
+    }
+}
+
+void MtpProperty::write(MtpDataPacket& packet) {
+    bool deviceProp = isDeviceProperty();
+
+    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);
+            if (deviceProp)
+                writeArrayValues(packet, mCurrentArrayValues, mCurrentArrayLength);
+            break;
+        default:
+            writeValue(packet, mDefaultValue);
+            if (deviceProp)
+                writeValue(packet, mCurrentValue);
+    }
+    packet.putUInt32(mGroupCode);
+    if (!deviceProp)
+        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::setDefaultValue(const uint16_t* string) {
+    free(mDefaultValue.str);
+    if (string) {
+        MtpStringBuffer buffer(string);
+        mDefaultValue.str = strdup(buffer);
+    }
+    else
+        mDefaultValue.str = NULL;
+}
+
+void MtpProperty::setCurrentValue(const uint16_t* string) {
+    free(mCurrentValue.str);
+    if (string) {
+        MtpStringBuffer buffer(string);
+        mCurrentValue.str = strdup(buffer);
+    }
+    else
+        mCurrentValue.str = NULL;
+}
+
+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.u.i8 = packet.getInt8();
+            break;
+        case MTP_TYPE_UINT8:
+        case MTP_TYPE_AUINT8:
+            value.u.u8 = packet.getUInt8();
+            break;
+        case MTP_TYPE_INT16:
+        case MTP_TYPE_AINT16:
+            value.u.i16 = packet.getInt16();
+            break;
+        case MTP_TYPE_UINT16:
+        case MTP_TYPE_AUINT16:
+            value.u.u16 = packet.getUInt16();
+            break;
+        case MTP_TYPE_INT32:
+        case MTP_TYPE_AINT32:
+            value.u.i32 = packet.getInt32();
+            break;
+        case MTP_TYPE_UINT32:
+        case MTP_TYPE_AUINT32:
+            value.u.u32 = packet.getUInt32();
+            break;
+        case MTP_TYPE_INT64:
+        case MTP_TYPE_AINT64:
+            value.u.i64 = packet.getInt64();
+            break;
+        case MTP_TYPE_UINT64:
+        case MTP_TYPE_AUINT64:
+            value.u.u64 = packet.getUInt64();
+            break;
+        case MTP_TYPE_INT128:
+        case MTP_TYPE_AINT128:
+            packet.getInt128(value.u.i128);
+            break;
+        case MTP_TYPE_UINT128:
+        case MTP_TYPE_AUINT128:
+            packet.getUInt128(value.u.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.u.i8);
+            break;
+        case MTP_TYPE_UINT8:
+        case MTP_TYPE_AUINT8:
+            packet.putUInt8(value.u.u8);
+            break;
+        case MTP_TYPE_INT16:
+        case MTP_TYPE_AINT16:
+            packet.putInt16(value.u.i16);
+            break;
+        case MTP_TYPE_UINT16:
+        case MTP_TYPE_AUINT16:
+            packet.putUInt16(value.u.u16);
+            break;
+        case MTP_TYPE_INT32:
+        case MTP_TYPE_AINT32:
+            packet.putInt32(value.u.i32);
+            break;
+        case MTP_TYPE_UINT32:
+        case MTP_TYPE_AUINT32:
+            packet.putUInt32(value.u.u32);
+            break;
+        case MTP_TYPE_INT64:
+        case MTP_TYPE_AINT64:
+            packet.putInt64(value.u.i64);
+            break;
+        case MTP_TYPE_UINT64:
+        case MTP_TYPE_AUINT64:
+            packet.putUInt64(value.u.u64);
+            break;
+        case MTP_TYPE_INT128:
+        case MTP_TYPE_AINT128:
+            packet.putInt128(value.u.i128);
+            break;
+        case MTP_TYPE_UINT128:
+        case MTP_TYPE_AUINT128:
+            packet.putUInt128(value.u.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..98b465a
--- /dev/null
+++ b/media/mtp/MtpProperty.h
@@ -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.
+ */
+
+#ifndef _MTP_PROPERTY_H
+#define _MTP_PROPERTY_H
+
+#include "MtpTypes.h"
+
+namespace android {
+
+class MtpDataPacket;
+
+struct MtpPropertyValue {
+    union {
+        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;
+    } u;
+    // string in UTF8 format
+    char*               str;
+};
+
+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,
+    };
+
+    uint32_t            mGroupCode;
+    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);
+    void                write(MtpDataPacket& packet);
+
+    void                setDefaultValue(const uint16_t* string);
+    void                setCurrentValue(const uint16_t* string);
+
+    void                print();
+
+    inline bool         isDeviceProperty() const {
+                            return (   ((mCode & 0xF000) == 0x5000)
+                                    || ((mCode & 0xF800) == 0xD000));
+                        }
+
+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..84a3e2c
--- /dev/null
+++ b/media/mtp/MtpServer.cpp
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <sys/stat.h>
+#include <dirent.h>
+
+#include <cutils/properties.h>
+
+#define LOG_TAG "MtpServer"
+
+#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,
+};
+
+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)
+{
+}
+
+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
+                    || operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
+                    || operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);
+        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:");
+                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");
+        }
+    }
+
+    if (mSessionOpen)
+        mDatabase->sessionEnded();
+}
+
+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);
+    }
+}
+
+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_SET_OBJECT_PROP_VALUE:
+            response = doSetObjectPropValue();
+            break;
+        case MTP_OPERATION_GET_DEVICE_PROP_VALUE:
+            response = doGetDevicePropValue();
+            break;
+        case MTP_OPERATION_SET_DEVICE_PROP_VALUE:
+            response = doSetDevicePropValue();
+            break;
+        case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
+            response = doResetDevicePropValue();
+            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;
+        case MTP_OPERATION_GET_DEVICE_PROP_DESC:
+            response = doGetDevicePropDesc();
+            break;
+        default:
+            LOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
+            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];
+
+    MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats();
+    MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats();
+    MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties();
+
+    // 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.putAUInt16(deviceProperties); // Device Properties Supported
+    mData.putAUInt16(captureFormats); // Capture Formats
+    mData.putAUInt16(playbackFormats);  // 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
+
+    delete playbackFormats;
+    delete captureFormats;
+    delete deviceProperties;
+
+    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;
+
+    mDatabase->sessionStarted();
+
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doCloseSession() {
+    if (!mSessionOpen)
+        return MTP_RESPONSE_SESSION_NOT_OPEN;
+    mSessionID = 0;
+    mSessionOpen = false;
+    mDatabase->sessionEnded();
+    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);
+    MtpDevicePropertyList* properties = mDatabase->getSupportedObjectProperties(format);
+    mData.putAUInt16(properties);
+    delete properties;
+    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);
+
+    // FIXME - check for invalid object handle
+    MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle);
+    if (handles) {
+        mData.putAUInt32(handles);
+        delete handles;
+    } else {
+        mData.putEmptyArray();
+    }
+    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);
+    LOGD("GetObjectPropValue %d %s\n", handle,
+            MtpDebug::getObjectPropCodeName(property));
+
+    return mDatabase->getObjectPropertyValue(handle, property, mData);
+}
+
+MtpResponseCode MtpServer::doSetObjectPropValue() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    MtpObjectProperty property = mRequest.getParameter(2);
+    LOGD("SetObjectPropValue %d %s\n", handle,
+            MtpDebug::getObjectPropCodeName(property));
+
+    return mDatabase->setObjectPropertyValue(handle, property, mData);
+}
+
+MtpResponseCode MtpServer::doGetDevicePropValue() {
+    MtpDeviceProperty property = mRequest.getParameter(1);
+    LOGD("GetDevicePropValue %s\n",
+            MtpDebug::getDevicePropCodeName(property));
+
+    return mDatabase->getDevicePropertyValue(property, mData);
+}
+
+MtpResponseCode MtpServer::doSetDevicePropValue() {
+    MtpDeviceProperty property = mRequest.getParameter(1);
+    LOGD("SetDevicePropValue %s\n",
+            MtpDebug::getDevicePropCodeName(property));
+
+    return mDatabase->setDevicePropertyValue(property, mData);
+}
+
+MtpResponseCode MtpServer::doResetDevicePropValue() {
+    MtpDeviceProperty property = mRequest.getParameter(1);
+    LOGD("ResetDevicePropValue %s\n",
+            MtpDebug::getDevicePropCodeName(property));
+
+    return mDatabase->resetDeviceProperty(property);
+}
+
+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);
+    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;
+}
+
+static void deleteRecursive(const char* path) {
+    char pathbuf[PATH_MAX];
+    int pathLength = strlen(path);
+    if (pathLength >= sizeof(pathbuf) - 1) {
+        LOGE("path too long: %s\n", path);
+    }
+    strcpy(pathbuf, path);
+    if (pathbuf[pathLength - 1] != '/') {
+        pathbuf[pathLength++] = '/';
+    }
+    char* fileSpot = pathbuf + pathLength;
+    int pathRemaining = sizeof(pathbuf) - pathLength - 1;
+
+    DIR* dir = opendir(path);
+    if (!dir) {
+        LOGE("opendir %s failed: %s", path, strerror(errno));
+        return;
+    }
+
+    struct dirent* entry;
+    while ((entry = readdir(dir))) {
+        const char* name = entry->d_name;
+
+        // ignore "." and ".."
+        if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
+            continue;
+        }
+
+        int nameLength = strlen(name);
+        if (nameLength > pathRemaining) {
+            LOGE("path %s/%s too long\n", path, name);
+            continue;
+        }
+        strcpy(fileSpot, name);
+
+        int type = entry->d_type;
+        if (entry->d_type == DT_DIR) {
+            deleteRecursive(pathbuf);
+            rmdir(pathbuf);
+        } else {
+            unlink(pathbuf);
+        }
+    }
+}
+
+static void deletePath(const char* path) {
+    struct stat statbuf;
+    if (stat(path, &statbuf) == 0) {
+        if (S_ISDIR(statbuf.st_mode)) {
+            deleteRecursive(path);
+            rmdir(path);
+        } else {
+            unlink(path);
+        }
+    } else {
+        LOGE("deletePath stat failed for %s: %s", path, strerror(errno));
+    }
+}
+
+MtpResponseCode MtpServer::doDeleteObject() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    MtpObjectFormat format = mRequest.getParameter(2);
+    // FIXME - support deleting all objects if handle is 0xFFFFFFFF
+    // FIXME - implement deleting objects by format
+
+    MtpString filePath;
+    int64_t fileLength;
+    int result = mDatabase->getObjectFilePath(handle, filePath, fileLength);
+    if (result == MTP_RESPONSE_OK) {
+        LOGV("deleting %s", (const char *)filePath);
+        deletePath((const char *)filePath);
+        return mDatabase->deleteFile(handle);
+    } else {
+        return result;
+    }
+}
+
+MtpResponseCode MtpServer::doGetObjectPropDesc() {
+    MtpObjectProperty propCode = mRequest.getParameter(1);
+    MtpObjectFormat format = mRequest.getParameter(2);
+    LOGD("GetObjectPropDesc %s %s\n", MtpDebug::getObjectPropCodeName(propCode),
+                                        MtpDebug::getFormatCodeName(format));
+    MtpProperty* property = mDatabase->getObjectPropertyDesc(propCode, format);
+    if (!property)
+        return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+    property->write(mData);
+    delete property;
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetDevicePropDesc() {
+    MtpDeviceProperty propCode = mRequest.getParameter(1);
+    LOGD("GetDevicePropDesc %s\n", MtpDebug::getDevicePropCodeName(propCode));
+    MtpProperty* property = mDatabase->getDevicePropertyDesc(propCode);
+    if (!property)
+        return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+    property->write(mData);
+    delete property;
+    return MTP_RESPONSE_OK;
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
new file mode 100644
index 0000000..68a6564
--- /dev/null
+++ b/media/mtp/MtpServer.h
@@ -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.
+ */
+
+#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 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;
+
+    // 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();
+
+    void                sendObjectAdded(MtpObjectHandle handle);
+    void                sendObjectRemoved(MtpObjectHandle handle);
+
+private:
+    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     doSetObjectPropValue();
+    MtpResponseCode     doGetDevicePropValue();
+    MtpResponseCode     doSetDevicePropValue();
+    MtpResponseCode     doResetDevicePropValue();
+    MtpResponseCode     doGetObjectInfo();
+    MtpResponseCode     doGetObject();
+    MtpResponseCode     doSendObjectInfo();
+    MtpResponseCode     doSendObject();
+    MtpResponseCode     doDeleteObject();
+    MtpResponseCode     doGetObjectPropDesc();
+    MtpResponseCode     doGetDevicePropDesc();
+};
+
+}; // 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..fe8cf04
--- /dev/null
+++ b/media/mtp/MtpStringBuffer.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 uint16_t* 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::set(const uint16_t* src) {
+    int count = 0;
+    uint16_t ch;
+    uint8_t* dest = mBuffer;
+
+    while ((ch = *src++) != 0 && count < 255) {
+        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;
+        }
+        count++;
+    }
+    *dest++ = 0;
+    mCharCount = count;
+    mByteCount = dest - mBuffer;
+}
+
+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 > 0 ? count + 1 : 0);
+
+    // 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);
+    }
+    // only terminate with zero if string is not empty
+    if (count > 0)
+        packet->putUInt16(0);
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpStringBuffer.h b/media/mtp/MtpStringBuffer.h
new file mode 100644
index 0000000..cbc8307
--- /dev/null
+++ b/media/mtp/MtpStringBuffer.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_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:
+    // mBuffer contains string in UTF8 format
+    // 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 uint16_t* src);
+                    MtpStringBuffer(const MtpStringBuffer& src);
+    virtual         ~MtpStringBuffer();
+
+    void            set(const char* src);
+    void            set(const uint16_t* 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..720c854
--- /dev/null
+++ b/media/mtp/MtpTypes.h
@@ -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.
+ */
+
+#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;
+
+// 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 MtpObjectPropertyList;
+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..b7afa66
--- /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        0x0001
+#define MTP_STORAGE_READ_ONLY_WITH_DELETE           0x0002
+
+// 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..1d81129
--- /dev/null
+++ b/media/tests/CameraBrowser/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+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..ac210b9
--- /dev/null
+++ b/media/tests/CameraBrowser/res/layout/object_info.xml
@@ -0,0 +1,157 @@
+<?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/format_label"
+            android:text="@string/format_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/format"
+            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..cd477f1
--- /dev/null
+++ b/media/tests/CameraBrowser/res/values/strings.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.
+-->
+
+<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="format_label">Format: </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..6d34fd4
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.media.MtpConstants;
+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 long mStorageID;
+    private long 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().getLongExtra("storage", 0);
+        mObjectID = getIntent().getLongExtra("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) {
+        long rowID = 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 == MtpConstants.FORMAT_ASSOCIATION) {
+            Intent intent = new Intent(this, ObjectBrowser.class);
+            intent.putExtra("device", mDeviceID);
+            intent.putExtra("storage", mStorageID);
+            intent.putExtra("object", rowID);
+            startActivity(intent);
+        } else {
+            Intent intent = new Intent(this, ObjectViewer.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..9f2f98e
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 long mStorageID;
+    private long 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,
+                        Mtp.Object.FORMAT,
+                        };
+
+    @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().getLongExtra("storage", 0);
+        mObjectID = getIntent().getLongExtra("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);
+                }
+            }
+            view = (TextView)findViewById(R.id.format);
+            view.setText(Long.toHexString(c.getLong(14)).toUpperCase());
+        }
+    }
+
+    @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
+                FileUtils.setPermissions(destDir.getPath(), 0775, Process.myUid(), Process.SDCARD_RW_GID);
+                FileUtils.setPermissions(destFile.getPath(), 0664, Process.myUid(), Process.SDCARD_RW_GID);
+
+                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..63e036e
--- /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", 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..3202cae
--- /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] == (char)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/media/tests/players/invoke_mock_media_player.cpp b/media/tests/players/invoke_mock_media_player.cpp
index b3cc8b6..53308be 100644
--- a/media/tests/players/invoke_mock_media_player.cpp
+++ b/media/tests/players/invoke_mock_media_player.cpp
@@ -26,6 +26,7 @@
 
 using android::INVALID_OPERATION;
 using android::ISurface;
+using android::Surface;
 using android::MediaPlayerBase;
 using android::OK;
 using android::Parcel;
@@ -67,7 +68,8 @@
     }
 
     virtual status_t    setDataSource(int fd, int64_t offset, int64_t length) {return OK;}
-    virtual status_t    setVideoSurface(const sp<ISurface>& surface) {return OK;}
+    virtual status_t    setVideoISurface(const sp<ISurface>& surface) {return OK;}
+    virtual status_t    setVideoSurface(const sp<Surface>& surface) {return OK;}
     virtual status_t    prepare() {return OK;}
     virtual status_t    prepareAsync() {return OK;}
     virtual status_t    start() {return OK;}
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/libagl/egl.cpp b/opengl/libagl/egl.cpp
index 460b74f..239dc05 100644
--- a/opengl/libagl/egl.cpp
+++ b/opengl/libagl/egl.cpp
@@ -1969,7 +1969,7 @@
     if (egl_display_t::is_valid(dpy) == EGL_FALSE)
         return setError(EGL_BAD_DISPLAY, EGL_FALSE);
     // TODO: eglSwapInterval()
-    return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+    return EGL_TRUE;
 }
 
 // ----------------------------------------------------------------------------
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index 2d1a278..bc944a0 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -59,6 +59,8 @@
         "EGL_KHR_image "
         "EGL_KHR_image_base "
         "EGL_KHR_image_pixmap "
+        "EGL_KHR_gl_texture_2D_image "
+        "EGL_KHR_fence_sync "
         "EGL_ANDROID_image_native_buffer "
         "EGL_ANDROID_swap_rectangle "
         ;
@@ -243,9 +245,23 @@
     EGLImageKHR images[IMPL_NUM_IMPLEMENTATIONS];
 };
 
+struct egl_sync_t : public egl_object_t
+{
+    typedef egl_object_t::LocalRef<egl_sync_t, EGLSyncKHR> Ref;
+
+    egl_sync_t(EGLDisplay dpy, EGLContext context, EGLSyncKHR sync)
+        : dpy(dpy), context(context), sync(sync)
+    {
+    }
+    EGLDisplay dpy;
+    EGLContext context;
+    EGLSyncKHR sync;
+};
+
 typedef egl_surface_t::Ref  SurfaceRef;
 typedef egl_context_t::Ref  ContextRef;
 typedef egl_image_t::Ref    ImageRef;
+typedef egl_sync_t::Ref     SyncRef;
 
 struct tls_t
 {
@@ -483,6 +499,11 @@
     return egl_to_native_cast<egl_image_t>(image);
 }
 
+static inline
+egl_sync_t* get_sync(EGLSyncKHR sync) {
+    return egl_to_native_cast<egl_sync_t>(sync);
+}
+
 static egl_connection_t* validate_display_config(
         EGLDisplay dpy, EGLConfig config,
         egl_display_t const*& dp)
@@ -1790,6 +1811,111 @@
      return EGL_TRUE;
 }
 
+// ----------------------------------------------------------------------------
+// EGL_EGLEXT_VERSION 5
+// ----------------------------------------------------------------------------
+
+
+EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list)
+{
+    EGLContext ctx = eglGetCurrentContext();
+    ContextRef _c(ctx);
+    if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_NO_SYNC_KHR);
+    if (!validate_display_context(dpy, ctx))
+        return EGL_NO_SYNC_KHR;
+    egl_display_t const * const dp = get_display(dpy);
+    egl_context_t * const c = get_context(ctx);
+    EGLSyncKHR result = EGL_NO_SYNC_KHR;
+    if (c->cnx->egl.eglCreateSyncKHR) {
+        EGLSyncKHR sync = c->cnx->egl.eglCreateSyncKHR(
+                dp->disp[c->impl].dpy, type, attrib_list);
+        if (sync == EGL_NO_SYNC_KHR)
+            return sync;
+        result = (egl_sync_t*)new egl_sync_t(dpy, ctx, sync);
+    }
+    return (EGLSyncKHR)result;
+}
+
+EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync)
+{
+    egl_display_t const * const dp = get_display(dpy);
+    if (dp == 0) {
+        return setError(EGL_BAD_DISPLAY, EGL_FALSE);
+    }
+
+    SyncRef _s(sync);
+    if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+    egl_sync_t* syncObject = get_sync(sync);
+
+    EGLContext ctx = syncObject->context;
+    ContextRef _c(ctx);
+    if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_FALSE);
+    if (!validate_display_context(dpy, ctx))
+        return EGL_FALSE;
+
+    egl_context_t * const c = get_context(ctx);
+
+    if (c->cnx->egl.eglDestroySyncKHR) {
+        return c->cnx->egl.eglDestroySyncKHR(
+                dp->disp[c->impl].dpy, syncObject->sync);
+    }
+
+    return EGL_FALSE;
+}
+
+EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout)
+{
+    egl_display_t const * const dp = get_display(dpy);
+    if (dp == 0) {
+        return setError(EGL_BAD_DISPLAY, EGL_FALSE);
+    }
+
+    SyncRef _s(sync);
+    if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+    egl_sync_t* syncObject = get_sync(sync);
+
+    EGLContext ctx = syncObject->context;
+    ContextRef _c(ctx);
+    if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_FALSE);
+    if (!validate_display_context(dpy, ctx))
+        return EGL_FALSE;
+
+    egl_context_t * const c = get_context(ctx);
+
+    if (c->cnx->egl.eglClientWaitSyncKHR) {
+        return c->cnx->egl.eglClientWaitSyncKHR(
+                dp->disp[c->impl].dpy, syncObject->sync, flags, timeout);
+    }
+
+    return EGL_FALSE;
+}
+
+EGLBoolean eglGetSyncAttribKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value)
+{
+    egl_display_t const * const dp = get_display(dpy);
+    if (dp == 0) {
+        return setError(EGL_BAD_DISPLAY, EGL_FALSE);
+    }
+
+    SyncRef _s(sync);
+    if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+    egl_sync_t* syncObject = get_sync(sync);
+
+    EGLContext ctx = syncObject->context;
+    ContextRef _c(ctx);
+    if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_FALSE);
+    if (!validate_display_context(dpy, ctx))
+        return EGL_FALSE;
+
+    egl_context_t * const c = get_context(ctx);
+
+    if (c->cnx->egl.eglGetSyncAttribKHR) {
+        return c->cnx->egl.eglGetSyncAttribKHR(
+                dp->disp[c->impl].dpy, syncObject->sync, attribute, value);
+    }
+
+    return EGL_FALSE;
+}
 
 // ----------------------------------------------------------------------------
 // ANDROID extensions
diff --git a/opengl/libs/EGL/egl_entries.in b/opengl/libs/EGL/egl_entries.in
index 5d89287..63c3c19 100644
--- a/opengl/libs/EGL/egl_entries.in
+++ b/opengl/libs/EGL/egl_entries.in
@@ -51,6 +51,13 @@
 EGL_ENTRY(EGLImageKHR, eglCreateImageKHR,   EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *)
 EGL_ENTRY(EGLBoolean,  eglDestroyImageKHR,  EGLDisplay, EGLImageKHR)
 
+/* EGL_EGLEXT_VERSION 5 */
+
+EGL_ENTRY(EGLSyncKHR,   eglCreateSyncKHR,       EGLDisplay, EGLenum, const EGLint *)
+EGL_ENTRY(EGLBoolean,   eglDestroySyncKHR,      EGLDisplay, EGLSyncKHR)
+EGL_ENTRY(EGLint,       eglClientWaitSyncKHR,   EGLDisplay, EGLSyncKHR, EGLint, EGLTimeKHR)
+EGL_ENTRY(EGLBoolean,   eglGetSyncAttribKHR,    EGLDisplay, EGLSyncKHR, EGLint, EGLint *)
+
 /* ANDROID extensions */
 
 EGL_ENTRY(EGLBoolean, eglSetSwapRectangleANDROID, EGLDisplay, EGLSurface, EGLint, EGLint, EGLint, EGLint)
diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp
index 18dd483..fee4609 100644
--- a/opengl/libs/GLES2/gl2.cpp
+++ b/opengl/libs/GLES2/gl2.cpp
@@ -39,6 +39,8 @@
 #undef CALL_GL_API
 #undef CALL_GL_API_RETURN
 
+#define DEBUG_CALL_GL_API 0
+
 #if USE_FAST_TLS_KEY
 
     #ifdef HAVE_ARM_TLS_REGISTER
@@ -74,10 +76,24 @@
 
     #define API_ENTRY(_api) _api
 
+#if DEBUG_CALL_GL_API
+
     #define CALL_GL_API(_api, ...)                                       \
         gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl;  \
-        _c->_api(__VA_ARGS__)
-    
+        _c->_api(__VA_ARGS__); \
+        GLenum status = GL_NO_ERROR; \
+        while ((status = glGetError()) != GL_NO_ERROR) { \
+            LOGD("[" #_api "] 0x%x", status); \
+        }
+
+#else
+
+    #define CALL_GL_API(_api, ...)                                       \
+        gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl;  \
+        _c->_api(__VA_ARGS__);
+
+#endif
+
     #define CALL_GL_API_RETURN(_api, ...)                                \
         gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl;  \
         return _c->_api(__VA_ARGS__)
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..a069f67
--- /dev/null
+++ b/opengl/tests/gl_perf/fill_common.cpp
@@ -0,0 +1,298 @@
+/*
+ * 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 "fragment_shaders.cpp"
+
+FILE * fOut = NULL;
+void ptSwap();
+
+static char gCurrentTestName[1024];
+static uint32_t gWidth = 0;
+static uint32_t gHeight = 0;
+
+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();
+}
+
+
+static void endTimer(int count) {
+    uint64_t t2 = getTime();
+    double delta = ((double)(t2 - gTime)) / 1000000000;
+    double pixels = (gWidth * gHeight) * count;
+    double mpps = pixels / delta / 1000000;
+    double dc60 = ((double)count) / delta / 60;
+
+    if (fOut) {
+        fprintf(fOut, "%s, %f, %f\r\n", gCurrentTestName, mpps, dc60);
+        fflush(fOut);
+    } else {
+        printf("%s, %f, %f\n", gCurrentTestName, mpps, dc60);
+    }
+    LOGI("%s, %f, %f\r\n", gCurrentTestName, 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"
+    "uniform vec2 u_texOff;\n"
+
+    "void main() {\n"
+    "    v_color = a_color;\n"
+    "    v_tex0 = a_tex0;\n"
+    "    v_tex1 = a_tex1;\n"
+    "    v_tex0.x += u_texOff.x;\n"
+    "    v_tex1.y += u_texOff.y;\n"
+    "    gl_Position = a_pos;\n"
+    "}\n";
+
+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,
+        0.0f,1.0f,
+        1.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 warmup, int pgm, uint32_t passCount) {
+    if (warmup) {
+        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 (uint32_t ct=0; ct < passCount; ct++) {
+        int loc = glGetUniformLocation(pgm, "u_texOff");
+        glUniform2f(loc, ((float)ct) / passCount, ((float)ct) / 2.f / passCount);
+
+        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(passCount);
+}
+
+
+static uint32_t rgb(uint32_t r, uint32_t g, uint32_t b)
+{
+    uint32_t ret = 0xff000000;
+    ret |= r & 0xff;
+    ret |= (g & 0xff) << 8;
+    ret |= (b & 0xff) << 16;
+    return ret;
+}
+
+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] = rgb(x, (((x+y) & 0xff) == 0x7f) * 0xff, y);
+        }
+    }
+    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] = rgb(x << 4, (((x+y) & 0xf) == 0x7) * 0xff, y << 4);
+        }
+    }
+    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);
+    free(m);
+}
+
+static void doSingleTest(uint32_t pgmNum, int tex) {
+    const char *pgmTxt = gFragmentTests[pgmNum]->txt;
+    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, tex);
+    glActiveTexture(GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, tex);
+    glActiveTexture(GL_TEXTURE0);
+
+    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(gCurrentTestName, "%s, %i, %i, 1", gFragmentTests[pgmNum]->name, pgmNum, tex);
+    doLoop(true, pgm, 100);
+    doLoop(false, pgm, 100);
+}
+
diff --git a/opengl/tests/gl_perf/filltest.cpp b/opengl/tests/gl_perf/filltest.cpp
new file mode 100644
index 0000000..3f8faca
--- /dev/null
+++ b/opengl/tests/gl_perf/filltest.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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"
+
+
+bool doTest(uint32_t w, uint32_t h) {
+    gWidth = w;
+    gHeight = h;
+    setupVA();
+    genTextures();
+
+    printf("\nvarColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\n");
+
+    for (uint32_t num = 0; num < gFragmentTestCount; num++) {
+        doSingleTest(num, 2);
+        if (gFragmentTests[num]->texCount) {
+            doSingleTest(num, 1);
+        }
+    }
+
+    exit(0);
+    return true;
+}
diff --git a/opengl/tests/gl_perf/fragment_shaders.cpp b/opengl/tests/gl_perf/fragment_shaders.cpp
new file mode 100644
index 0000000..79d5ead
--- /dev/null
+++ b/opengl/tests/gl_perf/fragment_shaders.cpp
@@ -0,0 +1,139 @@
+
+typedef struct FragmentTestRec {
+	const char * name;
+	uint32_t texCount;
+	const char * txt;
+} FragmentTest;
+
+static FragmentTest fpFill = {
+	"Solid color", 0,
+
+    "precision mediump float;\n"
+    "uniform vec4 u_color;\n"
+    "void main() {\n"
+    "  gl_FragColor = u_color;\n"
+    "}\n"
+};
+
+static FragmentTest fpGradient = {
+	"Solid gradient", 0,
+
+    "precision mediump float;\n"
+    "varying lowp vec4 v_color;\n"
+    "void main() {\n"
+    "  gl_FragColor = v_color;\n"
+    "}\n"
+};
+
+static FragmentTest fpCopyTex = {
+	"Texture copy", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  gl_FragColor = texture2D(u_tex0, v_tex0);\n"
+    "}\n"
+};
+
+static FragmentTest fpCopyTexGamma = {
+	"Texture copy with gamma", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  vec4 t = texture2D(u_tex0, v_tex0);\n"
+    "  t.rgb = pow(t.rgb, vec3(1.4, 1.4, 1.4));\n"
+    "  gl_FragColor = t;\n"
+    "}\n"
+};
+
+static FragmentTest fpTexSpec = {
+	"Texture spec", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  vec4 t = texture2D(u_tex0, v_tex0);\n"
+    "  float simSpec = dot(gl_FragCoord.xyz, gl_FragCoord.xyz);\n"
+    "  simSpec = pow(clamp(simSpec, 0.1, 1.0), 40.0);\n"
+    "  gl_FragColor = t + vec4(simSpec, simSpec, simSpec, simSpec);\n"
+    "}\n"
+};
+
+static FragmentTest fpDepTex = {
+	"Dependent Lookup", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "void main() {\n"
+    "  vec4 t = texture2D(u_tex0, v_tex0);\n"
+    "  t += texture2D(u_tex0, t.xy);\n"
+    "  gl_FragColor = t;\n"
+    "}\n"
+};
+
+static FragmentTest fpModulateConstantTex = {
+	"Texture modulate constant", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "uniform sampler2D u_tex0;\n"
+    "uniform vec4 u_color;\n"
+
+    "void main() {\n"
+    "  lowp vec4 c = texture2D(u_tex0, v_tex0);\n"
+	"  c *= u_color;\n"
+    "  gl_FragColor = c;\n"
+    "}\n"
+};
+
+static FragmentTest fpModulateVaryingTex = {
+	"Texture modulate gradient", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "varying lowp vec4 v_color;\n"
+    "uniform sampler2D u_tex0;\n"
+
+    "void main() {\n"
+    "  lowp vec4 c = texture2D(u_tex0, v_tex0);\n"
+	"  c *= v_color;\n"
+    "  gl_FragColor = c;\n"
+    "}\n"
+};
+
+static FragmentTest fpModulateVaryingConstantTex = {
+	"Texture modulate gradient constant", 1,
+
+    "precision mediump float;\n"
+    "varying vec2 v_tex0;\n"
+    "varying lowp vec4 v_color;\n"
+    "uniform sampler2D u_tex0;\n"
+    "uniform vec4 u_color;\n"
+
+    "void main() {\n"
+    "  lowp vec4 c = texture2D(u_tex0, v_tex0);\n"
+	"  c *= v_color;\n"
+	"  c *= u_color;\n"
+    "  gl_FragColor = c;\n"
+    "}\n"
+};
+
+static FragmentTest *gFragmentTests[] = {
+	&fpFill,
+	&fpGradient,
+	&fpCopyTex,
+	&fpCopyTexGamma,
+   &fpTexSpec,
+   &fpDepTex,
+	&fpModulateConstantTex,
+	&fpModulateVaryingTex,
+	&fpModulateVaryingConstantTex,
+
+};
+
+static const size_t gFragmentTestCount = sizeof(gFragmentTests) / sizeof(gFragmentTests[0]);
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..f993371
--- /dev/null
+++ b/opengl/tests/gl_perfapp/jni/gl_code.cpp
@@ -0,0 +1,103 @@
+// 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.)
+
+
+int pgm;
+
+void ptSwap() {
+}
+
+void doTest() {
+    uint32_t testNum = stateClock >> 2;
+    int texSize = ((stateClock >> 1) & 0x1) + 1;
+
+    if (testNum >= gFragmentTestCount) {
+       LOGI("done\n");
+       if (fOut) {
+           fclose(fOut);
+           fOut = NULL;
+       }
+       done = true;
+       return;
+    }
+
+    // LOGI("doTest %d %d %d\n", texCount, extraMath, testSubState);
+
+//        for (uint32_t num = 0; num < gFragmentTestCount; num++) {
+    doSingleTest(testNum, texSize);
+}
+
+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)
+{
+    gWidth = width;
+    gHeight = height;
+    if (!done) {
+            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(100);
+        }
+        doTest();
+        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/testFramerate/Android.mk b/opengl/tests/testFramerate/Android.mk
new file mode 100644
index 0000000..500abf3
--- /dev/null
+++ b/opengl/tests/testFramerate/Android.mk
@@ -0,0 +1,19 @@
+#########################################################################
+# Test framerate and look for hiccups
+#########################################################################
+
+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 := TestFramerate
+
+include $(BUILD_PACKAGE)
diff --git a/opengl/tests/testFramerate/AndroidManifest.xml b/opengl/tests/testFramerate/AndroidManifest.xml
new file mode 100644
index 0000000..e04342c
--- /dev/null
+++ b/opengl/tests/testFramerate/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.testframerate">
+    <uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8" />
+
+    <application
+            android:label="@string/testFramerate_activity">
+        <activity android:name="TestFramerateActivity"
+                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/testFramerate/res/values/strings.xml b/opengl/tests/testFramerate/res/values/strings.xml
new file mode 100644
index 0000000..e6b3088
--- /dev/null
+++ b/opengl/tests/testFramerate/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="testFramerate_activity">TestFramerate</string>
+
+</resources>
+
diff --git a/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateActivity.java b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateActivity.java
new file mode 100644
index 0000000..cbe279b
--- /dev/null
+++ b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateActivity.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.testframerate;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.io.File;
+
+
+public class TestFramerateActivity extends Activity {
+
+    TestFramerateView mView;
+
+    @Override protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mView = new TestFramerateView(getApplication());
+        setContentView(mView);
+        mView.setFocusableInTouchMode(true);
+    }
+
+    @Override protected void onPause() {
+        super.onPause();
+        mView.onPause();
+    }
+
+    @Override protected void onResume() {
+        super.onResume();
+        mView.onResume();
+    }
+}
diff --git a/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateView.java b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateView.java
new file mode 100644
index 0000000..f3fb5de
--- /dev/null
+++ b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateView.java
@@ -0,0 +1,91 @@
+/*
+ * 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.testframerate;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+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;
+
+import android.opengl.GLES20;
+
+class TestFramerateView extends GLSurfaceView {
+    private static String TAG = "TestFramerateView";
+
+    public TestFramerateView(Context context) {
+        super(context);
+        setEGLContextClientVersion(2);
+        setRenderer(new Renderer());
+    }
+
+    private long mLastTime_us = 0;
+    private long mNumShortFramesElapsed = 0;
+    private void registerTime(long now_us) {
+        long longFrameTime_ms = Integer.parseInt(SystemProperties.get("debug.longframe_ms", "16"));
+        long elapsedTime_us = now_us - mLastTime_us;
+        float fps = 1000000.f / elapsedTime_us;
+        if (mLastTime_us > 0 && elapsedTime_us > longFrameTime_ms*1000) {
+          Log.v(TAG, "Long frame: " + elapsedTime_us/1000.f + " ms (" + fps + " fps)");
+          if (mNumShortFramesElapsed > 0) {
+            Log.v(TAG, "  Short frames since last long frame: " + mNumShortFramesElapsed);
+            mNumShortFramesElapsed = 0;
+          }
+        } else {
+            ++mNumShortFramesElapsed;
+        }
+
+        mLastTime_us = now_us;
+    }
+
+    private class Renderer implements GLSurfaceView.Renderer {
+        public Renderer() {
+        }
+
+
+        public void onDrawFrame(GL10 gl) {
+            long now_us = System.nanoTime() / 1000;
+            registerTime(now_us);
+
+            float red = (now_us % 1000000) / 1000000.f;
+            float green = (now_us % 2000000) / 2000000.f;
+            float blue = (now_us % 3000000) / 3000000.f;
+            GLES20.glClearColor(red, green, blue, 1.0f);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            GLES20.glViewport(0, 0, width, height);
+        }
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+        }
+
+    }
+}
diff --git a/opengl/tests/testLatency/Android.mk b/opengl/tests/testLatency/Android.mk
new file mode 100644
index 0000000..96417c7
--- /dev/null
+++ b/opengl/tests/testLatency/Android.mk
@@ -0,0 +1,20 @@
+#########################################################################
+# Test end-to-end latency.
+#########################################################################
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build activity
+
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SDK_VERSION := 8
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TestLatency
+
+include $(BUILD_PACKAGE)
diff --git a/opengl/tests/testLatency/AndroidManifest.xml b/opengl/tests/testLatency/AndroidManifest.xml
new file mode 100644
index 0000000..741266e
--- /dev/null
+++ b/opengl/tests/testLatency/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.testlatency">
+    <uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8" />
+
+    <application
+            android:label="@string/testLatency_activity">
+        <activity android:name="TestLatencyActivity"
+                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/testLatency/res/values/strings.xml b/opengl/tests/testLatency/res/values/strings.xml
new file mode 100644
index 0000000..0309991
--- /dev/null
+++ b/opengl/tests/testLatency/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="testLatency_activity">TestLatency</string>
+
+</resources>
+
diff --git a/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyActivity.java b/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyActivity.java
new file mode 100644
index 0000000..ed993cb
--- /dev/null
+++ b/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyActivity.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.testlatency;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.io.File;
+
+
+public class TestLatencyActivity extends Activity {
+
+    TestLatencyView mView;
+
+    @Override protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mView = new TestLatencyView(getApplication());
+        setContentView(mView);
+        mView.setFocusableInTouchMode(true);
+    }
+
+    @Override protected void onPause() {
+        super.onPause();
+        mView.onPause();
+    }
+
+    @Override protected void onResume() {
+        super.onResume();
+        mView.onResume();
+    }
+}
diff --git a/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyView.java b/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyView.java
new file mode 100644
index 0000000..d62bf17
--- /dev/null
+++ b/opengl/tests/testLatency/src/com/android/testlatency/TestLatencyView.java
@@ -0,0 +1,267 @@
+/*
+ * 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.testlatency;
+
+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 java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+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;
+
+import android.opengl.GLES20;
+
+/**
+ * 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 TestLatencyView extends GLSurfaceView {
+    private static String TAG = "TestLatencyiew";
+    private float mX;
+    private float mY;
+    private float mDX;
+    private float mDY;
+    private long  mT;
+    private long  mDT;
+
+    public TestLatencyView(Context context) {
+        super(context);
+        setEGLContextClientVersion(2);
+        setRenderer(new Renderer());
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_MOVE:
+            float x = event.getX();
+            float y = event.getY();
+            long  t = event.getEventTime();
+            synchronized(this) {
+                mDT = t - mT;
+                mT = t;
+                mDX = x - mX;
+                mX = x;
+                mDY = y - mY;
+                mY = y;
+            }
+            break;
+        default:
+            break;
+        }
+        return true;
+    }
+
+    private class Renderer implements GLSurfaceView.Renderer {
+        private float mScaleX, mScaleY, mOffsetX, mOffsetY;
+        private final float MS_PER_FRAME = 1000 / 60;
+        public Renderer() {
+            mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length * 4)
+                .order(ByteOrder.nativeOrder()).asFloatBuffer();
+        }
+
+
+        public void onDrawFrame(GL10 gl) {
+            GLES20.glClearColor(0.4f, 0.4f, 0.4f, 1.0f);
+            GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+            GLES20.glUseProgram(mProgram);
+            checkGlError("glUseProgram");
+
+            float x, y, dx, dy;
+            long t, dt;
+            synchronized(TestLatencyView.this) {
+                x = mX;
+                y = mY;
+                dx = mDX;
+                dy = mDY;
+                dt = mDT;
+            }
+
+            if (dt > 0) {
+                dx = dx * MS_PER_FRAME / dt;
+                dy = dy * MS_PER_FRAME / dt;
+            }
+
+            GLES20.glEnableVertexAttribArray(mvPositionHandle);
+            checkGlError("glEnableVertexAttribArray");
+            GLES20.glEnableVertexAttribArray(mvColorHandle);
+            checkGlError("glEnableVertexAttribArray");
+            for(int step = 0; step < 8; step++) {
+                float sx = (x + dx * step) * mScaleX + mOffsetX;
+                float sy = (y + dy * step) * mScaleY + mOffsetY;
+                int cbase = step * 4;
+
+                for (int i = 0; i < mTriangleVerticesData.length; i += 6) {
+                    mTriangleVerticesData2[i] = sx + mTriangleVerticesData[i];
+                    mTriangleVerticesData2[i+1] = -sy + mTriangleVerticesData[i+1];
+                    mTriangleVerticesData2[i+2] = mColors[cbase];
+                    mTriangleVerticesData2[i+3] = mColors[cbase+1];
+                    mTriangleVerticesData2[i+4] = mColors[cbase+2];
+                    mTriangleVerticesData2[i+5] = mColors[cbase+3];
+                }
+                mTriangleVertices.position(0);
+                mTriangleVertices.put(mTriangleVerticesData2).position(0);
+
+                GLES20.glVertexAttribPointer(mvPositionHandle, 2, GLES20.GL_FLOAT, false, 6*4, mTriangleVertices);
+                checkGlError("glVertexAttribPointer mvPosition");
+                mTriangleVertices.put(mTriangleVerticesData2).position(2);
+                GLES20.glVertexAttribPointer(mvColorHandle, 4, GLES20.GL_FLOAT, false, 6*4, mTriangleVertices);
+                checkGlError("glVertexAttribPointer mvColor");
+                GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
+                checkGlError("glDrawArrays");
+            }
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            GLES20.glViewport(0, 0, width, height);
+            mScaleX = 2.0f / width;
+            mScaleY = 2.0f / height;
+            mOffsetX = -1f;
+            mOffsetY = -1f;
+        }
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            mProgram = createProgram(mVertexShader, mFragmentShader);
+            if (mProgram == 0) {
+                return;
+            }
+            mvPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+            checkGlError("glGetAttribLocation");
+            if (mvPositionHandle == -1) {
+                throw new RuntimeException("Could not get attrib location for vPosition");
+            }
+            mvColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
+            checkGlError("glGetAttribLocation");
+            if (mvColorHandle == -1) {
+                throw new RuntimeException("Could not get attrib location for vColor");
+            }
+        }
+
+        private int loadShader(int shaderType, String source) {
+            int shader = GLES20.glCreateShader(shaderType);
+            if (shader != 0) {
+                GLES20.glShaderSource(shader, source);
+                GLES20.glCompileShader(shader);
+                int[] compiled = new int[1];
+                GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+                if (compiled[0] == 0) {
+                    Log.e(TAG, "Could not compile shader " + shaderType + ":");
+                    Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
+                    GLES20.glDeleteShader(shader);
+                    shader = 0;
+                }
+            }
+            return shader;
+        }
+
+        private int createProgram(String vertexSource, String fragmentSource) {
+            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+            if (vertexShader == 0) {
+                return 0;
+            }
+
+            int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+            if (pixelShader == 0) {
+                return 0;
+            }
+
+            int program = GLES20.glCreateProgram();
+            if (program != 0) {
+                GLES20.glAttachShader(program, vertexShader);
+                checkGlError("glAttachShader vertexShader");
+                GLES20.glAttachShader(program, pixelShader);
+                checkGlError("glAttachShader pixelShader");
+                GLES20.glLinkProgram(program);
+                int[] linkStatus = new int[1];
+                GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+                if (linkStatus[0] != GLES20.GL_TRUE) {
+                    Log.e(TAG, "Could not link program: ");
+                    Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+                    GLES20.glDeleteProgram(program);
+                    program = 0;
+                }
+            }
+            return program;
+        }
+
+        private void checkGlError(String op) {
+            int error;
+            while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+                Log.e(TAG, op + ": glError " + error);
+                throw new RuntimeException(op + ": glError " + error);
+            }
+        }
+
+        // X, Y, R G B A
+        private final float[] mTriangleVerticesData = {
+                -0.025f, 0.3f, 0.0f, 1.0f, 0.0f, 1.0f,
+                 0.0f  , 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
+                 0.025f, 0.3f, 1.0f, 1.0f, 255.0f, 1.0f
+                };
+
+        // Color cascade:
+        private final float[] mColors = {
+                0.0f, 0.0f, 0.0f, 1.0f,
+                0.5f, 0.0f, 0.0f, 1.0f,
+                0.0f, 0.5f, 0.0f, 1.0f,
+                0.5f, 0.5f, 0.0f, 1.0f,
+
+                0.0f, 0.0f, 0.5f, 1.0f,
+                1.0f, 0.0f, 0.0f, 1.0f,
+                1.0f, 1.0f, 1.0f, 1.0f,
+                0.0f, 1.0f, 0.0f, 1.0f
+        };
+
+        private float[] mTriangleVerticesData2 = new float[mTriangleVerticesData.length];
+        private FloatBuffer mTriangleVertices;
+
+        private final String mVertexShader = "attribute vec4 aPosition;\n"
+            + "attribute vec4 aColor;\n"
+            + "varying vec4 vColor;\n"
+            + "void main() {\n"
+            + "  gl_Position = aPosition;\n"
+            + "  vColor = aColor;\n"
+            + "}\n";
+
+        private final String mFragmentShader = "precision mediump float;\n"
+            + "varying vec4 vColor;\n"
+            + "void main() {\n"
+            + "  gl_FragColor = vColor;\n"
+            + "}\n";
+
+        private int mProgram;
+        private int mvPositionHandle;
+        private int mvColorHandle;
+
+    }
+}
+
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/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/SettingsProvider/res/values-rm/strings.xml b/packages/SettingsProvider/res/values-rm/strings.xml
new file mode 100644
index 0000000..861e625
--- /dev/null
+++ b/packages/SettingsProvider/res/values-rm/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="4567566098528588863">"Memoria dals parameters"</string>
+</resources>
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 52e04d7..8de507e 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -3,16 +3,16 @@
 /**
  * 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 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this 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.
  */
 -->
@@ -24,6 +24,7 @@
     <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,wifi</string>
     <string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi</string>
     <bool name="def_auto_time">true</bool>
+    <bool name="def_auto_time_zone">true</bool>
     <bool name="def_accelerometer_rotation">true</bool>
     <!-- Default screen brightness, from 0 to 255.  102 is 40%. -->
     <integer name="def_screen_brightness">102</integer>
@@ -31,12 +32,12 @@
     <fraction name="def_window_animation_scale">100%</fraction>
     <fraction name="def_window_transition_scale">100%</fraction>
     <bool name="def_haptic_feedback">true</bool>
-    
+
     <bool name="def_bluetooth_on">false</bool>
     <bool name="def_install_non_market_apps">false</bool>
-    <!-- Comma-separated list of location providers. 
+    <!-- Comma-separated list of location providers.
          Network location is off by default because it requires
-         user opt-in via Setup Wizard or Settings.  
+         user opt-in via Setup Wizard or Settings.
     -->
     <string name="def_location_providers_allowed" translatable="false">gps</string>
     <bool name="assisted_gps_enabled">true</bool>
@@ -45,11 +46,11 @@
     <bool name="def_usb_mass_storage_enabled">true</bool>
     <bool name="def_wifi_on">false</bool>
     <bool name="def_networks_available_notification_on">true</bool>
-    
+
     <bool name="def_backup_enabled">false</bool>
     <string name="def_backup_transport" translatable="false">android/com.android.internal.backup.LocalTransport</string>
 
-    <!-- Default value for whether or not to pulse the notification LED when there is a 
+    <!-- Default value for whether or not to pulse the notification LED when there is a
          pending notification -->
     <bool name="def_notification_pulse">true</bool>
 
@@ -72,4 +73,34 @@
 
     <!-- Default for Settings.System.VIBRATE_IN_SILENT -->
     <bool name="def_vibrate_in_silent">true</bool>
+
+    <bool name="def_use_ptp_interface">false</bool>
+
+    <!-- Default for Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION -->
+    <bool name="def_accessibility_script_injection">false</bool>
+
+    <!-- Default for Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS -->
+    <string name="def_accessibility_web_content_key_bindings" translatable="false">
+            <!-- DPAD/Trackball UP maps to traverse previous on current axis and send an event. -->
+            0x13=0x01000100;
+            <!-- DPAD/Trackball DOWN maps to traverse next on current axis and send an event. -->
+            0x14=0x01010100;
+            <!-- DPAD/Trackball LEFT maps to action in non-android default navigation axis. -->
+            0x15=0x04000000;
+            <!-- DPAD/Trackball RIGHT maps to no action in non-android default navigation axis. -->
+            0x16=0x04000000;
+            <!-- Left Alt+DPAD/Trackball UP transitions from an axis to another and sends an event. -->
+            <!-- Axis transitions:  2 -> 7; 1 -> 2; 0 -> 1; 3 -> 0; 4 -> 0; 5 -> 0; 6 -> 0; -->
+            0x120013=0x03020701:0x03010201:0x03000101:0x03030001:0x03040001:0x03050001:0x03060001;
+            <!-- Left Alt+DPAD/Trackball DOWN transitions from an axis to another and sends an event. -->
+            <!-- Axis transitions: 1 -> 0; 2 -> 1; 7 -> 2; 3 -> 7; 4 -> 7; 5 -> 7; 6 -> 7; -->
+            0x120014=0x03010001:0x03020101:0x03070201:0x03030701:0x03040701:0x03050701:0x03060701;
+            <!-- Left Alt+DPAD/Trackball LEFT transitions from an axis to another and sends an event. -->
+            <!-- Axis transitions: 4 -> 3; 5 -> 4; 6 -> 5; 0 -> 6; 1 -> 6; 2 -> 6; 7 -> 6; -->
+            0x120015=0x03040301:0x03050401:0x03060501:0x03000601:0x03010601:0x03020601:0x03070601;
+            <!-- Left Alt+DPAD/Trackball RIGHT transitions from an axis to another and sends an event.  -->
+            <!-- Axis transitions: 5 -> 6; 4 -> 5; 3 -> 4; 2 -> 3; 7 -> 3; 1 -> 3; 0 -> 3; -->
+            0x120016=0x03050601:0x03040501:0x03030401:0x03020301:0x03070301:0x03010301:0x03000301;
+    </string>
+
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index ad04bb4..9ac832b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -16,12 +16,20 @@
 
 package com.android.providers.settings;
 
+import com.android.internal.content.PackageHelper;
+import com.android.internal.telephony.RILConstants;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.XmlResourceParser;
 import android.database.Cursor;
@@ -35,18 +43,7 @@
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Config;
 import android.util.Log;
-import android.util.Xml;
-
-import com.android.internal.content.PackageHelper;
-import com.android.internal.telephony.RILConstants;
-import com.android.internal.util.XmlUtils;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternView;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.util.HashSet;
@@ -64,7 +61,7 @@
     // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
     // is properly propagated through your change.  Not doing so will result in a loss of user
     // settings.
-    private static final int DATABASE_VERSION = 57;
+    private static final int DATABASE_VERSION = 59;
 
     private Context mContext;
 
@@ -734,6 +731,50 @@
             }
             upgradeVersion = 57;
         }
+
+        if (upgradeVersion == 57) {
+            /*
+             * New settings to:
+             *  1. Enable injection of accessibility scripts in WebViews.
+             *  2. Define the key bindings for traversing web content in WebViews.
+             */
+            db.beginTransaction();
+            SQLiteStatement stmt = null;
+            try {
+                stmt = db.compileStatement("INSERT INTO secure(name,value)"
+                        + " VALUES(?,?);");
+                loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+                        R.bool.def_accessibility_script_injection);
+                stmt.close();
+                stmt = db.compileStatement("INSERT INTO secure(name,value)"
+                        + " VALUES(?,?);");
+                loadStringSetting(stmt, Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
+                        R.string.def_accessibility_web_content_key_bindings);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+                if (stmt != null) stmt.close();
+            }
+            upgradeVersion = 58;
+        }
+
+        if (upgradeVersion == 58) {
+            /* Add default for new Auto Time Zone */
+            db.beginTransaction();
+            SQLiteStatement stmt = null;
+            try {
+                stmt = db.compileStatement("INSERT INTO secure(name,value)"
+                        + " VALUES(?,?);");
+                loadBooleanSetting(stmt, Settings.System.AUTO_TIME_ZONE,
+                        R.bool.def_auto_time_zone); // Sync timezone to NITZ
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+                if (stmt != null) stmt.close();
+            }
+            upgradeVersion = 59;
+        }
+
         // *** Remember to update DATABASE_VERSION above!
 
         if (upgradeVersion != currentVersion) {
@@ -876,7 +917,7 @@
                 String cls = parser.getAttributeValue(null, "class");
                 String shortcutStr = parser.getAttributeValue(null, "shortcut");
 
-                int shortcutValue = (int) shortcutStr.charAt(0);
+                int shortcutValue = shortcutStr.charAt(0);
                 if (TextUtils.isEmpty(shortcutStr)) {
                     Log.w(TAG, "Unable to get shortcut for: " + pkg + "/" + cls);
                 }
@@ -1044,7 +1085,10 @@
     
             loadBooleanSetting(stmt, Settings.System.AUTO_TIME,
                     R.bool.def_auto_time); // Sync time to NITZ
-    
+
+            loadBooleanSetting(stmt, Settings.System.AUTO_TIME_ZONE,
+                    R.bool.def_auto_time_zone); // Sync timezone to NITZ
+
             loadIntegerSetting(stmt, Settings.System.SCREEN_BRIGHTNESS,
                     R.integer.def_screen_brightness);
     
@@ -1068,6 +1112,9 @@
     
             loadBooleanSetting(stmt, Settings.System.VIBRATE_IN_SILENT,
                     R.bool.def_vibrate_in_silent);
+
+            loadBooleanSetting(stmt, Settings.System.USE_PTP_INTERFACE,
+                    R.bool.def_use_ptp_interface);
         } finally {
             if (stmt != null) stmt.close();
         }
@@ -1184,6 +1231,12 @@
     
             loadBooleanSetting(stmt, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED,
                     R.bool.def_mount_ums_notify_enabled);
+
+            loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+                    R.bool.def_accessibility_script_injection);
+
+            loadStringSetting(stmt, Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
+                    R.string.def_accessibility_web_content_key_bindings);
         } finally {
             if (stmt != null) stmt.close();
         }
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 4c83768..2c13069 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -3,11 +3,16 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := $(call all-subdir-java-files) \
+    ../../../ex/carousel/java/com/android/ex/carousel/carousel.rs
 
 LOCAL_JAVA_LIBRARIES := services
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-common-carousel
+
 LOCAL_PACKAGE_NAME := SystemUI
 LOCAL_CERTIFICATE := platform
 
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
 include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0fccbe7..6057023 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -5,6 +5,9 @@
         >
 
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.GET_TASKS" />
 
     <application
         android:persistent="true"
@@ -13,7 +16,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"
             />
 
@@ -21,5 +29,12 @@
                 android:excludeFromRecents="true">
         </activity>
 
+        <activity android:name=".recent.RecentApplicationsActivity"
+            android:theme="@android:style/Theme.NoTitleBar"
+            android:excludeFromRecents="true"
+            android:launchMode="singleInstance"
+            android:exported="true">
+        </activity>
+
     </application>
 </manifest>
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
new file mode 100644
index 0000000..5e48461
--- /dev/null
+++ b/packages/SystemUI/proguard.flags
@@ -0,0 +1,6 @@
+-keep class com.android.systemui.statusbar.tablet.TabletStatusBarService {
+  public void notificationIconsClicked(android.view.View);
+  public void systemInfoClicked(android.view.View);
+  public void recentButtonClicked(android.view.View);
+  public void toggleLightsOut(android.view.View);
+}
diff --git a/packages/SystemUI/res/anim/hydraulic_brake_interpolator.xml b/packages/SystemUI/res/anim/hydraulic_brake_interpolator.xml
new file mode 100644
index 0000000..5b6778e
--- /dev/null
+++ b/packages/SystemUI/res/anim/hydraulic_brake_interpolator.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/ease_out_interpolator.xml
+**
+** Copyright 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.
+*/
+-->
+
+<decelerateInterpolator 
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:factor="10.0" />
diff --git a/packages/SystemUI/res/anim/lights_out_in.xml b/packages/SystemUI/res/anim/lights_out_in.xml
new file mode 100644
index 0000000..8154ec0
--- /dev/null
+++ b/packages/SystemUI/res/anim/lights_out_in.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <translate android:fromYDelta="100%p" android:toYDelta="0"
+        android:duration="@android:integer/config_mediumAnimTime" 
+        android:interpolator="@anim/hydraulic_brake_interpolator"
+        />
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/lights_out_out.xml b/packages/SystemUI/res/anim/lights_out_out.xml
new file mode 100644
index 0000000..b4bc55a
--- /dev/null
+++ b/packages/SystemUI/res/anim/lights_out_out.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <translate android:toYDelta="100%p" android:fromYDelta="0" 
+        android:duration="@android:integer/config_mediumAnimTime" 
+        android:interpolator="@anim/hydraulic_brake_interpolator"
+        />
+    <alpha android:toAlpha="0.0" android:fromAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/navigation_in.xml b/packages/SystemUI/res/anim/navigation_in.xml
new file mode 100644
index 0000000..630fd72
--- /dev/null
+++ b/packages/SystemUI/res/anim/navigation_in.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/navigation_out.xml b/packages/SystemUI/res/anim/navigation_out.xml
new file mode 100644
index 0000000..4717e47
--- /dev/null
+++ b/packages/SystemUI/res/anim/navigation_out.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <alpha android:toAlpha="0.0" android:fromAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/notification_buttons_in.xml b/packages/SystemUI/res/anim/notification_buttons_in.xml
new file mode 100644
index 0000000..630fd72
--- /dev/null
+++ b/packages/SystemUI/res/anim/notification_buttons_in.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/notification_buttons_out.xml b/packages/SystemUI/res/anim/notification_buttons_out.xml
new file mode 100644
index 0000000..4717e47
--- /dev/null
+++ b/packages/SystemUI/res/anim/notification_buttons_out.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <alpha android:toAlpha="0.0" android:fromAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/notification_icons_in.xml b/packages/SystemUI/res/anim/notification_icons_in.xml
new file mode 100644
index 0000000..630fd72
--- /dev/null
+++ b/packages/SystemUI/res/anim/notification_icons_in.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/notification_icons_out.xml b/packages/SystemUI/res/anim/notification_icons_out.xml
new file mode 100644
index 0000000..4717e47
--- /dev/null
+++ b/packages/SystemUI/res/anim/notification_icons_out.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <alpha android:toAlpha="0.0" android:fromAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/recent_app_enter.xml b/packages/SystemUI/res/anim/recent_app_enter.xml
new file mode 100644
index 0000000..4947eee
--- /dev/null
+++ b/packages/SystemUI/res/anim/recent_app_enter.xml
@@ -0,0 +1,30 @@
+<?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.
+*/
+-->
+
+<!-- Special window zoom animation: this is the element that enters the screen,
+     it starts at 200% and scales down.  Goes with zoom_exit.xml. -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@android:anim/decelerate_interpolator">
+    <scale android:fromXScale="0.25" android:toXScale="1.0"
+           android:fromYScale="0.25" android:toYScale="1.0"
+           android:pivotX="0%p" android:pivotY="0%p"
+           android:duration="500" />
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+            android:duration="500"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recent_app_leave.xml b/packages/SystemUI/res/anim/recent_app_leave.xml
new file mode 100644
index 0000000..3d83988
--- /dev/null
+++ b/packages/SystemUI/res/anim/recent_app_leave.xml
@@ -0,0 +1,30 @@
+<?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.
+*/
+-->
+
+<!-- Special window zoom animation: this is the element that enters the screen,
+     it starts at 200% and scales down.  Goes with zoom_exit.xml. -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@android:anim/decelerate_interpolator">
+    <scale android:fromXScale="1.0" android:toXScale="0.25"
+           android:fromYScale="1.0" android:toYScale="0.25"
+           android:pivotX="0%p" android:pivotY="0%p"
+           android:duration="500" />
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+            android:duration="500"/>
+</set>
diff --git a/packages/SystemUI/res/anim/status_bar_in.xml b/packages/SystemUI/res/anim/status_bar_in.xml
new file mode 100644
index 0000000..460fe50
--- /dev/null
+++ b/packages/SystemUI/res/anim/status_bar_in.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <translate android:fromYDelta="-100%p" android:toYDelta="0"
+        android:duration="@android:integer/config_longAnimTime" 
+        android:interpolator="@anim/hydraulic_brake_interpolator"
+        />
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/status_bar_out.xml b/packages/SystemUI/res/anim/status_bar_out.xml
new file mode 100644
index 0000000..6572ab4
--- /dev/null
+++ b/packages/SystemUI/res/anim/status_bar_out.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <translate android:toYDelta="-100%p" android:fromYDelta="0"
+        android:duration="@android:integer/config_longAnimTime" 
+        android:interpolator="@anim/hydraulic_brake_interpolator"
+        />
+    <alpha android:toAlpha="0.0" android:fromAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/system_in.xml b/packages/SystemUI/res/anim/system_in.xml
new file mode 100644
index 0000000..630fd72
--- /dev/null
+++ b/packages/SystemUI/res/anim/system_in.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/anim/system_out.xml b/packages/SystemUI/res/anim/system_out.xml
new file mode 100644
index 0000000..4717e47
--- /dev/null
+++ b/packages/SystemUI/res/anim/system_out.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <alpha android:toAlpha="0.0" android:fromAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime" 
+        />
+</set>
diff --git a/packages/SystemUI/res/drawable-hdpi/alert_bar_background_normal.9.png b/packages/SystemUI/res/drawable-hdpi/alert_bar_background_normal.9.png
new file mode 100644
index 0000000..bc127bd
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/alert_bar_background_normal.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/alert_bar_background_pressed.9.png b/packages/SystemUI/res/drawable-hdpi/alert_bar_background_pressed.9.png
new file mode 100644
index 0000000..59af804
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/alert_bar_background_pressed.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/battery_0.png b/packages/SystemUI/res/drawable-hdpi/battery_0.png
new file mode 100644
index 0000000..f4103a8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/battery_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/battery_100.png b/packages/SystemUI/res/drawable-hdpi/battery_100.png
new file mode 100644
index 0000000..061cbe5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/battery_100.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/battery_20.png b/packages/SystemUI/res/drawable-hdpi/battery_20.png
new file mode 100644
index 0000000..0064027
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/battery_20.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/battery_40.png b/packages/SystemUI/res/drawable-hdpi/battery_40.png
new file mode 100644
index 0000000..10de0e7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/battery_40.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/battery_60.png b/packages/SystemUI/res/drawable-hdpi/battery_60.png
new file mode 100644
index 0000000..aa2b8ef
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/battery_60.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/battery_80.png b/packages/SystemUI/res/drawable-hdpi/battery_80.png
new file mode 100644
index 0000000..fe231f0
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/battery_80.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/button_frame_default.9.png b/packages/SystemUI/res/drawable-hdpi/button_frame_default.9.png
new file mode 100644
index 0000000..d809b84
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/button_frame_default.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/button_frame_pressed.9.png b/packages/SystemUI/res/drawable-hdpi/button_frame_pressed.9.png
new file mode 100644
index 0000000..5cc007c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/button_frame_pressed.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png b/packages/SystemUI/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png
deleted file mode 100644
index d21aad2..0000000
--- a/packages/SystemUI/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/divider_horizontal_light_opaque.9.png b/packages/SystemUI/res/drawable-hdpi/divider_horizontal_light_opaque.9.png
deleted file mode 100644
index f70f079..0000000
--- a/packages/SystemUI/res/drawable-hdpi/divider_horizontal_light_opaque.9.png
+++ /dev/null
Binary files differ
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/signal_0.png b/packages/SystemUI/res/drawable-hdpi/signal_0.png
new file mode 100644
index 0000000..00e36c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/signal_100.png b/packages/SystemUI/res/drawable-hdpi/signal_100.png
new file mode 100644
index 0000000..96e52ff
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/signal_100.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/signal_20.png b/packages/SystemUI/res/drawable-hdpi/signal_20.png
new file mode 100644
index 0000000..c0f652a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/signal_20.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/signal_40.png b/packages/SystemUI/res/drawable-hdpi/signal_40.png
new file mode 100644
index 0000000..995dd8e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/signal_40.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/signal_60.png b/packages/SystemUI/res/drawable-hdpi/signal_60.png
new file mode 100644
index 0000000..51e31ba
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/signal_60.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/signal_80.png b/packages/SystemUI/res/drawable-hdpi/signal_80.png
new file mode 100644
index 0000000..afa656e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/signal_80.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_notify_alarm.png b/packages/SystemUI/res/drawable-hdpi/stat_notify_alarm.png
index 2d3eb30..89daee1 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_notify_alarm.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_notify_alarm.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth.png
index e8fbc9e..96dc085 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png
index 18c77df..1e4bbf5 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_1x.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_1x.png
index 3fe08c2..bdc8c27 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_1x.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_3g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_3g.png
index 95866b1..d0d1345 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_3g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_e.png
index 1a69d13..4211b8c 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_g.png
index 09094d1..d5ece7a 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_h.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_h.png
index 0f64e0f..6687b40 100755
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_h.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_h.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_1x.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_1x.png
old mode 100644
new mode 100755
index 90454b0..ba24082
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_1x.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_3g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_3g.png
old mode 100644
new mode 100755
index 5524e26..5af2b05
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_3g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_e.png
old mode 100644
new mode 100755
index a43c233..9909b09
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_g.png
old mode 100644
new mode 100755
index 03a06a0..0e02b8d
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_h.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_h.png
old mode 100644
new mode 100755
index be7eed4..f84ad32
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_h.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_h.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_1x.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_1x.png
old mode 100644
new mode 100755
index 2d77956..d80a8ce
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_1x.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_3g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_3g.png
old mode 100644
new mode 100755
index a8411fc6..31c976a
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_3g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_g.png
old mode 100644
new mode 100755
index b135a81..a487f29
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_h.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_h.png
old mode 100644
new mode 100755
index 9ef67d1d..816085b
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_h.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_h.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_1x.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_1x.png
old mode 100644
new mode 100755
index c9196b2..0132019
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_1x.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_3g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_3g.png
old mode 100644
new mode 100755
index f7dda73..3903545
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_3g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_e.png
old mode 100644
new mode 100755
index cf17eea..ed099ff
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_g.png
old mode 100644
new mode 100755
index 9e0d73c..c930e4c
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_h.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_h.png
old mode 100644
new mode 100755
index c506f75..407a06c
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_h.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_h.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_1x.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_1x.png
old mode 100644
new mode 100755
index 6258164..6141f72
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_1x.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_3g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_3g.png
old mode 100644
new mode 100755
index eb5150f..d44a4cf
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_3g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_e.png
old mode 100644
new mode 100755
index 2496714..54ebd9b
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_g.png
old mode 100644
new mode 100755
index 4aba1b1..2fe0bbf
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_h.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_h.png
old mode 100644
new mode 100755
index 3624a8d..e58e019
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_h.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_h.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_1x.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_1x.png
index 720f578..00a29a4 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_1x.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_3g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_3g.png
index 07ea499..11ee0f2 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_3g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_e.png
index f8779a6..fc135fc 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_g.png
index f6fe4b2..3d33a62 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_h.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_h.png
index 393963f..f36e1eb 100755
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_h.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_h.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_1x.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_1x.png
index abe961a..ef5dbf4 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_1x.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_3g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_3g.png
index 95bb3cd..dba9675 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_3g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_e.png
index e6e9df4..2e5d82e 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_g.png
index a14e8d6..0985a09 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_h.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_h.png
index 404afff..7a32c43 100755
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_h.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_h.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_1x.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_1x.png
index a47a482..b622556 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_1x.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_3g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_3g.png
index cac7802..04ec59e 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_3g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_e.png
index 41e86cc..a47b982 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_g.png
index 6c4203d..9d90e71 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_g.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_h.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_h.png
index 9927dbf..920f290 100755
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_h.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_h.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_gps_acquiring.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_gps_acquiring.png
index 56943ea..9003d67 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_gps_acquiring.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_gps_acquiring.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_1_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_1_fully.png
index eeb7f67..adf668d 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_2_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_2_fully.png
index aee093a..7bf6b51 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_3_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_3_fully.png
index a40017f..78738ac 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_4_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_4_fully.png
index c3b44ee..ac88143 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_ringer_silent.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_ringer_silent.png
index 5a741bb..bdd37e1 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_ringer_silent.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_ringer_silent.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_ringer_vibrate.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_ringer_vibrate.png
index 7ff375a..21c1c08 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_ringer_vibrate.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_ringer_vibrate.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_flightmode.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_flightmode.png
index 01c7e2a..7a419f1 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_flightmode.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_flightmode.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png
index 03d2147..1adc05a 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.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_close_on.9.png b/packages/SystemUI/res/drawable-hdpi/status_bar_close_on.9.png
deleted file mode 100644
index 87d1944..0000000
--- a/packages/SystemUI/res/drawable-hdpi/status_bar_close_on.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_expand_default.png b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_default.png
new file mode 100644
index 0000000..92fc7f8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_expand_pressed.png b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_pressed.png
new file mode 100644
index 0000000..77f09ac
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_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_item_app_background_normal.9.png b/packages/SystemUI/res/drawable-hdpi/status_bar_item_app_background_normal.9.png
new file mode 100644
index 0000000..4fbfa4f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_item_app_background_normal.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-hdpi/statusbar_background.9.png b/packages/SystemUI/res/drawable-hdpi/statusbar_background.9.png
deleted file mode 100644
index 98e5487..0000000
--- a/packages/SystemUI/res/drawable-hdpi/statusbar_background.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_airplane_default.png b/packages/SystemUI/res/drawable-hdpi/system_panel_airplane_default.png
new file mode 100644
index 0000000..e375ee9
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/system_panel_airplane_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_brightness_default.png b/packages/SystemUI/res/drawable-hdpi/system_panel_brightness_default.png
new file mode 100644
index 0000000..f62502b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/system_panel_brightness_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_default.png b/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_default.png
new file mode 100644
index 0000000..e887fb8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_locked.png b/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_locked.png
new file mode 100644
index 0000000..58159d5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_locked.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_sound_default.png b/packages/SystemUI/res/drawable-hdpi/system_panel_sound_default.png
new file mode 100644
index 0000000..6e857b5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/system_panel_sound_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/alert_bar_background_normal.9.png b/packages/SystemUI/res/drawable-mdpi/alert_bar_background_normal.9.png
new file mode 100644
index 0000000..258de13
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/alert_bar_background_normal.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/alert_bar_background_pressed.9.png b/packages/SystemUI/res/drawable-mdpi/alert_bar_background_pressed.9.png
new file mode 100644
index 0000000..258de13
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/alert_bar_background_pressed.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/battery_low_battery.png b/packages/SystemUI/res/drawable-mdpi/battery_low_battery.png
deleted file mode 100644
index 60bbe6c..0000000
--- a/packages/SystemUI/res/drawable-mdpi/battery_low_battery.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/button_frame_default.9.png b/packages/SystemUI/res/drawable-mdpi/button_frame_default.9.png
new file mode 100644
index 0000000..7ab1f26
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/button_frame_default.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/button_frame_pressed.9.png b/packages/SystemUI/res/drawable-mdpi/button_frame_pressed.9.png
new file mode 100644
index 0000000..08f7a4d
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/button_frame_pressed.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png b/packages/SystemUI/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png
deleted file mode 100644
index 9444f0d..0000000
--- a/packages/SystemUI/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/divider_horizontal_light_opaque.9.png b/packages/SystemUI/res/drawable-mdpi/divider_horizontal_light_opaque.9.png
deleted file mode 100644
index f70f079..0000000
--- a/packages/SystemUI/res/drawable-mdpi/divider_horizontal_light_opaque.9.png
+++ /dev/null
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/ic_sysbar_airplane_off.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_airplane_off.png
new file mode 100644
index 0000000..d897ba61
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_airplane_off.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_airplane_on.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_airplane_on.png
new file mode 100644
index 0000000..0296b5b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_airplane_on.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..6c5a79b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png
new file mode 100644
index 0000000..9ababb7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_on.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_on.png
new file mode 100644
index 0000000..668b472
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_on.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_bluetooth_off.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_bluetooth_off.png
new file mode 100644
index 0000000..e463ba4
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_bluetooth_off.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_bluetooth_on.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_bluetooth_on.png
new file mode 100644
index 0000000..1239d50
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_bluetooth_on.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_brightness.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_brightness.png
new file mode 100644
index 0000000..97fa5fc
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_brightness.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_brightness_auto.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_brightness_auto.png
new file mode 100644
index 0000000..37a1533
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_brightness_auto.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_brightness_low.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_brightness_low.png
new file mode 100644
index 0000000..8a55e3a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_brightness_low.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_close.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_close.png
new file mode 100644
index 0000000..53abcbc
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_close.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_default_bg.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_default_bg.png
new file mode 100644
index 0000000..3e82d4e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_default_bg.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_gps_off.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_gps_off.png
new file mode 100644
index 0000000..dc2ed34
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_gps_off.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_gps_on.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_gps_on.png
new file mode 100644
index 0000000..1e39fdc
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_gps_on.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_home.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..ed813dc
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_home.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_lightsout.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_lightsout.png
new file mode 100644
index 0000000..8a07acc
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_lightsout.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_menu.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_menu.png
new file mode 100644
index 0000000..d7775f2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_menu.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_open.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_open.png
new file mode 100644
index 0000000..b7d624e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_open.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_press_bg.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_press_bg.png
new file mode 100644
index 0000000..0958393
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_press_bg.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_recent.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..178af73
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_lanscape.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_lanscape.png
new file mode 100644
index 0000000..adaadf7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_lanscape.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_portrait.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_portrait.png
new file mode 100644
index 0000000..fdc0ac7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_portrait.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_on.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_on.png
new file mode 100644
index 0000000..4517d1b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_on.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_sound_off.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_sound_off.png
new file mode 100644
index 0000000..bd11e86
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_sound_off.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_sound_on.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_sound_on.png
new file mode 100644
index 0000000..1b3ba2f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_sound_on.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png
new file mode 100644
index 0000000..ffbd2d3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_off.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_off.png
new file mode 100644
index 0000000..87acc14
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_off.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_on.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_on.png
new file mode 100644
index 0000000..bbb1c74
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_on.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..5c57802
--- /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..a2527b3
--- /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_expand_default.png b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_default.png
new file mode 100644
index 0000000..ce64926
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_expand_pressed.png b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_pressed.png
new file mode 100644
index 0000000..c63c426
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_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..f219ded
--- /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..9e64fe8
--- /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..502acce
--- /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_item_app_background_normal.9.png b/packages/SystemUI/res/drawable-mdpi/status_bar_item_app_background_normal.9.png
new file mode 100644
index 0000000..c079615
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_item_app_background_normal.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_recent_default.png b/packages/SystemUI/res/drawable-mdpi/status_bar_recent_default.png
new file mode 100644
index 0000000..4dd8dc7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_recent_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_recent_pressed.png b/packages/SystemUI/res/drawable-mdpi/status_bar_recent_pressed.png
new file mode 100644
index 0000000..350a3e9
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_recent_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..3b7c9c7
--- /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..3b7c9c7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_veto_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/statusbar_background.9.png b/packages/SystemUI/res/drawable-mdpi/statusbar_background.9.png
deleted file mode 100644
index 4b5dbe3..0000000
--- a/packages/SystemUI/res/drawable-mdpi/statusbar_background.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_0.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_0.png
new file mode 100644
index 0000000..ff75a51
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_10.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_10.png
new file mode 100644
index 0000000..66ab4c6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_10.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_100.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_100.png
new file mode 100644
index 0000000..3b50500
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_100.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_20.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_20.png
new file mode 100644
index 0000000..9119065
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_20.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_30.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_30.png
new file mode 100644
index 0000000..296f19f26b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_30.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_40.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_40.png
new file mode 100644
index 0000000..9daab23
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_40.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_50.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_50.png
new file mode 100644
index 0000000..62d24c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_50.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_60.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_60.png
new file mode 100644
index 0000000..eea927a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_60.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_70.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_70.png
new file mode 100644
index 0000000..6816088
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_70.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_80.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_80.png
new file mode 100644
index 0000000..b7dd9bb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_80.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_battery_90.png b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_90.png
new file mode 100644
index 0000000..6e36f53
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_battery_90.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_batterymini_100.png b/packages/SystemUI/res/drawable-mdpi/sysbar_batterymini_100.png
new file mode 100644
index 0000000..8eb0f29
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_batterymini_100.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_batterymini_red.png b/packages/SystemUI/res/drawable-mdpi/sysbar_batterymini_red.png
new file mode 100644
index 0000000..adcc6b9
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_batterymini_red.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_hidenotification_handle.png b/packages/SystemUI/res/drawable-mdpi/sysbar_hidenotification_handle.png
new file mode 100644
index 0000000..e43edd7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_hidenotification_handle.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_minimeter_bg.png b/packages/SystemUI/res/drawable-mdpi/sysbar_minimeter_bg.png
new file mode 100644
index 0000000..0d265fc
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_minimeter_bg.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_panel_bg.9.png b/packages/SystemUI/res/drawable-mdpi/sysbar_panel_bg.9.png
new file mode 100644
index 0000000..77e034b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_panel_bg.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_0.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_0.png
new file mode 100644
index 0000000..7469372
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_10.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_10.png
new file mode 100644
index 0000000..6625d9a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_10.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_100.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_100.png
new file mode 100644
index 0000000..b2e763b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_100.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_20.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_20.png
new file mode 100644
index 0000000..fb66362
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_20.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_30.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_30.png
new file mode 100644
index 0000000..a87d94e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_30.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_40.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_40.png
new file mode 100644
index 0000000..8e229d5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_40.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_50.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_50.png
new file mode 100644
index 0000000..fe989d4
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_50.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_60.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_60.png
new file mode 100644
index 0000000..aac57dc
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_60.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_70.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_70.png
new file mode 100644
index 0000000..2281968
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_70.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_80.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_80.png
new file mode 100644
index 0000000..7177ae1
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_80.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signal_90.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_90.png
new file mode 100644
index 0000000..7f60480
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signal_90.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_signalmini_100.png b/packages/SystemUI/res/drawable-mdpi/sysbar_signalmini_100.png
new file mode 100644
index 0000000..a5eaa63
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_signalmini_100.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_toggle_bg_off.9.png b/packages/SystemUI/res/drawable-mdpi/sysbar_toggle_bg_off.9.png
new file mode 100644
index 0000000..94849d8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_toggle_bg_off.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/sysbar_toggle_bg_on.9.png b/packages/SystemUI/res/drawable-mdpi/sysbar_toggle_bg_on.9.png
new file mode 100644
index 0000000..f11058c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/sysbar_toggle_bg_on.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_airplane_default.png b/packages/SystemUI/res/drawable-mdpi/system_panel_airplane_default.png
new file mode 100644
index 0000000..eb87532
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/system_panel_airplane_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_brightness_default.png b/packages/SystemUI/res/drawable-mdpi/system_panel_brightness_default.png
new file mode 100644
index 0000000..3bfc83e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/system_panel_brightness_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_default.png b/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_default.png
new file mode 100644
index 0000000..0d8479c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_locked.png b/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_locked.png
new file mode 100644
index 0000000..8f1d26c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_locked.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_sound_default.png b/packages/SystemUI/res/drawable-mdpi/system_panel_sound_default.png
new file mode 100644
index 0000000..22636d6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/system_panel_sound_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/alert_bar_background.xml b/packages/SystemUI/res/drawable/alert_bar_background.xml
new file mode 100644
index 0000000..24b6aa3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/alert_bar_background.xml
@@ -0,0 +1,23 @@
+<?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/alert_bar_background_pressed" />
+    <item
+         android:drawable="@drawable/alert_bar_background_normal" />
+</selector>
+
diff --git a/packages/SystemUI/res/drawable/button_frame.xml b/packages/SystemUI/res/drawable/button_frame.xml
new file mode 100644
index 0000000..5db39a5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/button_frame.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/button_frame_pressed" />
+    <item android:drawable="@drawable/button_frame_default" />
+</selector>
+
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_icon_bg.xml b/packages/SystemUI/res/drawable/ic_sysbar_icon_bg.xml
new file mode 100644
index 0000000..d8ba2a8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_sysbar_icon_bg.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/ic_sysbar_press_bg" />
+    <item android:drawable="@drawable/ic_sysbar_default_bg" />
+</selector>
+
diff --git a/packages/SystemUI/res/drawable/recent_overlay.png b/packages/SystemUI/res/drawable/recent_overlay.png
new file mode 100644
index 0000000..4dfa3d9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recent_overlay.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/recent_rez_border.png b/packages/SystemUI/res/drawable/recent_rez_border.png
new file mode 100644
index 0000000..ad025f5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recent_rez_border.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_expand.xml b/packages/SystemUI/res/drawable/status_bar_expand.xml
new file mode 100644
index 0000000..f966920
--- /dev/null
+++ b/packages/SystemUI/res/drawable/status_bar_expand.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_expand_pressed" />
+    <item android:drawable="@drawable/status_bar_expand_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_recent.xml b/packages/SystemUI/res/drawable/status_bar_recent.xml
new file mode 100755
index 0000000..d708455
--- /dev/null
+++ b/packages/SystemUI/res/drawable/status_bar_recent.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_recent_pressed" />
+    <item android:drawable="@drawable/status_bar_recent_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/drawable/sysbar_battery.xml b/packages/SystemUI/res/drawable/sysbar_battery.xml
new file mode 100644
index 0000000..9551bf0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/sysbar_battery.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/stat_sys_battery.xml
+**
+** Copyright 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.
+*/
+-->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:maxLevel="01" android:drawable="@drawable/sysbar_battery_0" />
+    <item android:maxLevel="10" android:drawable="@drawable/sysbar_battery_10" />
+    <item android:maxLevel="20" android:drawable="@drawable/sysbar_battery_20" />
+    <item android:maxLevel="30" android:drawable="@drawable/sysbar_battery_30" />
+    <item android:maxLevel="40" android:drawable="@drawable/sysbar_battery_40" />
+    <item android:maxLevel="50" android:drawable="@drawable/sysbar_battery_50" />
+    <item android:maxLevel="60" android:drawable="@drawable/sysbar_battery_60" />
+    <item android:maxLevel="70" android:drawable="@drawable/sysbar_battery_70" />
+    <item android:maxLevel="80" android:drawable="@drawable/sysbar_battery_80" />
+    <item android:maxLevel="90" android:drawable="@drawable/sysbar_battery_90" />
+    <item android:maxLevel="101" android:drawable="@drawable/sysbar_battery_100" />
+</level-list>
diff --git a/packages/SystemUI/res/drawable/sysbar_batterymini.xml b/packages/SystemUI/res/drawable/sysbar_batterymini.xml
new file mode 100644
index 0000000..c7300e6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/sysbar_batterymini.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/stat_sys_battery.xml
+**
+** Copyright 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.
+*/
+-->
+
+<clip xmlns:android="http://schemas.android.com/apk/res/android"
+        android:clipOrientation="horizontal"
+        android:maxLevel="100"
+        android:gravity="left">
+    <level-list>
+        <item android:maxLevel="1500"  android:drawable="@drawable/sysbar_batterymini_red" />
+        <item android:maxLevel="10000" android:drawable="@drawable/sysbar_batterymini_100" />
+    </level-list>
+</clip>
diff --git a/packages/SystemUI/res/drawable/sysbar_signal.xml b/packages/SystemUI/res/drawable/sysbar_signal.xml
new file mode 100644
index 0000000..9561c37
--- /dev/null
+++ b/packages/SystemUI/res/drawable/sysbar_signal.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/stat_sys_battery.xml
+**
+** Copyright 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.
+*/
+-->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:maxLevel="01" android:drawable="@drawable/sysbar_signal_0" />
+    <item android:maxLevel="10" android:drawable="@drawable/sysbar_signal_10" />
+    <item android:maxLevel="20" android:drawable="@drawable/sysbar_signal_20" />
+    <item android:maxLevel="30" android:drawable="@drawable/sysbar_signal_30" />
+    <item android:maxLevel="40" android:drawable="@drawable/sysbar_signal_40" />
+    <item android:maxLevel="50" android:drawable="@drawable/sysbar_signal_50" />
+    <item android:maxLevel="60" android:drawable="@drawable/sysbar_signal_60" />
+    <item android:maxLevel="70" android:drawable="@drawable/sysbar_signal_70" />
+    <item android:maxLevel="80" android:drawable="@drawable/sysbar_signal_80" />
+    <item android:maxLevel="90" android:drawable="@drawable/sysbar_signal_90" />
+    <item android:maxLevel="101" android:drawable="@drawable/sysbar_signal_100" />
+</level-list>
diff --git a/packages/SystemUI/res/drawable/sysbar_signalmini.xml b/packages/SystemUI/res/drawable/sysbar_signalmini.xml
new file mode 100644
index 0000000..598bf10
--- /dev/null
+++ b/packages/SystemUI/res/drawable/sysbar_signalmini.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/stat_sys_battery.xml
+**
+** Copyright 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.
+*/
+-->
+
+<clip xmlns:android="http://schemas.android.com/apk/res/android"
+        android:clipOrientation="horizontal"
+        android:gravity="right"
+        android:maxLevel="10000"
+        android:drawable="@drawable/sysbar_signalmini_100" />
diff --git a/packages/SystemUI/res/drawable/sysbar_wifi.xml b/packages/SystemUI/res/drawable/sysbar_wifi.xml
new file mode 100644
index 0000000..9561c37
--- /dev/null
+++ b/packages/SystemUI/res/drawable/sysbar_wifi.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/stat_sys_battery.xml
+**
+** Copyright 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.
+*/
+-->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:maxLevel="01" android:drawable="@drawable/sysbar_signal_0" />
+    <item android:maxLevel="10" android:drawable="@drawable/sysbar_signal_10" />
+    <item android:maxLevel="20" android:drawable="@drawable/sysbar_signal_20" />
+    <item android:maxLevel="30" android:drawable="@drawable/sysbar_signal_30" />
+    <item android:maxLevel="40" android:drawable="@drawable/sysbar_signal_40" />
+    <item android:maxLevel="50" android:drawable="@drawable/sysbar_signal_50" />
+    <item android:maxLevel="60" android:drawable="@drawable/sysbar_signal_60" />
+    <item android:maxLevel="70" android:drawable="@drawable/sysbar_signal_70" />
+    <item android:maxLevel="80" android:drawable="@drawable/sysbar_signal_80" />
+    <item android:maxLevel="90" android:drawable="@drawable/sysbar_signal_90" />
+    <item android:maxLevel="101" android:drawable="@drawable/sysbar_signal_100" />
+</level-list>
diff --git a/packages/SystemUI/res/drawable/sysbar_wifimini.xml b/packages/SystemUI/res/drawable/sysbar_wifimini.xml
new file mode 100644
index 0000000..ca6c9ed
--- /dev/null
+++ b/packages/SystemUI/res/drawable/sysbar_wifimini.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/stat_sys_battery.xml
+**
+** Copyright 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.
+*/
+-->
+
+<clip xmlns:android="http://schemas.android.com/apk/res/android"
+        android:clipOrientation="horizontal"
+        android:gravity="right"
+        android:maxLevel="100"
+        android:drawable="@drawable/sysbar_signalmini_100" />
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..6aee011
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml
@@ -0,0 +1,180 @@
+<?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.
+-->
+
+<!--    android:background="@drawable/status_bar_closed_default_background" -->
+<com.android.systemui.statusbar.tablet.TabletStatusBarView
+    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"
+    >
+    <RelativeLayout
+        android:id="@+id/bar_contents"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        >
+
+        <ImageView
+            class="com.android.systemui.statusbar.tablet.NotificationIconArea$MoreView"
+            android:id="@+id/expand"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/ic_sysbar_open"
+            android:paddingLeft="6dip"
+            android:onClick="notificationIconsClicked"
+            />
+
+        <LinearLayout
+            android:id="@+id/notificationButtons"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_toRightOf="@+id/expand"
+            android:layout_toLeftOf="@+id/systemInfo"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:visibility="gone"
+            >
+
+            <TextView android:id="@+id/do_not_disturb"
+                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_do_not_disturb_button"
+                />
+
+            <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"
+                />
+
+        </LinearLayout>
+
+        <com.android.systemui.statusbar.tablet.NotificationIconArea
+            android:id="@+id/notificationIcons"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_toRightOf="@+id/expand"
+            android:layout_toLeftOf="@+id/systemInfo"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            >
+            <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="32dip"
+                android:layout_height="match_parent"
+                android:background="@drawable/sysbar_hidenotification_handle"
+                android:layout_marginLeft="8dip"
+                />
+        </com.android.systemui.statusbar.tablet.NotificationIconArea>
+
+        <FrameLayout
+            android:id="@+id/ticker"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentLeft="true"
+            android:layout_toLeftOf="@+id/systemInfo"
+            android:paddingLeft="6dip"
+            android:gravity="center_vertical"
+            android:animateLayoutChanges="true"
+            />
+
+        <include layout="@layout/status_bar_center"
+            android:layout_width="256dip"
+            android:layout_height="match_parent"
+            android:layout_centerInParent="true"
+            />
+
+        <LinearLayout
+            android:id="@+id/navigationArea"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentRight="true"
+            android:orientation="horizontal"
+            >
+            <com.android.systemui.statusbar.KeyButtonView android:id="@+id/menu"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:src="@drawable/ic_sysbar_menu"
+                android:background="@drawable/ic_sysbar_icon_bg"
+                android:paddingLeft="4dip"
+                android:paddingRight="4dip"
+                systemui:keyCode="82"
+                />
+            <ImageButton android:id="@+id/recent"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:src="@drawable/ic_sysbar_recent"
+                android:background="@drawable/ic_sysbar_icon_bg"
+                android:paddingLeft="4dip"
+                android:paddingRight="4dip"
+                android:onClick="recentButtonClicked"
+                />
+            <com.android.systemui.statusbar.KeyButtonView android:id="@+id/home"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingLeft="4dip"
+                android:paddingRight="4dip"
+                android:src="@drawable/ic_sysbar_home"
+                android:background="@drawable/ic_sysbar_icon_bg"
+                systemui:keyCode="3"
+                />
+            <com.android.systemui.statusbar.KeyButtonView android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingLeft="4dip"
+                android:paddingRight="4dip"
+                android:src="@drawable/ic_sysbar_back"
+                android:background="@drawable/ic_sysbar_icon_bg"
+                systemui:keyCode="4"
+                />
+        </LinearLayout>
+    </RelativeLayout>
+
+    <!-- It's curtains for you. -->
+    <ImageView
+        android:id="@+id/lights_out"
+        android:src="@drawable/ic_sysbar_lightsout"
+        android:gravity="center"
+        android:background="#FF000000"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible"
+        android:clickable="true"
+        />
+</com.android.systemui.statusbar.tablet.TabletStatusBarView>
+
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_center.xml b/packages/SystemUI/res/layout-xlarge/status_bar_center.xml
new file mode 100644
index 0000000..2d74672
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/status_bar_center.xml
@@ -0,0 +1,73 @@
+<?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.
+-->
+
+<!-- Center of status bar: System info display, system info panel trigger -->
+<RelativeLayout android:id="@+id/systemInfo"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_centerInParent="true"
+    android:clickable="true"
+    android:onClick="systemInfoClicked"
+    >
+    <com.android.systemui.statusbar.Clock
+        style="@*android:style/TextAppearance.StatusBar.Icon"
+        android:id="@+id/clock"
+        android:layout_width="64dip"
+        android:layout_height="48dip"
+        android:layout_centerInParent="true"
+        android:singleLine="true"
+        android:gravity="center"
+        android:textSize="16sp"
+        android:textStyle="bold"
+        android:padding="2dip"
+        />
+    <ImageView
+        android:id="@+id/battery"
+        android:layout_width="64dip"
+        android:layout_height="16dip"
+        android:layout_toLeftOf="@id/clock"
+        android:layout_centerInParent="true"
+        android:background="@drawable/sysbar_minimeter_bg" 
+        />
+    <ImageView
+        android:id="@+id/signal"
+        android:layout_width="64dip"
+        android:layout_height="16dip"
+        android:layout_toRightOf="@id/clock"
+        android:layout_centerInParent="true"
+        android:background="@drawable/sysbar_minimeter_bg" 
+        />
+    <ImageView
+        android:id="@+id/battery_icon"
+        android:layout_height="30dip"
+        android:layout_width="30dip"
+        android:layout_toLeftOf="@id/battery"
+        android:layout_centerInParent="true"
+        android:src="@drawable/ic_sysbar_battery_mini"
+        />
+    <ImageView
+        android:id="@+id/signal_icon"
+        android:layout_height="30dip"
+        android:layout_width="30dip"
+        android:layout_toRightOf="@id/signal"
+        android:layout_centerInParent="true"
+        android:src="@drawable/ic_sysbar_wifi_mini"
+        />
+</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..049a1cc
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/status_bar_latest_event.xml
@@ -0,0 +1,38 @@
+<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="match_parent"
+        android:layout_centerVertical="true"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/status_bar_veto"
+        android:scaleType="center"
+        android:background="@null"
+        android:paddingLeft="16dip"
+        android:paddingRight="16dip"
+        />
+
+    <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..80c9a49
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
@@ -0,0 +1,49 @@
+<?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" -->
+<com.android.systemui.statusbar.tablet.NotificationPanel
+    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"
+    >
+
+    <ScrollView
+        android:id="@+id/notificationScroller"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        >
+        <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>
+    </ScrollView>
+</com.android.systemui.statusbar.tablet.NotificationPanel>
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..7e469f7
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.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.
+*/
+-->
+
+<com.android.systemui.statusbar.tablet.SystemPanel
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:background="@drawable/sysbar_panel_bg"
+    android:orientation="vertical"
+    android:paddingLeft="70dip"
+    android:paddingRight="120dip"
+    >
+
+    <!-- top row: quick settings buttons -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="64dip"
+        android:orientation="horizontal"
+        android:gravity="center"
+        >
+        <ImageButton android:id="@+id/brightness"
+            android:layout_width="90dip"
+            android:layout_height="64dip"
+            android:src="@drawable/ic_sysbar_brightness"
+            android:background="@drawable/sysbar_toggle_bg_off"
+            />
+        <ImageButton android:id="@+id/sound"
+            android:layout_width="90dip"
+            android:layout_height="64dip"
+            android:layout_marginLeft="8dip"
+            android:src="@drawable/ic_sysbar_sound_on"
+            android:background="@drawable/sysbar_toggle_bg_off"
+            />
+        <ImageButton android:id="@+id/orientation"
+            android:layout_width="90dip"
+            android:layout_height="64dip"
+            android:layout_marginLeft="8dip"
+            android:src="@drawable/ic_sysbar_rotate_on"
+            android:background="@drawable/sysbar_toggle_bg_off"
+            />
+        <ImageButton android:id="@+id/airplane"
+            android:layout_width="90dip"
+            android:layout_height="64dip"
+            android:layout_marginLeft="8dip"
+            android:src="@drawable/ic_sysbar_airplane_on"
+            android:background="@drawable/sysbar_toggle_bg_off"
+            />
+        <ImageButton android:id="@+id/gps"
+            android:layout_width="90dip"
+            android:layout_height="64dip"
+            android:layout_marginLeft="8dip"
+            android:src="@drawable/ic_sysbar_gps_on"
+            android:background="@drawable/sysbar_toggle_bg_off"
+            />
+        <ImageButton android:id="@+id/bluetooth"
+            android:layout_width="90dip"
+            android:layout_height="64dip"
+            android:layout_marginLeft="8dip"
+            android:src="@drawable/ic_sysbar_bluetooth_on"
+            android:background="@drawable/sysbar_toggle_bg_off"
+            />
+    </LinearLayout>
+
+    <!-- main row: meters, clock -->
+    <RelativeLayout
+        android:padding="8dip"
+        android:layout_width="match_parent"
+        android:layout_height="192dip"
+        >
+        <RelativeLayout
+            android:layout_width="256dip"
+            android:layout_height="192dip"
+            android:layout_alignParentLeft="true"
+            android:layout_marginLeft="48dip"
+            >
+            <ImageView android:id="@+id/battery_meter"
+                android:layout_width="256dip"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:scaleType="centerCrop"
+                android:src="@drawable/sysbar_battery"
+                />
+            <TextView android:id="@+id/battery_info"
+                style="@style/TextAppearance.StatusBar.SystemPanel"
+                android:layout_width="match_parent"
+                android:layout_height="24dip"
+                android:gravity="center"
+                android:layout_above="@id/battery_meter"
+                />
+        </RelativeLayout>
+
+        <com.android.systemui.statusbar.Clock
+            style="@style/TextAppearance.StatusBar.SystemPanel"
+            android:id="@+id/clock"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textSize="50sp"
+            android:textStyle="normal"
+            android:textColor="#FFFFFFFF"
+            android:layout_centerHorizontal="true"
+            android:layout_alignParentBottom="true"
+            />
+
+        <RelativeLayout
+            android:layout_width="256dip"
+            android:layout_height="192dip"
+            android:layout_alignParentRight="true"
+            android:layout_marginRight="48dip"
+            >
+            <ImageView android:id="@+id/signal_meter"
+                android:layout_width="256dip"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:scaleType="centerCrop"
+                android:src="@drawable/sysbar_signal"
+                />
+
+            <TextView android:id="@+id/signal_info"
+                style="@style/TextAppearance.StatusBar.SystemPanel"
+                android:layout_width="match_parent"
+                android:layout_height="24dip"
+                android:gravity="center"
+                android:layout_above="@id/signal_meter"
+                />
+        </RelativeLayout>
+
+        <ImageView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="8dip"
+            android:layout_marginLeft="8dip"
+            android:src="@drawable/ic_sysbar_battery_on"
+            />
+        <ImageView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="8dip"
+            android:layout_marginRight="8dip"
+            android:src="@drawable/ic_sysbar_wifi_on"
+            />
+    </RelativeLayout>
+
+    <!-- bottom row: transient indicators, settings button -->
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1sp"
+        android:background="@android:drawable/divider_horizontal_dark"
+        />
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="48dip"
+        >
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:orientation="horizontal"
+            >
+            <!-- TODO: alarm -->
+            <!-- TODO: sync -->
+            <TextView android:id="@+id/date"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                />
+        </LinearLayout>
+
+        <TextView android:id="@+id/settings_button"
+            style="@style/TextAppearance.StatusBar.TextButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_centerVertical="true"
+            android:paddingRight="32dip"
+            android:paddingLeft="32dip"
+            android:textSize="20sp"
+            android:text="@string/system_panel_settings_button"
+            />
+        <View
+            android:layout_height="match_parent"
+            android:layout_width="1sp"
+            android:layout_toLeftOf="@id/settings_button"
+            android:background="@*android:drawable/divider_vertical_dark"
+            />
+
+    </RelativeLayout>
+</com.android.systemui.statusbar.tablet.SystemPanel>
diff --git a/packages/SystemUI/res/layout-xlarge/ticker.xml b/packages/SystemUI/res/layout-xlarge/ticker.xml
new file mode 100644
index 0000000..c8d855f
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/ticker.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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:background="#ff000000"
+    >
+
+    <LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical"
+        android:orientation="vertical"
+        android:paddingLeft="12dp"
+        >
+
+        <TextView android:id="@+id/title"
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textStyle="bold"
+            android:maxLines="1"
+            />
+        <TextView android:id="@+id/subtitle"
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="1"
+            />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout-xlarge/ticker_compat.xml b/packages/SystemUI/res/layout-xlarge/ticker_compat.xml
new file mode 100644
index 0000000..79c7543
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/ticker_compat.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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:background="#ff000000"
+    >
+
+    <TextView android:id="@+id/text"
+        android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_vertical"
+        android:layout_marginLeft="12dp"
+        android:gravity="center_vertical"
+        android:maxLines="2"
+        />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout-xlarge/ticker_icon.xml b/packages/SystemUI/res/layout-xlarge/ticker_icon.xml
new file mode 100644
index 0000000..9efa987
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/ticker_icon.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="30dp"
+    android:layout_height="30dp"
+    android:layout_gravity="center"
+    android:layout_marginLeft="6dp"
+    />
+
diff --git a/packages/SystemUI/res/layout/intruder_alert.xml b/packages/SystemUI/res/layout/intruder_alert.xml
new file mode 100644
index 0000000..ba4a774
--- /dev/null
+++ b/packages/SystemUI/res/layout/intruder_alert.xml
@@ -0,0 +1,58 @@
+<?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="32dip"
+    android:layout_width="match_parent"
+    android:paddingLeft="8dip"
+    android:paddingRight="8dip"
+    >
+        
+    <LinearLayout 
+        android:id="@+id/intruder_alert_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:animationCache="false"
+        android:orientation="horizontal"
+        android:background="@drawable/alert_bar_background"
+        android:clickable="true"
+        android:focusable="true"
+        android:descendantFocusability="afterDescendants"
+        >
+
+        <ImageView
+            android:id="@+id/alertIcon"
+            android:layout_width="25dip"
+            android:layout_height="25dip"
+            android:layout_marginLeft="6dip"
+            android:layout_marginRight="8dip"
+            />
+        <TextView
+            android:id="@+id/alertText"
+            android:textAppearance="@style/TextAppearance.StatusBar.IntruderAlert"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            />
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/recent_apps_activity.xml b/packages/SystemUI/res/layout/recent_apps_activity.xml
new file mode 100644
index 0000000..ec661e8
--- /dev/null
+++ b/packages/SystemUI/res/layout/recent_apps_activity.xml
@@ -0,0 +1,55 @@
+<?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.
+*/
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <!-- Title -->
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="#80FFFFFF"
+        android:textStyle="bold"
+        android:singleLine="true"
+        android:text="@string/recent_tasks_title"
+        android:visibility="gone"/>
+
+    <!-- This is only intended to be visible when carousel is invisible -->
+    <TextView
+        android:id="@+id/no_applications_message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:text="@string/recent_tasks_empty"
+        android:visibility="gone"/>
+
+    <com.android.systemui.recent.RecentApplicationsCarouselView
+        android:id="@+id/carousel"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1">
+    </com.android.systemui.recent.RecentApplicationsCarouselView>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/recents_detail_view.xml b/packages/SystemUI/res/layout/recents_detail_view.xml
new file mode 100644
index 0000000..879d0f2
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_detail_view.xml
@@ -0,0 +1,40 @@
+<?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.
+*/
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <!-- Application Title -->
+    <TextView android:id="@+id/app_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:singleLine="true"/>
+
+    <!-- Application Details -->
+    <TextView
+        android:id="@+id/app_description"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index a5d6885..2f1b36e8 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -21,7 +21,8 @@
 <!--    android:background="@drawable/status_bar_closed_default_background" -->
 <com.android.systemui.statusbar.StatusBarView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:background="@drawable/statusbar_background"
+    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
+    android:background="@drawable/status_bar_background"
     android:orientation="vertical"
     android:focusable="true"
     android:descendantFocusability="afterDescendants"
@@ -55,6 +56,8 @@
             android:layout_height="match_parent"
             android:singleLine="true"
             android:paddingRight="6dip"
+            android:textSize="16sp"
+            android:textStyle="bold"
             android:gravity="center_vertical|left"
             />
     </LinearLayout>
@@ -108,6 +111,6 @@
         android:gravity="center_vertical|left"
         android:paddingLeft="6px"
         android:paddingRight="6px"
-        android:background="@drawable/statusbar_background"
+        android:background="@drawable/status_bar_background"
         />
 </com.android.systemui.statusbar.StatusBarView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index c3f4205..b5b1b50 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -31,7 +31,7 @@
         android:paddingTop="3dp"
         android:paddingBottom="5dp"
         android:paddingRight="3dp"
-        android:background="@drawable/title_bar_portrait"
+        android:background="@drawable/shade_header_background"
         >
         <com.android.systemui.statusbar.CarrierLabel
             android:layout_width="0dp"
@@ -43,7 +43,7 @@
             android:paddingBottom="1dp"
             android:paddingLeft="4dp"
             android:textAppearance="?android:attr/textAppearanceLarge"
-            android:textColor="#ffdfdfdf"
+            android:textColor="?android:attr/textColorSecondary"
             />
         <TextView android:id="@+id/clear_all_button"
             android:layout_width="wrap_content"
@@ -57,7 +57,7 @@
             style="?android:attr/buttonStyle"
             android:paddingLeft="15dp"
             android:paddingRight="15dp"
-            android:background="@android:drawable/btn_default_small"
+            android:background="@drawable/btn_default_small"
             />
     </LinearLayout>
 
@@ -71,7 +71,6 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:fadingEdge="none"
-            android:overscrollMode="ifContentScrolls"
             >
             <com.android.systemui.statusbar.NotificationLinearLayout
                 android:id="@+id/notificationLinearLayout"
@@ -83,7 +82,7 @@
                 <TextView android:id="@+id/noNotificationsTitle"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:background="@drawable/shade_bgcolor"
+                    android:background="@drawable/title_bar_portrait"
                     android:paddingLeft="5dp"
                     android:textAppearance="@style/TextAppearance.StatusBar.Title"
                     android:text="@string/status_bar_no_notifications_title"
@@ -92,7 +91,7 @@
                 <TextView android:id="@+id/ongoingTitle"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:background="@drawable/shade_bgcolor"
+                    android:background="@drawable/title_bar_portrait"
                     android:paddingLeft="5dp"
                     android:textAppearance="@style/TextAppearance.StatusBar.Title"
                     android:text="@string/status_bar_ongoing_events_title"
@@ -106,7 +105,7 @@
                 <TextView android:id="@+id/latestTitle"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:background="@drawable/shade_bgcolor"
+                    android:background="@drawable/title_bar_portrait"
                     android:paddingLeft="5dp"
                     android:textAppearance="@style/TextAppearance.StatusBar.Title"
                     android:text="@string/status_bar_latest_events_title"
diff --git a/packages/SystemUI/res/layout/status_bar_latest_event.xml b/packages/SystemUI/res/layout/status_bar_latest_event.xml
index b8a1cbe..88d9739 100644
--- a/packages/SystemUI/res/layout/status_bar_latest_event.xml
+++ b/packages/SystemUI/res/layout/status_bar_latest_event.xml
@@ -16,8 +16,8 @@
 
     <View
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@drawable/divider_horizontal_light_opaque"
+        android:layout_height="1sp"
+        android:background="@android:drawable/divider_horizontal_bright"
         />
 
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_tracking.xml b/packages/SystemUI/res/layout/status_bar_tracking.xml
index 3b9b775..a2b40e6 100644
--- a/packages/SystemUI/res/layout/status_bar_tracking.xml
+++ b/packages/SystemUI/res/layout/status_bar_tracking.xml
@@ -26,12 +26,11 @@
     android:paddingRight="0px"
     >
 
-    <View
-         android:layout_width="match_parent"
-         android:layout_height="wrap_content"
-         android:layout_weight="1"
-         android:background="#ff212121"
-         />
+    <com.android.systemui.statusbar.TrackingPatternView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        />
 
     <com.android.systemui.statusbar.CloseDragHandle android:id="@+id/close"
         android:layout_width="match_parent"
@@ -43,7 +42,7 @@
             android:layout_height="wrap_content"
             android:layout_gravity="bottom"
             android:scaleType="fitXY"
-            android:src="@drawable/status_bar_close_on"
+            android:src="@drawable/shade_handlebar"
             />
 
     </com.android.systemui.statusbar.CloseDragHandle>
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..3c59c92
--- /dev/null
+++ b/packages/SystemUI/res/values-xlarge/strings.xml
@@ -0,0 +1,77 @@
+<?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>
+
+    <!-- System panel ("Quick Settings") -->
+
+    <!-- Text to display underneath the graphical battery meter. Should
+         include the word for "battery" and a place for the percentage charge
+         available. [CHAR LIMIT=20] -->
+    <string name="system_panel_battery_meter_format">
+        Battery: <xliff:g id="number">%d</xliff:g><xliff:g id="percent">%%</xliff:g>
+    </string>
+
+    <!-- Text to display underneath the graphical signal strength meter when
+         no connection is available. [CHAR LIMIT=20] -->
+    <string name="system_panel_signal_meter_disconnected">
+        no internet connection
+    </string>
+
+    <!-- Text to display underneath the graphical signal strength meter when
+         it is displaying information about a connected, named Wi-Fi network.
+         Should include the word for "Wi-Fi" and a placeholder for the
+         wireless network's SSID. [CHAR LIMIT=20] -->
+    <string name="system_panel_signal_meter_wifi_ssid_format">
+        Wi-Fi: <xliff:g id="ssid">%s</xliff:g>
+    </string>
+
+    <!-- Text to display underneath the graphical signal strength meter when
+         it is displaying Wi-Fi status and Wi-Fi is connected to a network
+         whose SSID is not available.
+         [CHAR LIMIT=20] -->
+    <string name="system_panel_signal_meter_wifi_nossid">
+        Wi-Fi: connected
+    </string>
+
+    <!-- Text to display underneath the graphical signal strength meter when
+         it is displaying Wi-Fi status and Wi-Fi is in the process of
+         connecting to a network.  [CHAR LIMIT=20] -->
+    <string name="system_panel_signal_meter_wifi_connecting">
+        Wi-Fi: connecting…
+    </string>
+ 
+    <!-- Text to display underneath the graphical signal strength meter when
+         it is displaying mobile data (3G) status and a network connection is
+         available.
+         [CHAR LIMIT=20] -->
+    <string name="system_panel_signal_meter_data_connected">
+        Mobile data: connected
+    </string>
+
+    <!-- Text to display underneath the graphical signal strength meter when
+         it is displaying mobile data (3G) status and a network connection is
+         unavailable.
+         [CHAR LIMIT=20] -->
+    <string name="system_panel_signal_meter_data_connecting">
+        Mobile data: connecting…
+    </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/colors.xml b/packages/SystemUI/res/values/colors.xml
index 6550438..a0def6b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -17,7 +17,5 @@
  */
 -->
 <resources>
-    <drawable name="shade_bgcolor">#ff282828</drawable>
-    <drawable name="notification_header_text_color">#ff969696</drawable>
     <drawable name="notification_number_text_color">#ffffffff</drawable>
 </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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ba3a3d1..1f24ba6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -24,6 +24,18 @@
          all of the currently visible notifications. -->
     <string name="status_bar_clear_all_button">Clear</string>
 
+    <!-- The text for the button in the notification window-shade that turns
+         on do not disturb mode, where notifications no longer show their ticker,
+         no sound plays, and no icons are visible.  The windowshade continues to show
+         the notifications. -->
+    <string name="status_bar_do_not_disturb_button">Do not disturb</string>
+
+    <!-- The text for the button in the notification window-shade that turns
+         off do not disturb mode.  After clicking this, notifications will be
+         shown again. -->
+    <string name="status_bar_please_disturb_button">Show notifications</string>
+
+
     <!-- The label in the bar at the top of the status bar when there are no notifications
          showing. -->
     <string name="status_bar_no_notifications_title">No notifications</string>
@@ -51,4 +63,20 @@
          power usage activity to find out what drained the battery. -->
     <string name="battery_low_why">Battery use</string>
 
+    <!-- Name of the button that links to the Settings app. [MAXCHARS=NONE] -->
+    <string name="system_panel_settings_button">Settings</string>
+
+
+    <!-- Recent Tasks dialog: title
+     TODO: this should move to SystemUI.apk, but the code for the old 
+            recent dialog is still in the framework
+     -->
+    <string name="recent_tasks_title">Recent</string>
+    <!-- Recent Tasks dialog: message when there are no recent applications
+     TODO: this should move to SystemUI.apk, but the code for the old 
+            recent dialog is still in the framework
+     -->
+    <string name="recent_tasks_empty">No recent applications.</string>
+
+    
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 86bcf3a..f592703 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -19,10 +19,24 @@
     <style name="TextAppearance.StatusBar.Title" parent="@android:style/TextAppearance.StatusBar">
         <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
         <item name="android:textStyle">bold</item>
-        <item name="android:textColor">@drawable/notification_header_text_color</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="TextAppearance.StatusBar.IntruderAlert"
         parent="@android:style/TextAppearance.StatusBar">
     </style>
+
+    <style name="TextAppearance.StatusBar.SystemPanel"
+        parent="@android:style/TextAppearance.StatusBar">
+        <item name="android:textAppearance">?android:attr/textAppearance</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#50FFFFFF</item>
+    </style>
+
+    <style name="TextAppearance.StatusBar.TextButton"
+        parent="@android:style/TextAppearance.StatusBar">
+        <item name="android:textAppearance">?android:attr/textAppearance</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#FFFFFFFF</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
new file mode 100644
index 0000000..16a3c17
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.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.recent;
+
+import com.android.systemui.R;
+
+import com.android.ex.carousel.CarouselView;
+import com.android.ex.carousel.CarouselViewHelper;
+import com.android.ex.carousel.CarouselRS.CarouselCallback;
+import com.android.ex.carousel.CarouselViewHelper.DetailTextureParameters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.IThumbnailReceiver;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.PorterDuff;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.Drawable;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.widget.TextView;
+
+public class RecentApplicationsActivity extends Activity {
+    private static final String TAG = "RecentApplicationsActivity";
+    private static boolean DBG = false;
+    private static final int CARD_SLOTS = 56;
+    private static final int VISIBLE_SLOTS = 7;
+    private static final int MAX_TASKS = VISIBLE_SLOTS * 2;
+
+    // TODO: these should be configurable
+    private static final int DETAIL_TEXTURE_MAX_WIDTH = 200;
+    private static final int DETAIL_TEXTURE_MAX_HEIGHT = 80;
+    private static final int TEXTURE_WIDTH = 256;
+    private static final int TEXTURE_HEIGHT = 256;
+
+    private ActivityManager mActivityManager;
+    private List<RunningTaskInfo> mRunningTaskList;
+    private boolean mPortraitMode = true;
+    private ArrayList<ActivityDescription> mActivityDescriptions
+            = new ArrayList<ActivityDescription>();
+    private CarouselView mCarouselView;
+    private LocalCarouselViewHelper mHelper;
+    private View mNoRecentsView;
+    private Bitmap mLoadingBitmap;
+    private Bitmap mRecentOverlay;
+    private boolean mHidden = false;
+    private boolean mHiding = false;
+    private DetailInfo mDetailInfo;
+
+    /**
+     * This class is a container for all items associated with the DetailView we'll
+     * be drawing to a bitmap and sending to Carousel.
+     *
+     */
+    static final class DetailInfo {
+        public DetailInfo(View _view, TextView _title, TextView _desc) {
+            view = _view;
+            title = _title;
+            description = _desc;
+        }
+
+        /**
+         * Draws view into the given bitmap, if provided
+         * @param bitmap
+         */
+        public Bitmap draw(Bitmap bitmap) {
+            resizeView(view, DETAIL_TEXTURE_MAX_WIDTH, DETAIL_TEXTURE_MAX_HEIGHT);
+            int desiredWidth = view.getWidth();
+            int desiredHeight = view.getHeight();
+            if (bitmap == null || desiredWidth != bitmap.getWidth()
+                    || desiredHeight != bitmap.getHeight()) {
+                bitmap = Bitmap.createBitmap(desiredWidth, desiredHeight, Config.ARGB_8888);
+            }
+            Canvas canvas = new Canvas(bitmap);
+            view.draw(canvas);
+            return bitmap;
+        }
+
+        /**
+         * Force a layout pass on the given view.
+         */
+        private void resizeView(View view, int maxWidth, int maxHeight) {
+            int widthSpec = MeasureSpec.getMode(MeasureSpec.AT_MOST)
+                    | MeasureSpec.getSize(maxWidth);
+            int heightSpec = MeasureSpec.getMode(MeasureSpec.AT_MOST)
+                    | MeasureSpec.getSize(maxHeight);
+            view.measure(widthSpec, heightSpec);
+            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
+            Log.v(TAG, "RESIZED VIEW: " + view.getWidth() + ", " + view.getHeight());
+        }
+
+        public View view;
+        public TextView title;
+        public TextView description;
+    }
+
+    static class ActivityDescription {
+        int id;
+        Bitmap thumbnail; // generated by Activity.onCreateThumbnail()
+        Drawable icon; // application package icon
+        String label; // application package label
+        CharSequence description; // generated by Activity.onCreateDescription()
+        Intent intent; // launch intent for application
+        Matrix matrix; // arbitrary rotation matrix to correct orientation
+        int position; // position in list
+
+        public ActivityDescription(Bitmap _thumbnail,
+                Drawable _icon, String _label, String _desc, int _id, int _pos)
+        {
+            thumbnail = _thumbnail;
+            icon = _icon;
+            label = _label;
+            description = _desc;
+            id = _id;
+            position = _pos;
+        }
+
+        public void clear() {
+            icon = null;
+            thumbnail = null;
+            label = null;
+            description = null;
+            intent = null;
+            matrix = null;
+            id = -1;
+            position = -1;
+        }
+    };
+
+    private ActivityDescription findActivityDescription(int id) {
+        for (int i = 0; i < mActivityDescriptions.size(); i++) {
+            ActivityDescription item = mActivityDescriptions.get(i);
+            if (item != null && item.id == id) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    private class LocalCarouselViewHelper extends CarouselViewHelper {
+        private Paint mPaint = new Paint();
+        private DetailTextureParameters mDetailParams = new DetailTextureParameters(10.0f, 20.0f);
+
+        public LocalCarouselViewHelper(Context context) {
+            super(context);
+        }
+
+        @Override
+        public DetailTextureParameters getDetailTextureParameters(int id) {
+            return mDetailParams;
+        }
+
+        public void onCardSelected(int n) {
+            if (n < mActivityDescriptions.size()) {
+                ActivityDescription item = mActivityDescriptions.get(n);
+                // prepare a launch intent and send it
+                if (item.intent != null) {
+                    item.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
+                    try {
+                        if (DBG) Log.v(TAG, "Starting intent " + item.intent);
+                        startActivity(item.intent);
+                        overridePendingTransition(R.anim.recent_app_enter, R.anim.recent_app_leave);
+                    } catch (ActivityNotFoundException e) {
+                        if (DBG) Log.w("Recent", "Unable to launch recent task", e);
+                    }
+                    finish();
+                }
+            }
+        }
+
+        @Override
+        public Bitmap getTexture(final int id) {
+            if (DBG) Log.v(TAG, "onRequestTexture(" + id + ")");
+            ActivityDescription info;
+            synchronized(mActivityDescriptions) {
+                info = mActivityDescriptions.get(id);
+            }
+            Bitmap bitmap = null;
+            if (info != null) {
+                bitmap = compositeBitmap(info);
+            }
+            return bitmap;
+        }
+
+        @Override
+        public Bitmap getDetailTexture(int n) {
+            Bitmap bitmap = null;
+            if (n < mActivityDescriptions.size()) {
+                ActivityDescription item = mActivityDescriptions.get(n);
+                mDetailInfo.title.setText(item.label);
+                mDetailInfo.description.setText(item.description);
+                bitmap = mDetailInfo.draw(null);
+            }
+            return bitmap;
+        }
+    };
+
+    private Bitmap compositeBitmap(ActivityDescription info) {
+        final int targetWidth = TEXTURE_WIDTH;
+        final int targetHeight = TEXTURE_HEIGHT;
+        final int border = 3; // inset along the edge for thumnnail content
+        final int overlap = 1; // how many pixels of overlap between border and thumbnail
+        final Resources res = getResources();
+        if (mRecentOverlay == null) {
+            mRecentOverlay = BitmapFactory.decodeResource(res, R.drawable.recent_overlay);
+        }
+
+        // Create a bitmap of the proper size/format and set the canvas to draw to it
+        final Bitmap result = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(result);
+        canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG));
+        Paint paint = new Paint();
+        paint.setFilterBitmap(false);
+
+        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+        canvas.save();
+        if (info.thumbnail != null) {
+            // Draw the thumbnail
+            int sourceWidth = targetWidth - 2 * (border - overlap);
+            int sourceHeight = targetHeight - 2 * (border - overlap);
+            final float scaleX = (float) sourceWidth / info.thumbnail.getWidth();
+            final float scaleY = (float) sourceHeight / info.thumbnail.getHeight();
+            canvas.translate(border * 0.5f, border * 0.5f);
+            canvas.scale(scaleX, scaleY);
+            canvas.drawBitmap(info.thumbnail, 0, 0, paint);
+        } else {
+            // Draw the Loading bitmap placeholder, TODO: Remove when RS handles blending
+            final float scaleX = (float) targetWidth / mLoadingBitmap.getWidth();
+            final float scaleY = (float) targetHeight / mLoadingBitmap.getHeight();
+            canvas.scale(scaleX, scaleY);
+            canvas.drawBitmap(mLoadingBitmap, 0, 0, paint);
+        }
+        canvas.restore();
+
+        // Draw overlay
+        canvas.save();
+        final float scaleOverlayX = (float) targetWidth / mRecentOverlay.getWidth();
+        final float scaleOverlayY = (float) targetHeight / mRecentOverlay.getHeight();
+        canvas.scale(scaleOverlayX, scaleOverlayY);
+        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
+        canvas.drawBitmap(mRecentOverlay, 0, 0, paint);
+        canvas.restore();
+
+        // Draw icon
+        if (info.icon != null) {
+            canvas.save();
+            info.icon.draw(canvas);
+            canvas.restore();
+        }
+
+        return result;
+    }
+
+    private final IThumbnailReceiver mThumbnailReceiver = new IThumbnailReceiver.Stub() {
+
+        public void finished() throws RemoteException {
+
+        }
+
+        public void newThumbnail(final int id, final Bitmap bitmap, CharSequence description)
+                throws RemoteException {
+            int w = bitmap.getWidth();
+            int h = bitmap.getHeight();
+            if (DBG) Log.v(TAG, "New thumbnail for id=" + id + ", dimensions=" + w + "x" + h
+                    + " description '" + description + "'");
+            ActivityDescription info = findActivityDescription(id);
+            if (info != null) {
+                info.thumbnail = bitmap;
+                info.description = description;
+                final int thumbWidth = bitmap.getWidth();
+                final int thumbHeight = bitmap.getHeight();
+                if ((mPortraitMode && thumbWidth > thumbHeight)
+                        || (!mPortraitMode && thumbWidth < thumbHeight)) {
+                    Matrix matrix = new Matrix();
+                    matrix.setRotate(90.0f, (float) thumbWidth / 2, (float) thumbHeight / 2);
+                    info.matrix = matrix;
+                } else {
+                    info.matrix = null;
+                }
+                mCarouselView.setTextureForItem(info.position, compositeBitmap(info));
+            } else {
+                if (DBG) Log.v(TAG, "Can't find view for id " + id);
+            }
+        }
+    };
+
+    /**
+     * We never really finish() RecentApplicationsActivity, since we don't want to
+     * get destroyed and pay the start-up cost to restart it.
+     */
+    @Override
+    public void finish() {
+        moveTaskToBack(true);
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        mHidden = !mHidden;
+        if (mHidden) {
+            mHiding = true;
+            moveTaskToBack(true);
+        } else {
+            mHiding = false;
+        }
+        super.onNewIntent(intent);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Resources res = getResources();
+        final View decorView = getWindow().getDecorView();
+
+        getWindow().getDecorView().setBackgroundColor(0x80000000);
+        setContentView(R.layout.recent_apps_activity);
+
+
+        if (mCarouselView == null) {
+            mLoadingBitmap = BitmapFactory.decodeResource(res, R.drawable.recent_rez_border);
+            mCarouselView = (CarouselView)findViewById(R.id.carousel);
+            mHelper = new LocalCarouselViewHelper(this);
+            mHelper.setCarouselView(mCarouselView);
+
+            mCarouselView.setSlotCount(CARD_SLOTS);
+            mCarouselView.setVisibleSlots(VISIBLE_SLOTS);
+            mCarouselView.createCards(0);
+            mCarouselView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS));
+            mCarouselView.setDefaultBitmap(mLoadingBitmap);
+            mCarouselView.setLoadingBitmap(mLoadingBitmap);
+            mCarouselView.setRezInCardCount(3.0f);
+            mCarouselView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+
+            mNoRecentsView = (View) findViewById(R.id.no_applications_message);
+
+            mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+            mPortraitMode = decorView.getHeight() > decorView.getWidth();
+
+            // Load detail view which will be used to render text
+            View detail = getLayoutInflater().inflate(R.layout.recents_detail_view, null);
+            TextView title = (TextView) detail.findViewById(R.id.app_title);
+            TextView description = (TextView) detail.findViewById(R.id.app_description);
+            mDetailInfo = new DetailInfo(detail, title, description);
+
+            refresh();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        refresh();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mPortraitMode = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT;
+        if (DBG) Log.v(TAG, "CONFIG CHANGE, mPortraitMode = " + mPortraitMode);
+        refresh();
+    }
+
+    void updateRunningTasks() {
+        mRunningTaskList = mActivityManager.getRunningTasks(MAX_TASKS, 0, mThumbnailReceiver);
+        if (DBG) Log.v(TAG, "Portrait: " + mPortraitMode);
+        for (RunningTaskInfo r : mRunningTaskList) {
+            if (r.thumbnail != null) {
+                int thumbWidth = r.thumbnail.getWidth();
+                int thumbHeight = r.thumbnail.getHeight();
+                if (DBG) Log.v(TAG, "Got thumbnail " + thumbWidth + "x" + thumbHeight);
+                ActivityDescription desc = findActivityDescription(r.id);
+                if (desc != null) {
+                    desc.thumbnail = r.thumbnail;
+                    desc.description = r.description;
+                    if ((mPortraitMode && thumbWidth > thumbHeight)
+                            || (!mPortraitMode && thumbWidth < thumbHeight)) {
+                        Matrix matrix = new Matrix();
+                        matrix.setRotate(90.0f, (float) thumbWidth / 2, (float) thumbHeight / 2);
+                        desc.matrix = matrix;
+                    }
+                } else {
+                    if (DBG) Log.v(TAG, "Couldn't find ActivityDesc for id=" + r.id);
+                }
+            } else {
+                if (DBG) Log.v(TAG, "*** RUNNING THUMBNAIL WAS NULL ***");
+            }
+        }
+        mCarouselView.createCards(mActivityDescriptions.size());
+    }
+
+    private void updateRecentTasks() {
+        final PackageManager pm = getPackageManager();
+        final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+
+        final List<ActivityManager.RecentTaskInfo> recentTasks =
+                am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
+
+        ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+                    .resolveActivityInfo(pm, 0);
+
+        // IconUtilities iconUtilities = new IconUtilities(this); // FIXME
+
+        int numTasks = recentTasks.size();
+        mActivityDescriptions.clear();
+        for (int i = 0, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
+            final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
+
+            Intent intent = new Intent(recentInfo.baseIntent);
+            if (recentInfo.origActivity != null) {
+                intent.setComponent(recentInfo.origActivity);
+            }
+
+            // Skip the current home activity.
+            if (homeInfo != null
+                    && homeInfo.packageName.equals(intent.getComponent().getPackageName())
+                    && homeInfo.name.equals(intent.getComponent().getClassName())) {
+                continue;
+            }
+
+            intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                    | Intent.FLAG_ACTIVITY_NEW_TASK);
+            final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+            if (resolveInfo != null) {
+                final ActivityInfo info = resolveInfo.activityInfo;
+                final String title = info.loadLabel(pm).toString();
+                Drawable icon = info.loadIcon(pm);
+
+                int id = recentTasks.get(i).id;
+                if (id != -1 && title != null && title.length() > 0 && icon != null) {
+                    // icon = null; FIXME: iconUtilities.createIconDrawable(icon);
+                    ActivityDescription item = new ActivityDescription(
+                            null, icon, title, null, id, index);
+                    item.intent = intent;
+                    mActivityDescriptions.add(item);
+                    if (DBG) Log.v(TAG, "Added item[" + index
+                            + "], id=" + item.id
+                            + ", title=" + item.label);
+                    ++index;
+                } else {
+                    if (DBG) Log.v(TAG, "SKIPPING item " + id);
+                }
+            }
+        }
+    }
+
+    private final Runnable mRefreshRunnable = new Runnable() {
+        public void run() {
+            updateRecentTasks();
+            updateRunningTasks();
+            showCarousel(mActivityDescriptions.size() > 0);
+        }
+    };
+
+    private void showCarousel(boolean show) {
+        if (show) {
+            // Make carousel visible
+            mNoRecentsView.setVisibility(View.GONE);
+            mCarouselView.setVisibility(View.VISIBLE);
+            mCarouselView.createCards(mActivityDescriptions.size());
+        } else {
+            // show "No Recent Tasks"
+            mNoRecentsView.setVisibility(View.VISIBLE);
+            mCarouselView.setVisibility(View.GONE);
+        }
+    }
+
+    private void refresh() {
+        if (!mHiding && mCarouselView != null) {
+            // Don't update the view now. Instead, post a request so it happens next time
+            // we reach the looper after a delay. This way we can fold multiple refreshes
+            // into just the latest.
+            mCarouselView.removeCallbacks(mRefreshRunnable);
+            mCarouselView.postDelayed(mRefreshRunnable, 50);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsCarouselView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsCarouselView.java
new file mode 100644
index 0000000..1c8ec95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsCarouselView.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.recent;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.ex.carousel.CarouselView;
+import com.android.systemui.R;
+
+public class RecentApplicationsCarouselView extends CarouselView {
+
+    public RecentApplicationsCarouselView(Context context) {
+        this(context, null);
+    }
+
+    public RecentApplicationsCarouselView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public Info getRenderScriptInfo() {
+        return new Info(R.raw.carousel);
+    }
+
+}
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 4f080d5..81091e3 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;
@@ -52,6 +52,8 @@
     private static final int OP_EXPAND = 1;
     private static final int OP_COLLAPSE = 2;
 
+    private static final int MSG_SET_LIGHTS_ON = 0x00070000;
+
     private StatusBarIconList mList;
     private Callbacks mCallbacks;
     private Handler mHandler = new H();
@@ -75,6 +77,7 @@
         public void disable(int state);
         public void animateExpand();
         public void animateCollapse();
+        public void setLightsOn(boolean on);
     }
 
     public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
@@ -143,6 +146,13 @@
         }
     }
 
+    public void setLightsOn(boolean on) {
+        synchronized (mList) {
+            mHandler.removeMessages(MSG_SET_LIGHTS_ON);
+            mHandler.obtainMessage(MSG_SET_LIGHTS_ON, on ? 1 : 0, 0, null).sendToTarget();
+        }
+    }
+
     private final class H extends Handler {
         public void handleMessage(Message msg) {
             final int what = msg.what & MSG_MASK;
@@ -196,6 +206,10 @@
                     } else {
                         mCallbacks.animateCollapse();
                     }
+                    break;
+                case MSG_SET_LIGHTS_ON:
+                    mCallbacks.setLightsOn(msg.arg1 != 0);
+                    break;
             }
         }
     }
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..b01c5e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
@@ -0,0 +1,143 @@
+/*
+ * 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.HapticFeedbackConstants;
+import android.view.IWindowManager;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+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, mLongPressed;
+    int mCode;
+    int mRepeat;
+    Runnable mCheckLongPress = new Runnable() {
+        public void run() {
+            Slog.d("KeyButtonView", "longpress");
+            if (isPressed()) {
+                mLongPressed = true;
+                mRepeat++;
+                sendEvent(KeyEvent.ACTION_DOWN,
+                        KeyEvent.FLAG_FROM_SYSTEM
+                        | KeyEvent.FLAG_VIRTUAL_HARD_KEY
+                        | KeyEvent.FLAG_LONG_PRESS);
+            }
+        }
+    };
+
+    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:
+                //Slog.d("KeyButtonView", "press");
+                mDownTime = SystemClock.uptimeMillis();
+                mRepeat = 0;
+                mSending = true;
+                mLongPressed = false;
+                sendEvent(KeyEvent.ACTION_DOWN,
+                        KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, mDownTime);
+                setPressed(true);
+                removeCallbacks(mCheckLongPress);
+                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+                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_VIRTUAL_HARD_KEY
+                                        | KeyEvent.FLAG_CANCELED);
+                        setPressed(false);
+                        removeCallbacks(mCheckLongPress);
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                setPressed(false);
+                if (mSending && !mLongPressed) {
+                    mSending = false;
+                    sendEvent(KeyEvent.ACTION_UP,
+                            KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY);
+                    removeCallbacks(mCheckLongPress);
+                }
+                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..7e8a5c1 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.isClearable()) {
                     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..57ebd27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
@@ -0,0 +1,1558 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.text.TextUtils;
+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);
+
+        // 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
+        if (notification.notification.tickerText != null
+                && !TextUtils.equals(notification.notification.tickerText,
+                    oldEntry.notification.notification.tickerText)) {
+            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);
+        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;
+    }
+
+    public void setLightsOn(boolean on) {
+        if (!on) {
+            // All we do for "lights out" mode on a phone is hide the status bar,
+            // which the window manager does.  But we do need to hide the windowshade
+            // on our own.
+            animateCollapse();
+        }
+    }
+
+    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, View sb) {
+            super(context, sb);
+        }
+
+        @Override
+        public 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
+        public 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);
+            }
+        }
+
+        public 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)) {
+                animateCollapse();
+            }
+            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 0309430..e0019b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
@@ -18,9 +18,8 @@
 
 import android.app.StatusBarManager;
 import android.app.AlertDialog;
-import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothPbap;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -319,9 +318,6 @@
     private boolean mVolumeVisible;
 
     // bluetooth device status
-    private int mBluetoothHeadsetState;
-    private boolean mBluetoothA2dpConnected;
-    private int mBluetoothPbapState;
     private boolean mBluetoothEnabled;
 
     // wifi
@@ -369,9 +365,7 @@
                 onBatteryOkay(intent);
             }
             else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) ||
-                    action.equals(BluetoothHeadset.ACTION_STATE_CHANGED) ||
-                    action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED) ||
-                    action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) {
+                    action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
                 updateBluetooth(intent);
             }
             else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) ||
@@ -454,9 +448,6 @@
         } else {
             mBluetoothEnabled = false;
         }
-        mBluetoothA2dpConnected = false;
-        mBluetoothHeadsetState = BluetoothHeadset.STATE_DISCONNECTED;
-        mBluetoothPbapState = BluetoothPbap.STATE_DISCONNECTED;
         mService.setIconVisibility("bluetooth", mBluetoothEnabled);
 
         // Gps status
@@ -490,9 +481,7 @@
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(AudioManager.VIBRATE_SETTING_CHANGED_ACTION);
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-        filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
-        filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
-        filter.addAction(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
+        filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
@@ -1064,28 +1053,16 @@
         if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
             mBluetoothEnabled = state == BluetoothAdapter.STATE_ON;
-        } else if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
-            mBluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                    BluetoothHeadset.STATE_ERROR);
-        } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
-            BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
-            if (a2dp.getConnectedSinks().size() != 0) {
-                mBluetoothA2dpConnected = true;
-            } else {
-                mBluetoothA2dpConnected = false;
+        } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                BluetoothAdapter.STATE_DISCONNECTED);
+            if (state == BluetoothAdapter.STATE_CONNECTED) {
+                iconId = R.drawable.stat_sys_data_bluetooth_connected;
             }
-        } else if (action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) {
-            mBluetoothPbapState = intent.getIntExtra(BluetoothPbap.PBAP_STATE,
-                    BluetoothPbap.STATE_DISCONNECTED);
         } else {
             return;
         }
 
-        if (mBluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED || mBluetoothA2dpConnected ||
-                mBluetoothPbapState == BluetoothPbap.STATE_CONNECTED) {
-            iconId = R.drawable.stat_sys_data_bluetooth_connected;
-        }
-
         mService.setIcon("bluetooth", iconId, 0);
         mService.setIconVisibility("bluetooth", mBluetoothEnabled);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
index f3da2a3..695fdba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
@@ -17,185 +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.text.TextUtils;
-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_ICONS = false;
-    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;
-
-    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();
@@ -204,12 +72,16 @@
         mCommandQueue = new CommandQueue(this, iconList);
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        boolean[] lightsOn = new boolean[1];
         try {
-            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications);
+            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
+                    lightsOn);
         } catch (RemoteException ex) {
             // If the system process isn't there we're doomed anyway.
         }
 
+        setLightsOn(lightsOn[0]);
+
         // Set up the initial icon state
         int N = iconList.size();
         int viewIndex = 0;
@@ -233,1271 +105,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
-    }
-
-    /**
-     * 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;
-
-        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);
-
-        // 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);
+        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_ICONS) {
-            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_ICONS) {
-            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_ICONS) {
-            Slog.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex);
-        }
-        mStatusIcons.removeViewAt(viewIndex);
-    }
-
-    public void addNotification(IBinder key, StatusBarNotification notification) {
-        boolean shouldTick = true;
-        if (notification.notification.fullScreenIntent != null) {
-            shouldTick = false;
-            Slog.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
-            try {
-                notification.notification.fullScreenIntent.send();
-            } catch (PendingIntent.CanceledException e) {
-            }
-        } 
-
-        StatusBarIconView iconView = addNotificationViews(key, notification);
-        if (iconView == null) return;
-
-        if (shouldTick) {
-            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
-        if (notification.notification.tickerText != null
-                && !TextUtils.equals(notification.notification.tickerText,
-                    oldEntry.notification.notification.tickerText)) {
-            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);
-        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;
-            }
-        }
-    }
-
-    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
-                + " mTicking=" + mTicking);
-
-        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);
-        }
-        if (mDateView.getVisibility() == View.VISIBLE) {
-            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();
-        }
-    }
-
-    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() {
-            if (SPEW) Slog.d(TAG, "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() {
-            if (SPEW) Slog.d(TAG, "tickerDone");
-            mTicking = false;
-            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, null));
-            if (mExpandedVisible) {
-                setDateViewVisibility(true, com.android.internal.R.anim.push_down_in);
-            }
-        }
-
-        void tickerHalting() {
-            if (SPEW) Slog.d(TAG, "tickerHalting");
-            mTicking = false;
-            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, null));
-            if (mExpandedVisible) {
-                setDateViewVisibility(true, com.android.internal.R.anim.fade_in);
-            }
-        }
-    }
-
-    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));
-        }
-
-        if (true) {
-            // must happen on ui thread
-            mHandler.post(new Runnable() {
-                    public void run() {
-                        Slog.d(TAG, "mStatusIcons:");
-                        mStatusIcons.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 == null) ? "???" : 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) {
-            Slog.d(TAG, "DISABLE_NOTIFICATION_TICKER: "
-                + (((net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0)
-                    ? "yes" : "no"));
-            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)) {
-                animateCollapse();
-            }
-            else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
-                updateResources();
-            }
-        }
-    };
-
-    /**
-     * 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/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java
index 3a697a6..e7b0509 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java
@@ -141,7 +141,7 @@
         }
     };
 
-    Ticker(Context context, StatusBarView sb) {
+    public Ticker(Context context, View sb) {
         mContext = context;
         mTickerView = sb.findViewById(R.id.ticker);
 
@@ -163,7 +163,7 @@
     }
 
 
-    void addEntry(StatusBarNotification n) {
+    public void addEntry(StatusBarNotification n) {
         int initialCount = mSegments.size();
 
         // If what's being displayed has the same text and icon, just drop it
@@ -212,7 +212,7 @@
         }
     }
 
-    void removeEntry(StatusBarNotification n) {
+    public void removeEntry(StatusBarNotification n) {
         for (int i=mSegments.size()-1; i>=0; i--) {
             Segment seg = mSegments.get(i);
             if (n.id == seg.notification.id && n.pkg.equals(seg.notification.pkg)) {
@@ -221,13 +221,13 @@
         }
     }
 
-    void halt() {
+    public void halt() {
         mHandler.removeCallbacks(mAdvanceTicker);
         mSegments.clear();
         tickerHalting();
     }
 
-    void reflowText() {
+    public void reflowText() {
         if (mSegments.size() > 0) {
             Segment seg = mSegments.get(0);
             CharSequence text = seg.getText();
@@ -266,8 +266,8 @@
         mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY);
     }
 
-    abstract void tickerStarting();
-    abstract void tickerDone();
-    abstract void tickerHalting();
+    public abstract void tickerStarting();
+    public abstract void tickerDone();
+    public abstract void tickerHalting();
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java
index 9749ae4..8140811 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java
@@ -34,5 +34,9 @@
         super.onSizeChanged(w, h, oldw, oldh);
         mTicker.reflowText();
     }
+
+    public void setTicker(Ticker t) {
+        mTicker = t;
+    }
 }
 
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..7c7d74c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.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 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";
+
+    IconLayout mIconLayout;
+    DraggerView mDraggerView;
+
+    public NotificationIconArea(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mIconLayout = (IconLayout)findViewById(R.id.icons);
+        mDraggerView = (DraggerView) findViewById(R.id.handle);
+    }
+
+    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/NotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
new file mode 100644
index 0000000..eee0a16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.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.android.systemui.statusbar.tablet;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+import android.util.AttributeSet;
+
+public class NotificationPanel extends LinearLayout implements StatusBarPanel {
+
+    public NotificationPanel(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationPanel(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public boolean isInContentArea(int x, int y) {
+        final int l = getPaddingLeft();
+        final int r = getWidth() - getPaddingRight();
+        final int t = getPaddingTop();
+        final int b = getHeight() - getPaddingBottom();
+        return x >= l && x < r && y >= t && y < b;
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/StatusBarPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/StatusBarPanel.java
new file mode 100644
index 0000000..8fa01d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/StatusBarPanel.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+public interface StatusBarPanel {
+    public boolean isInContentArea(int x, int y);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java
new file mode 100644
index 0000000..a0f5be6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.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 com.android.systemui.statusbar.tablet;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.app.StatusBarManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.location.LocationManager;
+import android.media.AudioManager;
+import android.net.NetworkInfo;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.ScrollView;
+import android.widget.TextSwitcher;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.List;
+
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.cdma.EriInfo;
+import com.android.internal.telephony.cdma.TtyIntent;
+
+import com.android.systemui.statusbar.*;
+import com.android.systemui.R;
+
+public class SystemPanel extends LinearLayout implements StatusBarPanel {
+    private static final String TAG = "SystemPanel";
+    private static final boolean DEBUG = TabletStatusBarService.DEBUG;
+    private static final boolean DEBUG_SIGNAL = false;
+
+    private static final int MINIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_DIM + 5;
+    private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON;
+    private static final int DEFAULT_BACKLIGHT = (int) (android.os.Power.BRIGHTNESS_ON * 0.4f);
+
+    private TabletStatusBarService mBar;
+    private boolean mAirplaneMode;
+
+    private ImageButton mBrightnessButton;
+    private ImageButton mSoundButton;
+    private ImageButton mOrientationButton;
+    private ImageButton mAirplaneButton;
+    private ImageButton mGpsButton;
+    private ImageButton mBluetoothButton;
+
+    private ImageView mBatteryMeter;
+    private ImageView mSignalMeter;
+
+    private TextView mBatteryText;
+    private TextView mSignalText;
+
+    private final AudioManager mAudioManager;
+    private final WifiManager mWifiManager;
+    private final TelephonyManager mPhone;
+    private final BluetoothAdapter mBluetoothAdapter;
+
+    // state trackers for telephony code
+    IccCard.State mSimState = IccCard.State.READY;
+    int mPhoneState = TelephonyManager.CALL_STATE_IDLE;
+    int mDataState = TelephonyManager.DATA_DISCONNECTED;
+    ServiceState mServiceState;
+    SignalStrength mSignalStrength;
+
+    // state for the meters
+    boolean mWifiEnabled, mWifiConnected;
+    int mWifiLevel;
+    String mWifiSsid;
+
+    boolean mDataEnabled, mDataConnected, mDataRoaming;
+    int mDataLevel;
+
+    public boolean isInContentArea(int x, int y) {
+        final int l = getPaddingLeft();
+        final int r = getWidth() - getPaddingRight();
+        final int t = getPaddingTop();
+        final int b = getHeight() - getPaddingBottom();
+        return x >= l && x < r && y >= t && y < b;
+    }
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+                refreshSound();
+            } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+                updateBattery(intent);
+            } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)
+                    || action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)
+                    || action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)
+                    || action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                updateWifiState(intent);
+            } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
+                updateSimState(intent);
+            } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+                refreshBluetooth();
+            }
+        }
+    };
+
+    private final void updateSimState(Intent intent) {
+        String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE);
+        if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
+            mSimState = IccCard.State.ABSENT;
+        }
+        else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
+            mSimState = IccCard.State.READY;
+        }
+        else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
+            final String lockedReason = intent.getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON);
+            if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
+                mSimState = IccCard.State.PIN_REQUIRED;
+            }
+            else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
+                mSimState = IccCard.State.PUK_REQUIRED;
+            }
+            else {
+                mSimState = IccCard.State.NETWORK_LOCKED;
+            }
+        } else {
+            mSimState = IccCard.State.UNKNOWN;
+        }
+        updateDataState();
+    }
+
+    private boolean isCdma() {
+        return (mSignalStrength != null) && !mSignalStrength.isGsm();
+    }
+
+    private boolean isEvdo() {
+        return ( (mServiceState != null)
+                 && ((mServiceState.getRadioTechnology()
+                        == ServiceState.RADIO_TECHNOLOGY_EVDO_0)
+                     || (mServiceState.getRadioTechnology()
+                        == ServiceState.RADIO_TECHNOLOGY_EVDO_A)
+                     || (mServiceState.getRadioTechnology()
+                        == ServiceState.RADIO_TECHNOLOGY_EVDO_B)));
+    }
+
+    private boolean hasService() {
+        if (mServiceState != null) {
+            switch (mServiceState.getState()) {
+                case ServiceState.STATE_OUT_OF_SERVICE:
+                case ServiceState.STATE_POWER_OFF:
+                    return false;
+                default:
+                    return true;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    private int getCdmaLevel() {
+        if (mSignalStrength == null) return 0;
+        final int cdmaDbm = mSignalStrength.getCdmaDbm();
+        final int cdmaEcio = mSignalStrength.getCdmaEcio();
+        int levelDbm = 0;
+        int levelEcio = 0;
+
+        if (cdmaDbm >= -75) levelDbm = 4;
+        else if (cdmaDbm >= -85) levelDbm = 3;
+        else if (cdmaDbm >= -95) levelDbm = 2;
+        else if (cdmaDbm >= -100) levelDbm = 1;
+        else levelDbm = 0;
+
+        // Ec/Io are in dB*10
+        if (cdmaEcio >= -90) levelEcio = 4;
+        else if (cdmaEcio >= -110) levelEcio = 3;
+        else if (cdmaEcio >= -130) levelEcio = 2;
+        else if (cdmaEcio >= -150) levelEcio = 1;
+        else levelEcio = 0;
+
+        return (levelDbm < levelEcio) ? levelDbm : levelEcio;
+    }
+
+    private int getEvdoLevel() {
+        if (mSignalStrength == null) return 0;
+        int evdoDbm = mSignalStrength.getEvdoDbm();
+        int evdoSnr = mSignalStrength.getEvdoSnr();
+        int levelEvdoDbm = 0;
+        int levelEvdoSnr = 0;
+
+        if (evdoDbm >= -65) levelEvdoDbm = 4;
+        else if (evdoDbm >= -75) levelEvdoDbm = 3;
+        else if (evdoDbm >= -90) levelEvdoDbm = 2;
+        else if (evdoDbm >= -105) levelEvdoDbm = 1;
+        else levelEvdoDbm = 0;
+
+        if (evdoSnr >= 7) levelEvdoSnr = 4;
+        else if (evdoSnr >= 5) levelEvdoSnr = 3;
+        else if (evdoSnr >= 3) levelEvdoSnr = 2;
+        else if (evdoSnr >= 1) levelEvdoSnr = 1;
+        else levelEvdoSnr = 0;
+
+        return (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr;
+    }
+
+    private void updateDataState() {
+        mDataConnected = hasService() && (mDataState == TelephonyManager.DATA_CONNECTED);
+
+        if (isCdma()) {
+            // these functions return a value from 0 to 4, inclusive
+            if ((mPhoneState == TelephonyManager.CALL_STATE_IDLE) && isEvdo()){
+                mDataLevel = getEvdoLevel() * 25;
+            } else {
+                mDataLevel = getCdmaLevel() * 25;
+            }
+        } else {
+            // GSM
+            
+            int asu = (mSignalStrength == null) ? 0 : mSignalStrength.getGsmSignalStrength();
+
+            // asu on [0,31]; 99 = unknown
+            // Android has historically shown anything >=12 as "full"
+            // XXX: tune this based on Industry Best Practices(TM)
+            if (asu <= 2 || asu == 99) mDataLevel = 0;
+            else mDataLevel = (int)(((float)Math.max(asu, 15) / 15) * 100);
+
+            mDataRoaming = mPhone.isNetworkRoaming();
+
+            mDataConnected = mDataConnected
+                && (mSimState == IccCard.State.READY || mSimState == IccCard.State.UNKNOWN);
+        }
+
+        if (DEBUG_SIGNAL || DEBUG) {
+            Slog.d(TAG, "updateDataState: connected=" + mDataConnected 
+                    + " level=" + mDataLevel
+                    + " isEvdo=" + isEvdo()
+                    + " isCdma=" + isCdma()
+                    + " mPhoneState=" + mPhoneState
+                    + " mDataState=" + mDataState
+                    );
+        }
+
+        refreshSignalMeters();
+    }
+
+    private void updateWifiState(Intent intent) {
+        if (DEBUG)
+            Slog.d(TAG, "updateWifiState: " + intent);
+
+        final String action = intent.getAction();
+        final boolean wasConnected = mWifiConnected;
+
+        if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+            mWifiEnabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                    WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
+        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+            final NetworkInfo networkInfo = (NetworkInfo)
+                    intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+            mWifiConnected = networkInfo != null && networkInfo.isConnected();
+        } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
+            final NetworkInfo.DetailedState detailedState = WifiInfo.getDetailedStateOf(
+                    (SupplicantState)intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE));
+            mWifiConnected = detailedState == NetworkInfo.DetailedState.CONNECTED;
+        } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+            final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
+            int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 101);
+            mWifiLevel = mWifiConnected ? newSignalLevel : 0;
+        }
+
+        if (mWifiConnected && !wasConnected) {
+            WifiInfo info = mWifiManager.getConnectionInfo();
+            if (DEBUG)
+                Slog.d(TAG, "updateWifiState: just connected: info=" + info);
+
+            if (info != null) {
+                // grab the initial signal strength
+                mWifiLevel = WifiManager.calculateSignalLevel(info.getRssi(), 101);
+
+                // find the SSID
+                mWifiSsid = info.getSSID();
+                if (mWifiSsid == null) {
+                    // OK, it's not in the connectionInfo; we have to go hunting for it
+                    List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
+                    for (WifiConfiguration net : networks) {
+                        if (net.networkId == info.getNetworkId()) {
+                            mWifiSsid = net.SSID;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        refreshSignalMeters();
+    }
+
+    // figure out what to show: first wifi, then 3G, then nothing
+    void refreshSignalMeters() {
+        if (mSignalMeter == null) return; // no UI yet
+
+        Context ctxt = getContext();
+
+        String text = null;
+        int level = 0;
+
+        if (mWifiConnected) {
+            if (mWifiSsid == null) {
+                text = ctxt.getString(R.string.system_panel_signal_meter_wifi_nossid);
+            } else {
+                text = ctxt.getString(R.string.system_panel_signal_meter_wifi_ssid_format,
+                                      mWifiSsid);
+            }
+            level = mWifiLevel;
+        } else if (mDataConnected) {
+            text = ctxt.getString(R.string.system_panel_signal_meter_data_connected);
+            level = mDataLevel;
+        } else {
+            text = ctxt.getString(R.string.system_panel_signal_meter_disconnected);
+            level = 0;
+        }
+
+        mSignalMeter.setImageResource(R.drawable.sysbar_signal);
+        mSignalMeter.setImageLevel(level);
+        mSignalText.setText(text);
+
+        // hack for now
+        mBar.setSignalMeter(level, mWifiConnected);
+    }
+
+    public void setBar(TabletStatusBarService bar) {
+        mBar = bar;
+    }
+
+    public void updateBattery(Intent intent) {
+        final int level = intent.getIntExtra("level", 0);
+        final boolean plugged = intent.getIntExtra("plugged", 0) != 0;
+
+        mBatteryMeter.setImageResource(R.drawable.sysbar_battery);
+        mBatteryMeter.setImageLevel(level);
+        mBatteryText.setText(getContext()
+                .getString(R.string.system_panel_battery_meter_format, level));
+
+        // hack for now
+        mBar.setBatteryMeter(level, plugged);
+    }
+    
+    public SystemPanel(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SystemPanel(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        // get notified of phone state changes
+        TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        telephonyManager.listen(mPhoneStateListener,
+                          PhoneStateListener.LISTEN_SERVICE_STATE
+                        | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
+                        | PhoneStateListener.LISTEN_CALL_STATE
+                        | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
+                        | PhoneStateListener.LISTEN_DATA_ACTIVITY);
+
+        // wifi status info
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        
+        // audio status 
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
+        // mobile data 
+        mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
+
+        // Bluetooth
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+    }
+
+    public void onAttachedToWindow() {
+        TextView settingsButton = (TextView)findViewById(R.id.settings_button);
+        settingsButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                getContext().startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS)
+                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+                mBar.animateCollapse();
+            }});
+
+        mBrightnessButton = (ImageButton)findViewById(R.id.brightness);
+        mBrightnessButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                rotateBrightness();
+            }
+        });
+
+        mSoundButton = (ImageButton)findViewById(R.id.sound);
+        mSoundButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                setSilentMode(!getSilentMode());
+                mSoundButton.setAlpha(getSilentMode() ? 0x7F : 0xFF);
+            }
+        });
+        mOrientationButton = (ImageButton)findViewById(R.id.orientation);
+        mOrientationButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Toast.makeText(getContext(), "Orientation control not implemented; please adjust neck angle.", Toast.LENGTH_SHORT).show();
+            }
+        });
+
+        mAirplaneButton = (ImageButton)findViewById(R.id.airplane);
+        mAirplaneButton.setAlpha(mAirplaneMode ? 0xFF : 0x7F);
+        mAirplaneButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                boolean newMode = !getAirplaneMode();
+                Toast.makeText(getContext(), "Attempting to turn "
+                    + (newMode ? "on" : "off") + " airplane mode (flaky).",
+                    Toast.LENGTH_SHORT).show();
+                setAirplaneMode(newMode);
+            }
+        });
+
+        mGpsButton = (ImageButton)findViewById(R.id.gps);
+        mGpsButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                toggleGps();
+                refreshGps();
+            }
+        });
+
+        mBluetoothButton = (ImageButton)findViewById(R.id.bluetooth);
+        mBluetoothButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                toggleBluetooth();
+                refreshBluetooth();
+            }
+        });
+
+        // register for broadcasts
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        getContext().registerReceiver(mReceiver, filter);
+        
+        mBatteryMeter = (ImageView)findViewById(R.id.battery_meter);
+        mBatteryMeter.setImageResource(R.drawable.sysbar_battery);
+        mBatteryMeter.setImageLevel(0);
+        mSignalMeter = (ImageView)findViewById(R.id.signal_meter);
+        mBatteryMeter.setImageResource(R.drawable.sysbar_signal);
+        mBatteryMeter.setImageLevel(0);
+
+        mBatteryText = (TextView)findViewById(R.id.battery_info);
+        mSignalText = (TextView)findViewById(R.id.signal_info);
+
+        refreshSignalMeters();
+        refreshBluetooth();
+        refreshGps();
+    }
+
+    public void onDetachedFromWindow() {
+        getContext().unregisterReceiver(mReceiver);
+    }
+
+    // ----------------------------------------------------------------------
+
+//    private boolean isAutoBrightness() {
+//        Context context = getContext();
+//        try {
+//            IPowerManager power = IPowerManager.Stub.asInterface(
+//                    ServiceManager.getService("power"));
+//            if (power != null) {
+//                int brightnessMode = Settings.System.getInt(context.getContentResolver(),
+//                        Settings.System.SCREEN_BRIGHTNESS_MODE);
+//                return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+//            }
+//        } catch (RemoteException e) {
+//        } catch (Settings.SettingNotFoundException e) {
+//        }
+//        return false;
+//    }
+
+    private void rotateBrightness() {
+        int icon = R.drawable.ic_sysbar_brightness;
+        int bg = R.drawable.sysbar_toggle_bg_on;
+        Context context = getContext();
+        try {
+            IPowerManager power = IPowerManager.Stub.asInterface(
+                    ServiceManager.getService("power"));
+            if (power != null) {
+                ContentResolver cr = context.getContentResolver();
+                int brightness = Settings.System.getInt(cr,
+                        Settings.System.SCREEN_BRIGHTNESS);
+                int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
+                //Only get brightness setting if available
+                if (context.getResources().getBoolean(
+                        com.android.internal.R.bool.config_automatic_brightness_available)) {
+                    brightnessMode = Settings.System.getInt(cr,
+                            Settings.System.SCREEN_BRIGHTNESS_MODE);
+                }
+
+                // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM
+                // Technically, not a toggle...
+                if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
+                    brightness = MINIMUM_BACKLIGHT;
+                    icon = R.drawable.ic_sysbar_brightness_low;
+                    brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
+                } else if (brightness < DEFAULT_BACKLIGHT) {
+                    brightness = DEFAULT_BACKLIGHT;
+                } else if (brightness < MAXIMUM_BACKLIGHT) {
+                    brightness = MAXIMUM_BACKLIGHT;
+                } else {
+                    brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+                    brightness = MINIMUM_BACKLIGHT;
+                    icon = R.drawable.ic_sysbar_brightness_auto;
+                }
+
+                if (context.getResources().getBoolean(
+                        com.android.internal.R.bool.config_automatic_brightness_available)) {
+                    // Set screen brightness mode (automatic or manual)
+                    Settings.System.putInt(context.getContentResolver(),
+                            Settings.System.SCREEN_BRIGHTNESS_MODE,
+                            brightnessMode);
+                } else {
+                    // Make sure we set the brightness if automatic mode isn't available
+                    brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
+                }
+                if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
+                    power.setBacklightBrightness(brightness);
+                    Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness);
+                }
+            }
+        } catch (RemoteException e) {
+        } catch (Settings.SettingNotFoundException e) {
+        }
+
+        mBrightnessButton.setImageResource(icon);
+        mBrightnessButton.setBackgroundResource(bg);
+    }
+
+    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onServiceStateChanged(ServiceState serviceState) {
+            if (DEBUG_SIGNAL || DEBUG) {
+                Slog.d(TAG, "phone service state changed: " + serviceState.getState());
+            }
+            mServiceState = serviceState;
+            mAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
+            if (mAirplaneButton != null) {
+                mAirplaneButton.setImageResource(mAirplaneMode 
+                                                 ? R.drawable.ic_sysbar_airplane_on
+                                                 : R.drawable.ic_sysbar_airplane_off);
+                mAirplaneButton.setBackgroundResource(mAirplaneMode 
+                                                 ? R.drawable.sysbar_toggle_bg_on
+                                                 : R.drawable.sysbar_toggle_bg_off);
+            }
+            updateDataState();
+        }
+        @Override
+        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+            if (DEBUG_SIGNAL || DEBUG) {
+                Slog.d(TAG, "onSignalStrengthsChanged: " + signalStrength);
+            }
+            mSignalStrength = signalStrength;
+            updateDataState();
+        }
+        @Override
+        public void onCallStateChanged(int state, String incomingNumber) {
+            mPhoneState = state;
+            // In cdma, if a voice call is made, RSSI should switch to 1x.
+            if (isCdma()) {
+                updateDataState();
+            }
+        }
+
+        @Override
+        public void onDataConnectionStateChanged(int state, int networkType) {
+            if (DEBUG_SIGNAL || DEBUG) {
+                Slog.d(TAG, "onDataConnectionStateChanged: state=" + state 
+                        + " type=" + networkType);
+            }
+            mDataState = state;
+//            updateDataNetType(networkType);
+            updateDataState();
+        }
+    };
+
+    private boolean getAirplaneMode() {
+        return mAirplaneMode;
+    }
+
+    private void setAirplaneMode(boolean on) {
+        Settings.System.putInt(
+                mContext.getContentResolver(),
+                Settings.System.AIRPLANE_MODE_ON,
+                on ? 1 : 0);
+        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.putExtra("state", on);
+        getContext().sendBroadcast(intent);
+    }
+
+    boolean getSilentMode() {
+        return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+    }
+
+    void setSilentMode(boolean on) {
+        if (on) {
+            mAudioManager.setRingerMode((Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.VIBRATE_IN_SILENT, 1) == 1)
+                ? AudioManager.RINGER_MODE_VIBRATE
+                : AudioManager.RINGER_MODE_SILENT);
+        } else {
+            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        }
+    }
+
+    void refreshSound() {
+        boolean silent = getSilentMode();
+        mSoundButton.setImageResource(!silent 
+                                         ? R.drawable.ic_sysbar_sound_on
+                                         : R.drawable.ic_sysbar_sound_off);
+        mSoundButton.setBackgroundResource(!silent 
+                                         ? R.drawable.sysbar_toggle_bg_on
+                                         : R.drawable.sysbar_toggle_bg_off);
+    }
+
+    void toggleBluetooth() {
+        if (mBluetoothAdapter == null) return;
+        if (mBluetoothAdapter.isEnabled()) {
+            mBluetoothAdapter.disable();
+        } else {
+            mBluetoothAdapter.enable();
+        }
+    }
+
+    void refreshBluetooth() {
+        boolean on = mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
+        mBluetoothButton.setImageResource(on ? R.drawable.ic_sysbar_bluetooth_on
+                                             : R.drawable.ic_sysbar_bluetooth_off);
+        mBluetoothButton.setBackgroundResource(on
+                                         ? R.drawable.sysbar_toggle_bg_on
+                                         : R.drawable.sysbar_toggle_bg_off);
+    }
+
+    private boolean isGpsEnabled() {
+        ContentResolver res = mContext.getContentResolver();
+        return Settings.Secure.isLocationProviderEnabled(
+                                res, LocationManager.GPS_PROVIDER);
+    }
+
+    private void toggleGps() {
+        Settings.Secure.setLocationProviderEnabled(mContext.getContentResolver(),
+                LocationManager.GPS_PROVIDER, !isGpsEnabled());
+    }
+
+    private void refreshGps() {
+        boolean on = isGpsEnabled();
+        mGpsButton.setImageResource(on ? R.drawable.ic_sysbar_gps_on
+                                       : R.drawable.ic_sysbar_gps_off);
+        mGpsButton.setBackgroundResource(on
+                                         ? R.drawable.sysbar_toggle_bg_on
+                                         : R.drawable.sysbar_toggle_bg_off);
+    }
+}
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..0e26f52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
@@ -0,0 +1,847 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.ActivityManagerNative;
+import android.app.PendingIntent;
+import android.app.Notification;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.ScrollView;
+import android.widget.TextSwitcher;
+import android.widget.TextView;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarNotification;
+
+import com.android.systemui.statusbar.*;
+import com.android.systemui.recent.RecentApplicationsActivity;
+import com.android.systemui.R;
+
+public class TabletStatusBarService extends StatusBarService {
+    public static final boolean DEBUG = false;
+    public static final String TAG = "TabletStatusBarService";
+
+    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;
+    
+    private static final int MAX_IMAGE_LEVEL = 10000;
+
+    int mIconSize;
+
+    H mHandler = new H();
+
+    // tracking all current notifications
+    private NotificationData mNotns = new NotificationData();
+    
+    TabletStatusBarView mStatusBarView;
+    View mNotificationTrigger;
+    NotificationIconArea mNotificationIconArea;
+    View mNotificationButtons;
+    View mSystemInfo;
+    View mNavigationArea;
+
+    NotificationPanel mNotificationPanel;
+    SystemPanel mSystemPanel;
+
+    ViewGroup mPile;
+    TextView mClearButton;
+    TextView mDoNotDisturbButton;
+
+    ImageView mBatteryMeter;
+    ImageView mSignalMeter;
+    ImageView mSignalIcon;
+
+    View mBarContents;
+    View mCurtains;
+
+    NotificationIconArea.IconLayout mIconLayout;
+
+    TabletTicker mTicker;
+    View mTickerView;
+    boolean mTicking;
+
+    // for disabling the status bar
+    int mDisabled = 0;
+
+    boolean mNotificationsOn = true;
+
+    protected void addPanelWindows() {
+        final Resources res = getResources();
+        final int barHeight= res.getDimensionPixelSize(
+            com.android.internal.R.dimen.status_bar_height);
+
+        mNotificationPanel = (NotificationPanel)View.inflate(this,
+                R.layout.sysbar_panel_notifications, null);
+        mNotificationPanel.setVisibility(View.GONE);
+        mNotificationPanel.setOnTouchListener(
+                new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel));
+
+        mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);
+
+        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_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);
+
+        mSystemPanel = (SystemPanel) View.inflate(this, R.layout.sysbar_panel_system, null);
+        mSystemPanel.setVisibility(View.GONE);
+        mSystemPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_SYSTEM_PANEL,
+                    mSystemPanel));
+
+        mStatusBarView.setIgnoreChildren(1, mSystemInfo, mSystemPanel);
+
+        lp = new WindowManager.LayoutParams(
+                800,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | 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);
+        mSystemPanel.setBar(this);
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate(); // will add the main bar view
+    }
+
+    protected View makeStatusBarView() {
+        Resources res = getResources();
+
+        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
+
+        final TabletStatusBarView sb = (TabletStatusBarView)View.inflate(
+                this, R.layout.status_bar, null);
+        mStatusBarView = sb;
+
+        sb.setHandler(mHandler);
+
+        mBarContents = sb.findViewById(R.id.bar_contents);
+        mCurtains = sb.findViewById(R.id.lights_out);
+        mSystemInfo = sb.findViewById(R.id.systemInfo);
+
+        mSystemInfo.setOnLongClickListener(new SetLightsOnListener(false));
+
+        SetLightsOnListener on = new SetLightsOnListener(true);
+        mCurtains.setOnClickListener(on);
+        mCurtains.setOnLongClickListener(on);
+
+        // the button to open the notification area
+        mNotificationTrigger = sb.findViewById(R.id.expand);
+
+        // the more notifications icon
+        mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons);
+
+        // the clear and dnd buttons
+        mNotificationButtons = sb.findViewById(R.id.notificationButtons);
+        mClearButton = (TextView)mNotificationButtons.findViewById(R.id.clear_all_button);
+        mClearButton.setOnClickListener(mClearButtonListener);
+        mDoNotDisturbButton = (TextView)mNotificationButtons.findViewById(R.id.do_not_disturb);
+        mDoNotDisturbButton.setOnClickListener(mDoNotDisturbButtonListener);
+
+
+        // where the icons go
+        mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
+
+        mTicker = new TabletTicker((Context)this, (FrameLayout)sb.findViewById(R.id.ticker));
+
+        // System info (center)
+        mBatteryMeter = (ImageView) sb.findViewById(R.id.battery);
+        mSignalMeter = (ImageView) sb.findViewById(R.id.signal);
+        mSignalIcon = (ImageView) sb.findViewById(R.id.signal_icon);
+
+        // The navigation buttons
+        mNavigationArea = sb.findViewById(R.id.navigationArea);
+
+        // set the initial view visibility
+        setAreThereNotifications();
+
+        // Add the windows
+        addPanelWindows();
+
+        mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
+        mPile.removeAllViews();
+        
+        ScrollView scroller = (ScrollView)mPile.getParent();
+        scroller.setFillViewport(true);
+
+        return sb;
+    }
+
+    protected int getStatusBarGravity() {
+        return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
+    }
+
+    private class H extends Handler {
+        public void handleMessage(Message m) {
+            switch (m.what) {
+                case MSG_OPEN_NOTIFICATION_PANEL:
+                    if (DEBUG) Slog.d(TAG, "opening notifications panel");
+                    if (mNotificationPanel.getVisibility() == View.GONE) {
+                        mDoNotDisturbButton.setText(mNotificationsOn
+                                ? R.string.status_bar_do_not_disturb_button
+                                : R.string.status_bar_please_disturb_button);
+                        mNotificationPanel.setVisibility(View.VISIBLE);
+                        setViewVisibility(mNotificationIconArea, View.GONE,
+                                R.anim.notification_icons_out);
+                        setViewVisibility(mNotificationButtons, View.VISIBLE,
+                                R.anim.notification_buttons_in);
+                    }
+                    break;
+                case MSG_CLOSE_NOTIFICATION_PANEL:
+                    if (DEBUG) Slog.d(TAG, "closing notifications panel");
+                    if (mNotificationPanel.getVisibility() == View.VISIBLE) {
+                        mNotificationPanel.setVisibility(View.GONE);
+                        setViewVisibility(mNotificationIconArea, View.VISIBLE,
+                                R.anim.notification_icons_in);
+                        setViewVisibility(mNotificationButtons, View.GONE,
+                                R.anim.notification_buttons_out);
+                    }
+                    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 setBatteryMeter(int level, boolean plugged) {
+        if (DEBUG) Slog.d(TAG, "battery=" + level + (plugged ? " - plugged" : " - unplugged"));
+        mBatteryMeter.setImageResource(R.drawable.sysbar_batterymini);
+        // adjust percent to permyriad for ClipDrawable's sake
+        mBatteryMeter.setImageLevel(level * (MAX_IMAGE_LEVEL / 100));
+    }
+
+    public void setSignalMeter(int level, boolean isWifi) {
+        if (DEBUG) Slog.d(TAG, "signal=" + level);
+        if (level < 0) {
+            mSignalMeter.setImageDrawable(null);
+            mSignalMeter.setImageLevel(0);
+            mSignalIcon.setImageDrawable(null);
+        } else {
+            mSignalMeter.setImageResource(R.drawable.sysbar_wifimini);
+            // adjust to permyriad
+            mSignalMeter.setImageLevel(level * (MAX_IMAGE_LEVEL / 100));
+            mSignalIcon.setImageResource(isWifi ? R.drawable.ic_sysbar_wifi_mini 
+                                                : R.drawable.ic_sysbar_wifi_mini); // XXX
+        }
+    }
+
+    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
+        if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon);
+    }
+
+    public void updateIcon(String slot, int index, int viewIndex,
+            StatusBarIcon old, StatusBarIcon icon) {
+        if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon);
+    }
+
+    public void removeIcon(String slot, int index, int viewIndex) {
+        if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")");
+    }
+
+    public void addNotification(IBinder key, StatusBarNotification notification) {
+        if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
+        addNotificationViews(key, notification);
+
+        boolean immersive = false;
+        try {
+            immersive = ActivityManagerNative.getDefault().isTopActivityImmersive();
+            Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
+        } catch (RemoteException ex) {
+        }
+        if (immersive) {
+            // TODO: immersive mode popups for tablet
+        } 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 {
+            tick(notification);
+        }
+
+        setAreThereNotifications();
+    }
+
+    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: ticker; immersive mode
+
+        setAreThereNotifications();
+    }
+
+    public void removeNotification(IBinder key) {
+        if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO");
+        removeNotificationViews(key);
+        setAreThereNotifications();
+    }
+
+    public void disable(int state) {
+        int old = mDisabled;
+        int diff = state ^ old;
+        Slog.d(TAG, "disable... old=0x" + Integer.toHexString(old)
+                + " diff=0x" + Integer.toHexString(diff)
+                + " state=0x" + Integer.toHexString(state));
+        mDisabled = state;
+
+        // act accordingly
+        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");
+                setViewVisibility(mNotificationTrigger, View.GONE,
+                        R.anim.notification_icons_out);
+                setViewVisibility(mNotificationIconArea, View.GONE,
+                        R.anim.notification_icons_out);
+                mTicker.halt();
+            } else {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
+                setViewVisibility(mNotificationTrigger, View.VISIBLE,
+                        R.anim.notification_icons_in);
+                setViewVisibility(mNotificationIconArea, View.VISIBLE,
+                        R.anim.notification_icons_in);
+            }
+        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+            if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+                mTicker.halt();
+            }
+        }
+        if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
+            if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
+                Slog.d(TAG, "DISABLE_SYSTEM_INFO: yes");
+                setViewVisibility(mSystemInfo, View.GONE, R.anim.navigation_out);
+            } else {
+                Slog.d(TAG, "DISABLE_SYSTEM_INFO: no");
+                setViewVisibility(mSystemInfo, View.VISIBLE, R.anim.navigation_in);
+            }
+        }
+        if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) {
+            if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) {
+                Slog.d(TAG, "DISABLE_NAVIGATION: yes");
+                setViewVisibility(mNavigationArea, View.GONE, R.anim.navigation_out);
+            } else {
+                Slog.d(TAG, "DISABLE_NAVIGATION: no");
+                setViewVisibility(mNavigationArea, View.VISIBLE, R.anim.navigation_in);
+            }
+        }
+    }
+
+    private boolean hasTicker(Notification n) {
+        return !TextUtils.isEmpty(n.tickerText)
+                || !TextUtils.isEmpty(n.tickerTitle)
+                || !TextUtils.isEmpty(n.tickerSubtitle);
+    }
+
+    private void tick(StatusBarNotification n) {
+        // Don't show the ticker when the windowshade is open.
+        if (mNotificationPanel.getVisibility() == View.VISIBLE) {
+            return;
+        }
+        // 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 (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) {
+            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
+                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
+                mTicker.add(n);
+            }
+        }
+    }
+
+    public void animateExpand() {
+        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
+        mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
+    }
+
+    public void animateCollapse() {
+        mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL);
+        mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL);
+        mHandler.removeMessages(MSG_CLOSE_SYSTEM_PANEL);
+        mHandler.sendEmptyMessage(MSG_CLOSE_SYSTEM_PANEL);
+    }
+
+    public void setLightsOn(boolean on) {
+        if (on) {
+            setViewVisibility(mCurtains, View.GONE, R.anim.lights_out_out);
+            setViewVisibility(mBarContents, View.VISIBLE, R.anim.status_bar_in);
+        } else {
+            animateCollapse();
+            setViewVisibility(mCurtains, View.VISIBLE, R.anim.lights_out_in);
+            setViewVisibility(mBarContents, View.GONE, R.anim.status_bar_out);
+        }
+    }
+
+    private void setAreThereNotifications() {
+        final boolean hasClearable = mNotns.hasClearableItems();
+
+        //Slog.d(TAG, "setAreThereNotifications hasClerable=" + hasClearable);
+
+        // Show or hide the "Clear all" button.  Note that we don't do an animation
+        // if it's not on screen, so that if someone opens the bar right then they
+        // don't see the animation in progress.
+        // (no ongoing notifications are clearable)
+        if (hasClearable) {
+            if (mNotificationButtons.getVisibility() == View.VISIBLE) {
+                setViewVisibility(mClearButton, View.VISIBLE, R.anim.notification_buttons_in);
+            } else {
+                mClearButton.setVisibility(View.VISIBLE);
+            }
+        } else {
+            if (mNotificationButtons.getVisibility() == View.VISIBLE) {
+                setViewVisibility(mClearButton, View.GONE, R.anim.notification_buttons_out);
+            } else {
+                mClearButton.setVisibility(View.GONE);
+            }
+        }
+
+        /*
+        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);
+        }
+        */
+    }
+
+    public void notificationIconsClicked(View v) {
+        if (DEBUG) Slog.d(TAG, "clicked notification icons");
+        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
+            int msg = (mNotificationPanel.getVisibility() == View.GONE) 
+                ? MSG_OPEN_NOTIFICATION_PANEL
+                : MSG_CLOSE_NOTIFICATION_PANEL;
+            mHandler.removeMessages(msg);
+            mHandler.sendEmptyMessage(msg);
+        }
+    }
+
+    public void systemInfoClicked(View v) {
+        if (DEBUG) Slog.d(TAG, "clicked system info");
+        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
+            int msg = (mSystemPanel.getVisibility() == View.GONE) 
+                ? MSG_OPEN_SYSTEM_PANEL
+                : MSG_CLOSE_SYSTEM_PANEL;
+            mHandler.removeMessages(msg);
+            mHandler.sendEmptyMessage(msg);
+        }
+    }
+
+    public void recentButtonClicked(View v) {
+        if (DEBUG) Slog.d(TAG, "clicked recent apps");
+        Intent intent = new Intent();
+        intent.setClass(this, RecentApplicationsActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        startActivity(intent);
+    }
+
+    /**
+     * 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 View.OnClickListener mDoNotDisturbButtonListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            mNotificationsOn = !mNotificationsOn;
+            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) {
+        if (DEBUG) {
+            Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
+        }
+        // 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, mPile)) {
+            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
+                    + notification);
+            return null;
+        }
+        // Add the icon.
+        mNotns.add(entry);
+        refreshIcons();
+
+        return iconView;
+    }
+
+    private void refreshIcons() {
+        // XXX: need to implement a new limited linear layout class
+        // to avoid removing & readding everything
+
+        int N = mNotns.size();
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize, mIconSize);
+
+        if (DEBUG) {
+            Slog.d(TAG, "refreshing icons (" + N + " notifications, mIconLayout="
+                    + mIconLayout + ", mPile=" + mPile);
+        }
+
+        mIconLayout.removeAllViews();
+        for (int i=0; i<4; i++) {
+            if (i>=N) break;
+            mIconLayout.addView(mNotns.get(N-i-1).icon, i, params);
+        }
+
+        mPile.removeAllViews();
+        for (int i=0; i<N; i++) {
+            mPile.addView(mNotns.get(N-i-1).row);
+        }
+    }
+
+    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);
+        if (entry.notification.isClearable()) {
+            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();
+                    }
+                });
+        } else {
+            vetoButton.setVisibility(View.INVISIBLE);
+        }
+
+        // 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;
+    }
+
+    public class SetLightsOnListener implements View.OnLongClickListener,
+           View.OnClickListener {
+        private boolean mOn;
+
+        SetLightsOnListener(boolean on) {
+            mOn = on;
+        }
+
+        public void onClick(View v) {
+            try {
+                mBarService.setLightsOn(mOn);
+            } catch (RemoteException ex) {
+                // system process
+            }
+        }
+
+        public boolean onLongClick(View v) {
+            try {
+                mBarService.setLightsOn(mOn);
+            } catch (RemoteException ex) {
+                // system process
+            }
+            return true;
+        }
+
+    }
+
+    public class TouchOutsideListener implements View.OnTouchListener {
+        private int mMsg;
+        private StatusBarPanel mPanel;
+
+        public TouchOutsideListener(int msg, StatusBarPanel panel) {
+            mMsg = msg;
+            mPanel = panel;
+        }
+
+        public boolean onTouch(View v, MotionEvent ev) {
+            final int action = ev.getAction();
+            if (action == MotionEvent.ACTION_OUTSIDE
+                    || (action == MotionEvent.ACTION_DOWN
+                        && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
+                mHandler.removeMessages(mMsg);
+                mHandler.sendEmptyMessage(mMsg);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private void setViewVisibility(View v, int vis, int anim) {
+        if (v.getVisibility() != vis) {
+            //Slog.d(TAG, "setViewVisibility vis=" + (vis == View.VISIBLE) + " v=" + v);
+            v.setAnimation(AnimationUtils.loadAnimation((Context)this, anim));
+            v.setVisibility(vis);
+        }
+    }
+}
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarView.java
new file mode 100644
index 0000000..d836e4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarView.java
@@ -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.
+ */
+
+package com.android.systemui.statusbar.tablet;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+public class TabletStatusBarView extends FrameLayout {
+    private Handler mHandler;
+
+    private View[] mIgnoreChildren = new View[2];
+    private View[] mPanels = new View[2];
+    private int[] mPos = new int[2];
+
+    public TabletStatusBarView(Context context) {
+        super(context);
+    }
+
+    public TabletStatusBarView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mHandler.removeMessages(TabletStatusBarService.MSG_CLOSE_NOTIFICATION_PANEL);
+            mHandler.sendEmptyMessage(TabletStatusBarService.MSG_CLOSE_NOTIFICATION_PANEL);
+            mHandler.removeMessages(TabletStatusBarService.MSG_CLOSE_SYSTEM_PANEL);
+            mHandler.sendEmptyMessage(TabletStatusBarService.MSG_CLOSE_SYSTEM_PANEL);
+
+            for (int i=0; i<mPanels.length; i++) {
+                if (mPanels[i].getVisibility() == View.VISIBLE) {
+                    if (eventInside(mIgnoreChildren[i], ev)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    private boolean eventInside(View v, MotionEvent ev) {
+        // assume that x and y are window coords because we are.
+        final int x = (int)ev.getX();
+        final int y = (int)ev.getY();
+
+        final int[] p = mPos;
+        v.getLocationInWindow(p);
+
+        final int l = p[0];
+        final int t = p[1];
+        final int r = p[0] + v.getWidth();
+        final int b = p[1] + v.getHeight();
+
+        return x >= l && x < r && y >= t && y < b;
+    }
+
+    public void setHandler(Handler h) {
+        mHandler = h;
+    }
+
+    public void setIgnoreChildren(int index, View ignore, View panel) {
+        mIgnoreChildren[index] = ignore;
+        mPanels[index] = panel;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java
new file mode 100644
index 0000000..3c3139f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.content.Context;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.statusbar.StatusBarNotification;
+
+import com.android.systemui.R;
+
+import java.util.Arrays;
+
+public class TabletTicker extends Handler {
+    private static final String TAG = "StatusBar.TabletTicker";
+
+    private static final int MSG_ADVANCE = 1;
+
+    private static final int ADVANCE_DELAY = 5000; // 5 seconds
+
+    private Context mContext;
+    private FrameLayout mParent;
+
+    private StatusBarNotification mCurrentNotification;
+    private View mCurrentView;
+
+    private StatusBarNotification[] mQueue;
+    private int mQueuePos;
+
+    public TabletTicker(Context context, FrameLayout parent) {
+        mContext = context;
+        mParent = parent;
+
+        // TODO: Make this a configuration value.
+        // 3 is enough to let us see most cases, but not get so far behind that it's annoying.
+        mQueue = new StatusBarNotification[3];
+    }
+
+    public void add(StatusBarNotification notification) {
+        if (false) {
+            Slog.d(TAG, "add mCurrentNotification=" + mCurrentNotification
+                    + " mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue));
+        }
+        mQueue[mQueuePos] = notification;
+
+        // If nothing is running now, start the next one
+        if (mCurrentNotification == null) {
+            sendEmptyMessage(MSG_ADVANCE);
+        }
+
+        if (mQueuePos < mQueue.length - 1) {
+            mQueuePos++;
+        }
+    }
+
+    public void halt() {
+        removeMessages(MSG_ADVANCE);
+        if (mCurrentView != null) {
+            final int N = mQueue.length;
+            for (int i=0; i<N; i++) {
+                mQueue[i] = null;
+            }
+            mQueuePos = 0;
+            sendEmptyMessage(MSG_ADVANCE);
+        }
+    }
+
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_ADVANCE:
+                advance();
+                break;
+        }
+    }
+
+    private void advance() {
+        // Out with the old...
+        if (mCurrentView != null) {
+            mParent.removeView(mCurrentView);
+            mCurrentView = null;
+            mCurrentNotification = null;
+        }
+
+        // In with the new...
+        final StatusBarNotification next = dequeue();
+        if (next != null) {
+            mCurrentNotification = next;
+            mCurrentView = makeTickerView(next);
+            mParent.addView(mCurrentView);
+            sendEmptyMessageDelayed(MSG_ADVANCE, ADVANCE_DELAY);
+        }
+    }
+
+    private StatusBarNotification dequeue() {
+        StatusBarNotification notification = mQueue[0];
+        if (false) {
+            Slog.d(TAG, "dequeue mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue));
+        }
+        final int N = mQueuePos;
+        for (int i=0; i<N; i++) {
+            mQueue[i] = mQueue[i+1];
+        }
+        mQueue[N] = null;
+        if (mQueuePos > 0) {
+            mQueuePos--;
+        }
+        return notification;
+    }
+
+    private View makeTickerView(StatusBarNotification notification) {
+        final Notification n = notification.notification;
+
+        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        int layoutId;
+        ViewGroup group;
+        if (n.tickerTitle != null || n.tickerSubtitle != null) {
+            group = (ViewGroup)inflater.inflate(R.layout.ticker, mParent, false);
+            if (n.tickerTitle != null) {
+                final TextView title = (TextView)group.findViewById(R.id.title);
+                title.setText(n.tickerTitle);
+            }
+            if (n.tickerSubtitle != null) {
+                final TextView subtitle = (TextView)group.findViewById(R.id.subtitle);
+                subtitle.setText(n.tickerSubtitle);
+            }
+        } else {
+            group = (ViewGroup)inflater.inflate(R.layout.ticker_compat, mParent, false);
+            TextView tv = (TextView)group.findViewById(R.id.text);
+            tv.setText(n.tickerText);
+        }
+
+        // No more than 2 icons.
+        if (n.tickerIcons != null) {
+            int N = n.tickerIcons.length;
+            if (N > 2) {
+                N = 2;
+            }
+            for (int i=N-1; i>= 0; i--) {
+                Bitmap b = n.tickerIcons[i];
+                if (b != null) {
+                    ImageView iv = (ImageView)inflater.inflate(R.layout.ticker_icon, group, false);
+                    iv.setImageBitmap(b);
+                    group.addView(iv, 0);
+                }
+            }
+        }
+
+        return group;
+    }
+}
+
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/packages/VpnServices/res/values-fr/strings.xml b/packages/VpnServices/res/values-fr/strings.xml
index 4a93e0a..80fefa4 100644
--- a/packages/VpnServices/res/values-fr/strings.xml
+++ b/packages/VpnServices/res/values-fr/strings.xml
@@ -2,7 +2,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4589592829302498102">"Services VPN"</string>
-    <string name="vpn_notification_title_connected" msgid="8598654486956133580">"VPN <xliff:g id="PROFILENAME">%s</xliff:g> connecté"</string>
+    <string name="vpn_notification_title_connected" msgid="8598654486956133580">"VPN \n<xliff:g id="PROFILENAME">%s</xliff:g> connecté"</string>
     <string name="vpn_notification_title_disconnected" msgid="6216572264382192027">"VPN <xliff:g id="PROFILENAME">%s</xliff:g> déconnecté"</string>
     <string name="vpn_notification_hint_disconnected" msgid="1952209867082269429">"Touchez l\'écran pour vous reconnecter à un VPN."</string>
 </resources>
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
index ba1d7f5..70a4b20 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
@@ -113,7 +113,15 @@
                     flags, PixelFormat.TRANSLUCENT);
             lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
             lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen;
-            lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+
+            if (mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation)) {
+                Log.d(TAG, "Rotation sensor for lock screen On!");
+                lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;
+            } else {
+                Log.d(TAG, "Rotation sensor for lock screen Off!");
+                lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+            }
+
             lp.setTitle("Keyguard");
             mWindowLayoutParams = lp;
 
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
index 88203c3..d19f318 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.policy.impl;
 
+import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+
 import com.android.internal.telephony.IccCard;
 import com.android.internal.widget.LockPatternUtils;
 
@@ -41,6 +43,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
 import android.telephony.TelephonyManager;
 import android.util.Config;
 import android.util.EventLog;
@@ -93,6 +96,7 @@
  */
 public class KeyguardViewMediator implements KeyguardViewCallback,
         KeyguardUpdateMonitor.SimStateCallback {
+    private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
     private final static boolean DEBUG = false && Config.LOGD;
     private final static boolean DBG_WAKE = DEBUG || true;
 
@@ -133,7 +137,7 @@
      * turning on the keyguard (i.e, the user has this much time to turn
      * the screen back on without having to face the keyguard).
      */
-    private static final int KEYGUARD_DELAY_MS = 5000;
+    private static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000;
 
     /**
      * How long we'll wait for the {@link KeyguardViewCallback#keyguardDoneDrawing()}
@@ -244,6 +248,7 @@
      * the keyguard.
      */
     private boolean mWaitingUntilKeyguardVisible = false;
+    private LockPatternUtils mLockPatternUtils;
 
     public KeyguardViewMediator(Context context, PhoneWindowManager callback,
             LocalPowerManager powerManager) {
@@ -275,8 +280,9 @@
 
         mUpdateMonitor.registerSimStateCallback(this);
 
-        mKeyguardViewProperties = new LockPatternKeyguardViewProperties(
-                new LockPatternUtils(mContext), mUpdateMonitor);
+        mLockPatternUtils = new LockPatternUtils(mContext);
+        mKeyguardViewProperties 
+                = new LockPatternKeyguardViewProperties(mLockPatternUtils, mUpdateMonitor);
 
         mKeyguardViewManager = new KeyguardViewManager(
                 context, WindowManagerImpl.getDefault(), this,
@@ -326,15 +332,46 @@
                 // to enable it a little bit later (i.e, give the user a chance
                 // to turn the screen back on within a certain window without
                 // having to unlock the screen)
-                long when = SystemClock.elapsedRealtime() + KEYGUARD_DELAY_MS;
-                Intent intent = new Intent(DELAYED_KEYGUARD_ACTION);
-                intent.putExtra("seq", mDelayedShowingSequence);
-                PendingIntent sender = PendingIntent.getBroadcast(mContext,
-                        0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
-                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when,
-                        sender);
-                if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = "
-                                 + mDelayedShowingSequence);
+                final ContentResolver cr = mContext.getContentResolver();
+
+                // From DisplaySettings
+                long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT,
+                        KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT);
+
+                // From SecuritySettings
+                final long lockAfterTimeout = Settings.Secure.getInt(cr,
+                        Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                        KEYGUARD_LOCK_AFTER_DELAY_DEFAULT);
+
+                // From DevicePolicyAdmin
+                final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
+                        .getMaximumTimeToLock(null);
+
+                long timeout;
+                if (policyTimeout > 0) {
+                    // policy in effect. Make sure we don't go beyond policy limit.
+                    displayTimeout = Math.max(displayTimeout, 0); // ignore negative values
+                    timeout = Math.min(policyTimeout - displayTimeout, lockAfterTimeout);
+                } else {
+                    timeout = lockAfterTimeout;
+                }
+
+                if (timeout <= 0) {
+                    // Lock now
+                    mSuppressNextLockSound = true;
+                    doKeyguard();
+                } else {
+                    // Lock in the future
+                    long when = SystemClock.elapsedRealtime() + timeout;
+                    Intent intent = new Intent(DELAYED_KEYGUARD_ACTION);
+                    intent.putExtra("seq", mDelayedShowingSequence);
+                    PendingIntent sender = PendingIntent.getBroadcast(mContext,
+                            0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when,
+                            sender);
+                    if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = "
+                                     + mDelayedShowingSequence);
+                }
             } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) {
                 // Do not enable the keyguard if the prox sensor forced the screen off.
             } else {
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 27706ef..822be46 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -645,7 +645,9 @@
             // Show LockScreen first for any screen other than Pattern unlock.
             final boolean usingLockPattern = mLockPatternUtils.getKeyguardStoredPasswordQuality()
                     == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-            if (isSecure() && usingLockPattern) {
+
+            boolean showSlidingTab = getResources().getBoolean(R.bool.config_enableSlidingTabFirst);
+            if (isSecure() && (usingLockPattern || !showSlidingTab)) {
                 return Mode.UnlockScreen;
             } else {
                 return Mode.LockScreen;
@@ -667,6 +669,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 +690,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..3583ab9 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/LockScreen.java
@@ -20,6 +20,8 @@
 import com.android.internal.telephony.IccCard;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.SlidingTab;
+import com.android.internal.widget.WaveView;
+import com.android.internal.widget.WaveView.OnTriggerListener;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -46,8 +48,9 @@
  * information about the device depending on its state, and how to get
  * past it, as applicable.
  */
-class LockScreen extends LinearLayout implements KeyguardScreen, KeyguardUpdateMonitor.InfoCallback,
-        KeyguardUpdateMonitor.SimStateCallback, SlidingTab.OnTriggerListener {
+class LockScreen extends LinearLayout implements KeyguardScreen,
+        KeyguardUpdateMonitor.InfoCallback,
+        KeyguardUpdateMonitor.SimStateCallback {
 
     private static final boolean DBG = false;
     private static final String TAG = "LockScreen";
@@ -59,12 +62,7 @@
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final KeyguardScreenCallback mCallback;
 
-    private TextView mCarrier;
-    private SlidingTab mSelector;
-    private TextView mTime;
-    private TextView mDate;
-    private TextView mStatus1;
-    private TextView mStatus2;
+    private SlidingTab mSlidingTab;
     private TextView mScreenLocked;
     private TextView mEmergencyCallText;
     private Button mEmergencyCallButton;
@@ -93,6 +91,11 @@
     private java.text.DateFormat mTimeFormat;
     private boolean mEnableMenuKeyInLockScreen;
 
+    private StatusView mStatusView;
+    private WaveView mEnergyWave;
+    private SlidingTabMethods mSlidingTabMethods;
+    private WaveViewMethods mWaveViewMethods;
+
     /**
      * The status of this lock screen.
      */
@@ -144,6 +147,91 @@
         }
     }
 
+    class SlidingTabMethods implements SlidingTab.OnTriggerListener {
+
+        private void updateRightTabResources() {
+            boolean vibe = mSilentMode
+                && (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE);
+
+            mSlidingTab.setRightTabResources(
+                    mSilentMode ? ( vibe ? R.drawable.ic_jog_dial_vibrate_on
+                                         : R.drawable.ic_jog_dial_sound_off )
+                                : R.drawable.ic_jog_dial_sound_on,
+                    mSilentMode ? R.drawable.jog_tab_target_yellow
+                                : R.drawable.jog_tab_target_gray,
+                    mSilentMode ? R.drawable.jog_tab_bar_right_sound_on
+                                : R.drawable.jog_tab_bar_right_sound_off,
+                    mSilentMode ? R.drawable.jog_tab_right_sound_on
+                                : R.drawable.jog_tab_right_sound_off);
+        }
+
+        /** {@inheritDoc} */
+        public void onTrigger(View v, int whichHandle) {
+            if (whichHandle == SlidingTab.OnTriggerListener.LEFT_HANDLE) {
+                mCallback.goToUnlockScreen();
+            } else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
+                // toggle silent mode
+                mSilentMode = !mSilentMode;
+                if (mSilentMode) {
+                    final boolean vibe = (Settings.System.getInt(
+                        getContext().getContentResolver(),
+                        Settings.System.VIBRATE_IN_SILENT, 1) == 1);
+
+                    mAudioManager.setRingerMode(vibe
+                        ? AudioManager.RINGER_MODE_VIBRATE
+                        : AudioManager.RINGER_MODE_SILENT);
+                } else {
+                    mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+                }
+
+                updateRightTabResources();
+
+                String message = mSilentMode ?
+                        getContext().getString(R.string.global_action_silent_mode_on_status) :
+                        getContext().getString(R.string.global_action_silent_mode_off_status);
+
+                final int toastIcon = mSilentMode
+                    ? R.drawable.ic_lock_ringer_off
+                    : R.drawable.ic_lock_ringer_on;
+
+                final int toastColor = mSilentMode
+                    ? getContext().getResources().getColor(R.color.keyguard_text_color_soundoff)
+                    : getContext().getResources().getColor(R.color.keyguard_text_color_soundon);
+                toastMessage(mScreenLocked, message, toastColor, toastIcon);
+                mCallback.pokeWakelock();
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void onGrabbedStateChange(View v, int grabbedState) {
+            if (grabbedState == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
+                mSilentMode = isSilentMode();
+                mSlidingTab.setRightHintText(mSilentMode ? R.string.lockscreen_sound_on_label
+                        : R.string.lockscreen_sound_off_label);
+            }
+            mCallback.pokeWakelock();
+        }
+    }
+
+    class WaveViewMethods implements WaveView.OnTriggerListener {
+        /** {@inheritDoc} */
+        public void onTrigger(View v, int whichHandle) {
+            if (whichHandle == WaveView.OnTriggerListener.CENTER_HANDLE) {
+                // Delay hiding lock screen long enough for animation to finish
+                postDelayed(new Runnable() {
+                    public void run() {
+                        mCallback.goToUnlockScreen();
+                    }
+                }, 500);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void onGrabbedStateChange(View v, int grabbedState) {
+            mCallback.pokeWakelock();
+        }
+    }
+
     /**
      * In general, we enable unlocking the insecure key guard with the menu key. However, there are
      * some cases where we wish to disable it, notably when the menu button placement or technology
@@ -195,19 +283,9 @@
             inflater.inflate(R.layout.keyguard_screen_tab_unlock_land, this, true);
         }
 
-        mCarrier = (TextView) findViewById(R.id.carrier);
-        // Required for Marquee to work
-        mCarrier.setSelected(true);
-        mCarrier.setTextColor(0xffffffff);
-
-        mDate = (TextView) findViewById(R.id.date);
-        mStatus1 = (TextView) findViewById(R.id.status1);
-        mStatus2 = (TextView) findViewById(R.id.status2);
+        mStatusView = new StatusView(this, mUpdateMonitor, mLockPatternUtils);
 
         mScreenLocked = (TextView) findViewById(R.id.screenLocked);
-        mSelector = (SlidingTab) findViewById(R.id.tab_selector);
-        mSelector.setHoldAfterTrigger(true, false);
-        mSelector.setLeftHintText(R.string.lockscreen_unlock_label);
 
         mEmergencyCallText = (TextView) findViewById(R.id.emergencyCallText);
         mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton);
@@ -220,7 +298,6 @@
             }
         });
 
-
         setFocusable(true);
         setFocusableInTouchMode(true);
         setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
@@ -231,15 +308,25 @@
         mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
         mSilentMode = isSilentMode();
 
-        mSelector.setLeftTabResources(
-                R.drawable.ic_jog_dial_unlock,
-                R.drawable.jog_tab_target_green,
-                R.drawable.jog_tab_bar_left_unlock,
-                R.drawable.jog_tab_left_unlock);
-
-        updateRightTabResources();
-
-        mSelector.setOnTriggerListener(this);
+        mSlidingTab = (SlidingTab) findViewById(R.id.tab_selector);
+        mEnergyWave = (WaveView) findViewById(R.id.wave_view);
+        if (mSlidingTab != null) {
+            mSlidingTab.setHoldAfterTrigger(true, false);
+            mSlidingTab.setLeftHintText(R.string.lockscreen_unlock_label);
+            mSlidingTab.setLeftTabResources(
+                    R.drawable.ic_jog_dial_unlock,
+                    R.drawable.jog_tab_target_green,
+                    R.drawable.jog_tab_bar_left_unlock,
+                    R.drawable.jog_tab_left_unlock);
+            mSlidingTabMethods = new SlidingTabMethods();
+            mSlidingTab.setOnTriggerListener(mSlidingTabMethods);
+            mSlidingTabMethods.updateRightTabResources();
+        } else if (mEnergyWave != null) {
+            mWaveViewMethods = new WaveViewMethods();
+            mEnergyWave.setOnTriggerListener(mWaveViewMethods);
+        } else {
+            throw new IllegalStateException("Must have either SlidingTab or WaveView defined");
+        }
 
         resetStatusInfo(updateMonitor);
     }
@@ -248,22 +335,6 @@
         return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
     }
 
-    private void updateRightTabResources() {
-        boolean vibe = mSilentMode
-            && (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE);
-
-        mSelector.setRightTabResources(
-                mSilentMode ? ( vibe ? R.drawable.ic_jog_dial_vibrate_on
-                                     : R.drawable.ic_jog_dial_sound_off )
-                            : R.drawable.ic_jog_dial_sound_on,
-                mSilentMode ? R.drawable.jog_tab_target_yellow
-                            : R.drawable.jog_tab_target_gray,
-                mSilentMode ? R.drawable.jog_tab_bar_right_sound_on
-                            : R.drawable.jog_tab_bar_right_sound_off,
-                mSilentMode ? R.drawable.jog_tab_right_sound_on
-                            : R.drawable.jog_tab_right_sound_off);
-    }
-
     private void resetStatusInfo(KeyguardUpdateMonitor updateMonitor) {
         mShowingBatteryInfo = updateMonitor.shouldShowBatteryInfo();
         mPluggedIn = updateMonitor.isDevicePluggedIn();
@@ -289,53 +360,6 @@
         return false;
     }
 
-    /** {@inheritDoc} */
-    public void onTrigger(View v, int whichHandle) {
-        if (whichHandle == SlidingTab.OnTriggerListener.LEFT_HANDLE) {
-            mCallback.goToUnlockScreen();
-        } else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
-            // toggle silent mode
-            mSilentMode = !mSilentMode;
-            if (mSilentMode) {
-                final boolean vibe = (Settings.System.getInt(
-                    getContext().getContentResolver(),
-                    Settings.System.VIBRATE_IN_SILENT, 1) == 1);
-
-                mAudioManager.setRingerMode(vibe
-                    ? AudioManager.RINGER_MODE_VIBRATE
-                    : AudioManager.RINGER_MODE_SILENT);
-            } else {
-                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-            }
-
-            updateRightTabResources();
-
-            String message = mSilentMode ?
-                    getContext().getString(R.string.global_action_silent_mode_on_status) :
-                    getContext().getString(R.string.global_action_silent_mode_off_status);
-
-            final int toastIcon = mSilentMode
-                ? R.drawable.ic_lock_ringer_off
-                : R.drawable.ic_lock_ringer_on;
-
-            final int toastColor = mSilentMode
-                ? getContext().getResources().getColor(R.color.keyguard_text_color_soundoff)
-                : getContext().getResources().getColor(R.color.keyguard_text_color_soundon);
-            toastMessage(mScreenLocked, message, toastColor, toastIcon);
-            mCallback.pokeWakelock();
-        }
-    }
-
-    /** {@inheritDoc} */
-    public void onGrabbedStateChange(View v, int grabbedState) {
-        if (grabbedState == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
-            mSilentMode = isSilentMode();
-            mSelector.setRightHintText(mSilentMode ? R.string.lockscreen_sound_on_label
-                    : R.string.lockscreen_sound_off_label);
-        }
-        mCallback.pokeWakelock();
-    }
-
     /**
      * Displays a message in a text view and then restores the previous text.
      * @param textView The text view.
@@ -428,38 +452,11 @@
     }
 
     private void refreshTimeAndDateDisplay() {
-        mDate.setText(DateFormat.format(mDateFormatString, new Date()));
+        mStatusView.refreshTimeAndDateDisplay();
     }
 
     private void updateStatusLines() {
-        if (!mStatus.showStatusLines()
-                || (mCharging == null && mNextAlarm == null)) {
-            mStatus1.setVisibility(View.INVISIBLE);
-            mStatus2.setVisibility(View.INVISIBLE);
-        } else if (mCharging != null && mNextAlarm == null) {
-            // charging only
-            mStatus1.setVisibility(View.VISIBLE);
-            mStatus2.setVisibility(View.INVISIBLE);
-
-            mStatus1.setText(mCharging);
-            mStatus1.setCompoundDrawablesWithIntrinsicBounds(mChargingIcon, null, null, null);
-        } else if (mNextAlarm != null && mCharging == null) {
-            // next alarm only
-            mStatus1.setVisibility(View.VISIBLE);
-            mStatus2.setVisibility(View.INVISIBLE);
-
-            mStatus1.setText(mNextAlarm);
-            mStatus1.setCompoundDrawablesWithIntrinsicBounds(mAlarmIcon, null, null, null);
-        } else if (mCharging != null && mNextAlarm != null) {
-            // both charging and next alarm
-            mStatus1.setVisibility(View.VISIBLE);
-            mStatus2.setVisibility(View.VISIBLE);
-
-            mStatus1.setText(mCharging);
-            mStatus1.setCompoundDrawablesWithIntrinsicBounds(mChargingIcon, null, null, null);
-            mStatus2.setText(mNextAlarm);
-            mStatus2.setCompoundDrawablesWithIntrinsicBounds(mAlarmIcon, null, null, null);
-        }
+        mStatusView.updateStatusLines(mStatus.showStatusLines(), mCharging, mChargingIcon, mAlarmIcon);
     }
 
     /** {@inheritDoc} */
@@ -498,6 +495,22 @@
     }
 
     /**
+     * Enables unlocking of this screen. Typically just shows the unlock widget.
+     */
+    private void enableUnlock() {
+        if (mEnergyWave != null) mEnergyWave.setVisibility(View.VISIBLE);
+        if (mSlidingTab != null) mSlidingTab.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * Disable unlocking of this screen. Typically just hides the unlock widget.
+     */
+    private void disableUnlock() {
+        if (mEnergyWave != null) mEnergyWave.setVisibility(View.GONE);
+        if (mSlidingTab != null) mSlidingTab.setVisibility(View.GONE);
+    }
+
+    /**
      * Update the layout to match the current status.
      */
     private void updateLayout(Status status) {
@@ -509,7 +522,7 @@
         switch (status) {
             case Normal:
                 // text
-                mCarrier.setText(
+                mStatusView.setCarrierText(
                         getCarrierString(
                                 mUpdateMonitor.getTelephonyPlmn(),
                                 mUpdateMonitor.getTelephonySpn()));
@@ -518,14 +531,15 @@
                 mScreenLocked.setText("");
 
                 // layout
-                mScreenLocked.setVisibility(View.VISIBLE);
-                mSelector.setVisibility(View.VISIBLE);
+                mScreenLocked.setVisibility(View.INVISIBLE);
                 mEmergencyCallText.setVisibility(View.GONE);
+                enableUnlock();
                 break;
+
             case NetworkLocked:
                 // The carrier string shows both sim card status (i.e. No Sim Card) and
                 // carrier's name and/or "Emergency Calls Only" status
-                mCarrier.setText(
+                mStatusView.setCarrierText(
                         getCarrierString(
                                 mUpdateMonitor.getTelephonyPlmn(),
                                 getContext().getText(R.string.lockscreen_network_locked_message)));
@@ -533,23 +547,24 @@
 
                 // layout
                 mScreenLocked.setVisibility(View.VISIBLE);
-                mSelector.setVisibility(View.VISIBLE);
                 mEmergencyCallText.setVisibility(View.GONE);
+                enableUnlock();
                 break;
+
             case SimMissing:
                 // text
-                mCarrier.setText(R.string.lockscreen_missing_sim_message_short);
+                mStatusView.setCarrierText(R.string.lockscreen_missing_sim_message_short);
                 mScreenLocked.setText(R.string.lockscreen_missing_sim_instructions);
 
                 // layout
                 mScreenLocked.setVisibility(View.VISIBLE);
-                mSelector.setVisibility(View.VISIBLE);
                 mEmergencyCallText.setVisibility(View.VISIBLE);
-                // do not need to show the e-call button; user may unlock
+                enableUnlock(); // do not need to show the e-call button; user may unlock
                 break;
+
             case SimMissingLocked:
                 // text
-                mCarrier.setText(
+                mStatusView.setCarrierText(
                         getCarrierString(
                                 mUpdateMonitor.getTelephonyPlmn(),
                                 getContext().getText(R.string.lockscreen_missing_sim_message_short)));
@@ -557,25 +572,27 @@
 
                 // layout
                 mScreenLocked.setVisibility(View.VISIBLE);
-                mSelector.setVisibility(View.GONE); // cannot unlock
                 mEmergencyCallText.setVisibility(View.VISIBLE);
                 mEmergencyCallButton.setVisibility(View.VISIBLE);
+                disableUnlock();
                 break;
+
             case SimLocked:
                 // text
-                mCarrier.setText(
+                mStatusView.setCarrierText(
                         getCarrierString(
                                 mUpdateMonitor.getTelephonyPlmn(),
                                 getContext().getText(R.string.lockscreen_sim_locked_message)));
 
                 // layout
                 mScreenLocked.setVisibility(View.INVISIBLE);
-                mSelector.setVisibility(View.VISIBLE);
                 mEmergencyCallText.setVisibility(View.GONE);
+                enableUnlock();
                 break;
+
             case SimPukLocked:
                 // text
-                mCarrier.setText(
+                mStatusView.setCarrierText(
                         getCarrierString(
                                 mUpdateMonitor.getTelephonyPlmn(),
                                 getContext().getText(R.string.lockscreen_sim_puk_locked_message)));
@@ -583,9 +600,9 @@
 
                 // layout
                 mScreenLocked.setVisibility(View.VISIBLE);
-                mSelector.setVisibility(View.GONE); // cannot unlock
                 mEmergencyCallText.setVisibility(View.VISIBLE);
                 mEmergencyCallButton.setVisibility(View.VISIBLE);
+                disableUnlock();
                 break;
         }
     }
@@ -652,13 +669,14 @@
 
     /** {@inheritDoc} */
     public void onPause() {
-
+        if (mEnergyWave != null) {
+            mEnergyWave.reset();
+        }
     }
 
     /** {@inheritDoc} */
     public void onResume() {
         resetStatusInfo(mUpdateMonitor);
-        mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
     }
 
     /** {@inheritDoc} */
@@ -671,11 +689,10 @@
         boolean silent = AudioManager.RINGER_MODE_NORMAL != state;
         if (silent != mSilentMode) {
             mSilentMode = silent;
-            updateRightTabResources();
+            if (mSlidingTabMethods != null) mSlidingTabMethods.updateRightTabResources();
         }
     }
 
     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..9db86aa 100644
--- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
@@ -30,6 +30,7 @@
 import android.telephony.TelephonyManager;
 import android.text.method.DigitsKeyListener;
 import android.text.method.TextKeyListener;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -50,19 +51,25 @@
 public class PasswordUnlockScreen extends LinearLayout implements KeyguardScreen,
         View.OnClickListener, KeyguardUpdateMonitor.InfoCallback, OnEditorActionListener {
 
+    private static final String TAG = "PasswordUnlockScreen";
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final KeyguardScreenCallback mCallback;
 
+    private boolean mIsAlpha;
+
     private EditText mPasswordEntry;
     private Button mEmergencyCallButton;
     private LockPatternUtils mLockPatternUtils;
     private PasswordEntryKeyboardView mKeyboardView;
+    private PasswordEntryKeyboardView mKeyboardViewAlpha;
     private PasswordEntryKeyboardHelper mKeyboardHelper;
+    private PasswordEntryKeyboardHelper mKeyboardHelperAlpha;
 
     private int mCreationOrientation;
     private int mCreationHardKeyboardHidden;
     private CountDownTimer mCountdownTimer;
-    private TextView mTitle;
+
+    private StatusView mStatusView;
 
     // To avoid accidental lockout due to events while the device in in the pocket, ignore
     // any passwords with length less than or equal to this length.
@@ -86,36 +93,65 @@
             layoutInflater.inflate(R.layout.keyguard_screen_password_landscape, this, true);
         }
 
+        mStatusView = new StatusView(this, mUpdateMonitor, mLockPatternUtils);
+
         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);
+        mKeyboardViewAlpha = (PasswordEntryKeyboardView) findViewById(R.id.keyboardAlpha);
         mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
         mPasswordEntry.setOnEditorActionListener(this);
         mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall);
         mEmergencyCallButton.setOnClickListener(this);
         mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
-        mTitle = (TextView) findViewById(R.id.enter_password_label);
 
-        mKeyboardHelper = new PasswordEntryKeyboardHelper(context, mKeyboardView, this);
-        mKeyboardHelper.setKeyboardMode(isAlpha ? PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
-                : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+        mKeyboardHelper = new PasswordEntryKeyboardHelper(context, mKeyboardView, this, false);
+        if (mKeyboardViewAlpha == null || !mIsAlpha) {
+            mKeyboardHelper.setKeyboardMode(mIsAlpha ?
+                    PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
+                    : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+            mKeyboardView.setVisibility(
+                    mCreationHardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
+                    ? View.INVISIBLE : View.VISIBLE);
+        } else {
+            mKeyboardHelperAlpha = new PasswordEntryKeyboardHelper(context, mKeyboardViewAlpha,
+                    this, false);
+            mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+            mKeyboardHelperAlpha.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
+            mKeyboardView.setVisibility(View.GONE);
+            mKeyboardViewAlpha.setVisibility(
+                    mCreationHardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
+                    ? View.INVISIBLE : View.VISIBLE);
+            mPasswordEntry.setWidth(mKeyboardViewAlpha.getLayoutParams().width);
+        }
 
-        mKeyboardView.setVisibility(mCreationHardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
-                ? View.INVISIBLE : View.VISIBLE);
+        mPasswordEntry.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_lock, 0,
+                0, 0);
         mPasswordEntry.requestFocus();
 
         // 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());
+            mStatusView.setInstructionText(R.string.keyguard_password_enter_pin_password_code);
         }
 
         mKeyboardHelper.setVibratePattern(mLockPatternUtils.isTactileFeedbackEnabled() ?
                 com.android.internal.R.array.config_virtualKeyVibePattern : 0);
+        if (mKeyboardHelperAlpha != null) {
+            mKeyboardHelperAlpha.setVibratePattern(mLockPatternUtils.isTactileFeedbackEnabled() ?
+                    com.android.internal.R.array.config_virtualKeyVibePattern : 0);
+        }
+
+        // until we get an update...
+        mStatusView.setCarrierText(LockScreen.getCarrierString(
+                        mUpdateMonitor.getTelephonyPlmn(),
+                        mUpdateMonitor.getTelephonySpn()));
     }
 
     @Override
@@ -136,8 +172,12 @@
 
     /** {@inheritDoc} */
     public void onResume() {
+        // reset status
+        mStatusView.resetStatusInfo(mUpdateMonitor, mLockPatternUtils);
+
         // start fresh
         mPasswordEntry.setText("");
+        resetStatusInfo();
         mPasswordEntry.requestFocus();
         mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
 
@@ -174,6 +214,9 @@
                 long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
                 handleAttemptLockout(deadline);
             }
+            mStatusView.setInstructionText(R.string.lockscreen_password_wrong);
+        } else if (entry.length() > 0) {
+            mStatusView.setInstructionText(R.string.lockscreen_password_wrong);
         }
         mPasswordEntry.setText("");
     }
@@ -191,14 +234,14 @@
                 String instructions = getContext().getString(
                         R.string.lockscreen_too_many_failed_attempts_countdown,
                         secondsRemaining);
-                mTitle.setText(instructions);
+                mStatusView.setInstructionText(instructions);
             }
 
             @Override
             public void onFinish() {
                 mPasswordEntry.setEnabled(true);
-                mTitle.setText(R.string.keyguard_password_enter_password_code);
                 mKeyboardView.setEnabled(true);
+                resetStatusInfo();
             }
         }.start();
     }
@@ -244,24 +287,41 @@
         return false;
     }
 
+    // ---------- InfoCallback
+
+    /** {@inheritDoc} */
+    public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
+        mStatusView.onRefreshBatteryInfo(showBatteryInfo, pluggedIn, batteryLevel);
+    }
+
+    /** {@inheritDoc} */
+    public void onTimeChanged() {
+        mStatusView.onTimeChanged();
+    }
+
+    /** {@inheritDoc} */
+    public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+        mStatusView.onRefreshCarrierInfo(plmn, spn);
+    }
+
+    /** {@inheritDoc} */
+    public void onRingerModeChanged(int state) {
+        // not currently used
+    }
+
+    // ---------- SimStateCallback
+
+    /** {@inheritDoc} */
     public void onPhoneStateChanged(String newState) {
         mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
     }
 
-    public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
-
-    }
-
-    public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
-
-    }
-
-    public void onRingerModeChanged(int state) {
-
-    }
-
-    public void onTimeChanged() {
-
+    private void resetStatusInfo() {
+        if(mIsAlpha) {
+            mStatusView.setInstructionText(R.string.keyguard_password_enter_password_code);
+        } else {
+            mStatusView.setInstructionText(R.string.keyguard_password_enter_pin_password_code);
+        }
     }
 
 }
diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
index 418e243..35fa3e5 100644
--- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
@@ -25,9 +25,6 @@
 import android.view.ViewGroup;
 import android.view.MotionEvent;
 import android.widget.Button;
-import android.widget.TextView;
-import android.text.format.DateFormat;
-import android.text.TextUtils;
 import android.util.Log;
 import com.android.internal.R;
 import com.android.internal.telephony.IccCard;
@@ -37,7 +34,6 @@
 import com.android.internal.widget.LockPatternView.Cell;
 
 import java.util.List;
-import java.util.Date;
 
 /**
  * This is the screen that shows the 9 circle unlock widget and instructs
@@ -75,27 +71,7 @@
      */
     private boolean mEnableFallback;
 
-    private String mDateFormatString;
-
-    private TextView mCarrier;
-    private TextView mDate;
-
-    // are we showing battery information?
-    private boolean mShowingBatteryInfo = false;
-
-    // last known plugged in state
-    private boolean mPluggedIn = false;
-
-    // last known battery level
-    private int mBatteryLevel = 100;
-
-    private String mNextAlarm = null;
-
-    private String mInstructions = null;
-    private TextView mStatus1;
-    private TextView mStatusSep;
-    private TextView mStatus2;
-
+    private StatusView mStatusView;
 
     private LockPatternView mLockPatternView;
 
@@ -133,15 +109,18 @@
     private void updateFooter(FooterMode mode) {
         switch (mode) {
             case Normal:
+                Log.d(TAG, "mode normal");
                 mFooterNormal.setVisibility(View.VISIBLE);
                 mFooterForgotPattern.setVisibility(View.GONE);
                 break;
             case ForgotLockPattern:
+                Log.d(TAG, "mode ForgotLockPattern");
                 mFooterNormal.setVisibility(View.GONE);
                 mFooterForgotPattern.setVisibility(View.VISIBLE);
                 mForgotPatternButton.setVisibility(View.VISIBLE);
                 break;
             case VerifyUnlocked:
+                Log.d(TAG, "mode VerifyUnlocked");
                 mFooterNormal.setVisibility(View.GONE);
                 mFooterForgotPattern.setVisibility(View.GONE);
         }
@@ -180,24 +159,16 @@
         mCreationOrientation = configuration.orientation;
 
         LayoutInflater inflater = LayoutInflater.from(context);
+
         if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) {
+            Log.d(TAG, "portrait mode");
             inflater.inflate(R.layout.keyguard_screen_unlock_portrait, this, true);
         } else {
+            Log.d(TAG, "landscape mode");
             inflater.inflate(R.layout.keyguard_screen_unlock_landscape, this, true);
         }
 
-        mCarrier = (TextView) findViewById(R.id.carrier);
-        mDate = (TextView) findViewById(R.id.date);
-
-        mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year);
-        refreshTimeAndDateDisplay();
-
-        mStatus1 = (TextView) findViewById(R.id.status1);
-        mStatusSep = (TextView) findViewById(R.id.statusSep);
-        mStatus2 = (TextView) findViewById(R.id.status2);
-
-        resetStatusInfo();
-
+        mStatusView = new StatusView(this, mUpdateMonitor, mLockPatternUtils);
 
         mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
 
@@ -249,15 +220,11 @@
         updateMonitor.registerSimStateCallback(this);
         setFocusableInTouchMode(true);
 
-        // Required to get Marquee to work.
-        mCarrier.setSelected(true);
-        mCarrier.setTextColor(0xffffffff);
-
         // until we get an update...
-        mCarrier.setText(
-                LockScreen.getCarrierString(
+        mStatusView.setCarrierText(LockScreen.getCarrierString(
                         mUpdateMonitor.getTelephonyPlmn(),
                         mUpdateMonitor.getTelephonySpn()));
+
     }
 
     private void refreshEmergencyButtonText() {
@@ -270,88 +237,6 @@
         mEnableFallback = state;
     }
 
-    private void resetStatusInfo() {
-        mInstructions = null;
-        mShowingBatteryInfo = mUpdateMonitor.shouldShowBatteryInfo();
-        mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
-        mBatteryLevel = mUpdateMonitor.getBatteryLevel();
-        mNextAlarm = mLockPatternUtils.getNextAlarm();
-        updateStatusLines();
-    }
-
-    private void updateStatusLines() {
-        if (mInstructions != null) {
-            // instructions only
-            mStatus1.setText(mInstructions);
-            if (TextUtils.isEmpty(mInstructions)) {
-                mStatus1.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
-            } else {
-                mStatus1.setCompoundDrawablesWithIntrinsicBounds(
-                        R.drawable.ic_lock_idle_lock, 0, 0, 0);
-            }
-
-            mStatus1.setVisibility(View.VISIBLE);
-            mStatusSep.setVisibility(View.GONE);
-            mStatus2.setVisibility(View.GONE);
-        } else if (mShowingBatteryInfo && mNextAlarm == null) {
-            // battery only
-            if (mPluggedIn) {
-              if (mBatteryLevel >= 100) {
-                mStatus1.setText(getContext().getString(R.string.lockscreen_charged));
-              } else {
-                  mStatus1.setText(getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel));
-              }
-            } else {
-                mStatus1.setText(getContext().getString(R.string.lockscreen_low_battery));
-            }
-            mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0);
-
-            mStatus1.setVisibility(View.VISIBLE);
-            mStatusSep.setVisibility(View.GONE);
-            mStatus2.setVisibility(View.GONE);
-
-        } else if (mNextAlarm != null && !mShowingBatteryInfo) {
-            // alarm only
-            mStatus1.setText(mNextAlarm);
-            mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0);
-
-            mStatus1.setVisibility(View.VISIBLE);
-            mStatusSep.setVisibility(View.GONE);
-            mStatus2.setVisibility(View.GONE);
-        } else if (mNextAlarm != null && mShowingBatteryInfo) {
-            // both battery and next alarm
-            mStatus1.setText(mNextAlarm);
-            mStatusSep.setText("|");
-            mStatus2.setText(getContext().getString(
-                    R.string.lockscreen_battery_short,
-                    Math.min(100, mBatteryLevel)));
-            mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0);
-            if (mPluggedIn) {
-                mStatus2.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0);
-            } else {
-                mStatus2.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
-            }
-
-            mStatus1.setVisibility(View.VISIBLE);
-            mStatusSep.setVisibility(View.VISIBLE);
-            mStatus2.setVisibility(View.VISIBLE);
-        } else {
-            // nothing specific to show; show general instructions
-            mStatus1.setText(R.string.lockscreen_pattern_instructions);
-            mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_lock, 0, 0, 0);
-
-            mStatus1.setVisibility(View.VISIBLE);
-            mStatusSep.setVisibility(View.GONE);
-            mStatus2.setVisibility(View.GONE);
-        }
-    }
-
-
-    private void refreshTimeAndDateDisplay() {
-        mDate.setText(DateFormat.format(mDateFormatString, new Date()));
-    }
-
-
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         // as long as the user is entering a pattern (i.e sending a touch
@@ -366,25 +251,21 @@
         return result;
     }
 
-
     // ---------- InfoCallback
 
     /** {@inheritDoc} */
     public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
-        mShowingBatteryInfo = showBatteryInfo;
-        mPluggedIn = pluggedIn;
-        mBatteryLevel = batteryLevel;
-        updateStatusLines();
+        mStatusView.onRefreshBatteryInfo(showBatteryInfo, pluggedIn, batteryLevel);
     }
 
     /** {@inheritDoc} */
     public void onTimeChanged() {
-        refreshTimeAndDateDisplay();
+        mStatusView.onTimeChanged();
     }
 
     /** {@inheritDoc} */
     public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
-        mCarrier.setText(LockScreen.getCarrierString(plmn, spn));
+        mStatusView.onRefreshCarrierInfo(plmn, spn);
     }
 
     /** {@inheritDoc} */
@@ -444,8 +325,8 @@
 
     /** {@inheritDoc} */
     public void onResume() {
-        // reset header
-        resetStatusInfo();
+        // reset status
+        mStatusView.resetStatusInfo(mUpdateMonitor, mLockPatternUtils);
 
         // reset lock pattern
         mLockPatternView.enableInput();
@@ -514,8 +395,8 @@
             if (mLockPatternUtils.checkPattern(pattern)) {
                 mLockPatternView
                         .setDisplayMode(LockPatternView.DisplayMode.Correct);
-                mInstructions = "";
-                updateStatusLines();
+                mStatusView.setInstructions("");
+                mStatusView.updateStatusLines();
                 mCallback.keyguardDone(true);
                 mCallback.reportSuccessfulUnlockAttempt();
             } else {
@@ -533,8 +414,9 @@
                     handleAttemptLockout(deadline);
                 } else {
                     // TODO mUnlockIcon.setVisibility(View.VISIBLE);
-                    mInstructions = getContext().getString(R.string.lockscreen_pattern_wrong);
-                    updateStatusLines();
+                    mStatusView.setInstructions(
+                            getContext().getString(R.string.lockscreen_pattern_wrong));
+                    mStatusView.updateStatusLines();
                     mLockPatternView.postDelayed(
                             mCancelPatternRunnable,
                             PATTERN_CLEAR_TIMEOUT_MS);
@@ -552,17 +434,18 @@
             @Override
             public void onTick(long millisUntilFinished) {
                 int secondsRemaining = (int) (millisUntilFinished / 1000);
-                mInstructions = getContext().getString(
+                mStatusView.setInstructions(getContext().getString(
                         R.string.lockscreen_too_many_failed_attempts_countdown,
-                        secondsRemaining);
-                updateStatusLines();
+                        secondsRemaining));
+                mStatusView.updateStatusLines();
             }
 
             @Override
             public void onFinish() {
                 mLockPatternView.setEnabled(true);
-                mInstructions = getContext().getString(R.string.lockscreen_pattern_instructions);
-                updateStatusLines();
+                mStatusView.setInstructions(getContext().getString(
+                        R.string.lockscreen_pattern_instructions));
+                mStatusView.updateStatusLines();
                 // TODO mUnlockIcon.setVisibility(View.VISIBLE);
                 mFailedPatternAttemptsSinceLastTimeout = 0;
                 if (mEnableFallback) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index dffccf8..c25df1d 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,8 +199,12 @@
             /* 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;
+        if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
+            return false; // Ignore. No title dominates.
+        }
+        if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
+            // Remove the action bar feature if we have no title. No title dominates.
+            removeFeature(FEATURE_ACTION_BAR);
         }
         return super.requestFeature(featureId);
     }
@@ -276,6 +288,8 @@
     public void setTitle(CharSequence title) {
         if (mTitleView != null) {
             mTitleView.setText(title);
+        } else if (mActionBar != null) {
+            mActionBar.setWindowTitle(title);
         }
         mTitle = title;
     }
@@ -301,7 +315,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 +329,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 +342,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
@@ -349,36 +371,37 @@
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
-        PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
-        if ((st != null) && (st.menu != null)) {
-            final MenuBuilder menuBuilder = (MenuBuilder) st.menu;
+        // Action bars handle their own menu state
+        if (mActionBar == null) {
+            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+            if ((st != null) && (st.menu != null)) {
+                final MenuBuilder menuBuilder = (MenuBuilder) st.menu;
 
-            if (st.isOpen) {
-                // Freeze state
-                final Bundle state = new Bundle();
-                menuBuilder.saveHierarchyState(state);
+                if (st.isOpen) {
+                    // Freeze state
+                    final Bundle state = new Bundle();
+                    menuBuilder.saveHierarchyState(state);
 
-                // Remove the menu views since they need to be recreated
-                // according to the new configuration
-                clearMenuViews(st);
+                    // Remove the menu views since they need to be recreated
+                    // according to the new configuration
+                    clearMenuViews(st);
 
-                // Re-open the same menu
-                reopenMenu(false);
+                    // Re-open the same menu
+                    reopenMenu(false);
 
-                // Restore state
-                menuBuilder.restoreHierarchyState(state);
+                    // Restore state
+                    menuBuilder.restoreHierarchyState(state);
 
-            } else {
-                // Clear menu views so on next menu opening, it will use
-                // the proper layout
-                clearMenuViews(st);
+                } else {
+                    // Clear menu views so on next menu opening, it will use
+                    // the proper layout
+                    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.
 
@@ -393,7 +416,12 @@
 
     @Override
     public final void openPanel(int featureId, KeyEvent event) {
-        openPanel(getPanelState(featureId, true), event);
+        if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null &&
+                mActionBar.isOverflowReserved()) {
+            mActionBar.showOverflowMenu();
+        } else {
+            openPanel(getPanelState(featureId, true), event);
+        }
     }
 
     private void openPanel(PanelFeatureState st, KeyEvent event) {
@@ -484,7 +512,10 @@
 
     @Override
     public final void closePanel(int featureId) {
-        if (featureId == FEATURE_CONTEXT_MENU) {
+        if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null &&
+                mActionBar.isOverflowReserved()) {
+            mActionBar.hideOverflowMenu();
+        } else if (featureId == FEATURE_CONTEXT_MENU) {
             closeContextMenu();
         } else {
             closePanel(getPanelState(featureId, true), true);
@@ -545,6 +576,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}.
@@ -558,7 +609,7 @@
             // The panel key was pushed, so set the chording key
             mPanelChordingKey = keyCode;
             mPanelMayLongPress = false;
-            
+
             PanelFeatureState st = getPanelState(featureId, true);
             if (!st.isOpen) {
                 if (getContext().getResources().getConfiguration().keyboard
@@ -567,7 +618,6 @@
                 }
                 return preparePanel(st, event);
             }
-            
         } else if (mPanelMayLongPress && mPanelChordingKey == keyCode
                 && (event.getFlags()&KeyEvent.FLAG_LONG_PRESS) != 0) {
             // We have had a long press while in a state where this
@@ -602,25 +652,38 @@
             }
             
             boolean playSoundEffect = false;
-            PanelFeatureState st = getPanelState(featureId, true);
-            if (st.isOpen || st.isHandled) {
+            final PanelFeatureState st = getPanelState(featureId, true);
+            if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null &&
+                    mActionBar.isOverflowReserved()) {
+                if (!mActionBar.isOverflowMenuShowing()) {
+                    final Callback cb = getCallback();
+                    if (cb != null &&
+                            cb.onPreparePanel(featureId, st.createdPanelView, st.menu)) {
+                        playSoundEffect = mActionBar.showOverflowMenu();
+                    }
+                } else {
+                    playSoundEffect = mActionBar.hideOverflowMenu();
+                }
+            } else {
+                if (st.isOpen || st.isHandled) {
 
-                // Play the sound effect if the user closed an open menu (and not if
-                // they just released a menu shortcut)
-                playSoundEffect = st.isOpen;
+                    // Play the sound effect if the user closed an open menu (and not if
+                    // they just released a menu shortcut)
+                    playSoundEffect = st.isOpen;
 
-                // Close menu
-                closePanel(st, true);
+                    // Close menu
+                    closePanel(st, true);
 
-            } else if (st.isPrepared) {
+                } else if (st.isPrepared) {
 
-                // Write 'menu opened' to event log
-                EventLog.writeEvent(50001, 0);
+                    // Write 'menu opened' to event log
+                    EventLog.writeEvent(50001, 0);
 
-                // Show menu
-                openPanel(st, event);
+                    // Show menu
+                    openPanel(st, event);
 
-                playSoundEffect = true;
+                    playSoundEffect = true;
+                }
             }
 
             if (playSoundEffect) {
@@ -777,8 +840,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;
     }
@@ -788,6 +863,21 @@
     }
 
     private void reopenMenu(boolean toggleMenuMode) {
+        if (mActionBar != null) {
+            if (!mActionBar.isOverflowMenuShowing() || !toggleMenuMode) {
+                final Callback cb = getCallback();
+                if (cb != null) {
+                    final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
+                    if (cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
+                        mActionBar.showOverflowMenu();
+                    }
+                }
+            } else {
+                mActionBar.hideOverflowMenu();
+            }
+            return;
+        }
+
         PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
 
         // Save the future expanded mode state since closePanel will reset it
@@ -1437,6 +1527,7 @@
     static private final String FOCUSED_ID_TAG = "android:focusedViewId";
     static private final String VIEWS_TAG = "android:views";
     static private final String PANELS_TAG = "android:Panels";
+    static private final String ACTION_BAR_TAG = "android:ActionBar";
 
     /** {@inheritDoc} */
     @Override
@@ -1470,6 +1561,10 @@
             outState.putSparseParcelableArray(PANELS_TAG, panelStates);
         }
 
+        if (mActionBar != null) {
+            outState.putBoolean(ACTION_BAR_TAG, mActionBar.isOverflowMenuShowing());
+        }
+
         return outState;
     }
 
@@ -1504,6 +1599,10 @@
         if (panelStates != null) {
             restorePanelState(panelStates);
         }
+
+        if (mActionBar != null && savedInstanceState.getBoolean(ACTION_BAR_TAG)) {
+            mActionBar.postShowOverflowMenu();
+        }
     }
 
     /**
@@ -1594,6 +1693,9 @@
         private boolean mWatchingForMenu;
         private int mDownY;
 
+        private ActionMode mActionMode;
+        private ActionBarContextView mActionModeView;
+
         public DecorView(Context context, int featureId) {
             super(context);
             mFeatureId = featureId;
@@ -1602,7 +1704,8 @@
         @Override
         public boolean dispatchKeyEvent(KeyEvent event) {
             final int keyCode = event.getKeyCode();
-            final boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
+            final int action = event.getAction();
+            final boolean isDown = action == KeyEvent.ACTION_DOWN;
 
             /*
              * If the user hits another key within the play sound delay, then
@@ -1659,6 +1762,14 @@
                 }
             }
 
+            // Back cancels action modes first.
+            if (mActionMode != null && keyCode == KeyEvent.KEYCODE_BACK) {
+                if (action == KeyEvent.ACTION_UP) {
+                    mActionMode.finish();
+                }
+                return true;
+            }
+
             final Callback cb = getCallback();
             final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                     : super.dispatchKeyEvent(event);
@@ -1881,6 +1992,55 @@
             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();
+            }
+
+            final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback);
+            ActionMode mode = getCallback().onStartActionMode(wrappedCallback);
+            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);
+                        if (stub != null) {
+                            mActionModeView = (ActionBarContextView) stub.inflate();
+                        }
+                    }
+                }
+
+                if (mActionModeView != null) {
+                    mode = new StandaloneActionMode(getContext(), mActionModeView, wrappedCallback);
+                    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;
         }
@@ -2060,6 +2220,37 @@
             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);
+                if (mActionModeView != null) {
+                    mActionModeView.removeAllViews();
+                }
+                mActionMode = null;
+            }
+        }
     }
 
     protected DecorView generateDecor() {
@@ -2108,6 +2299,17 @@
 
         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_windowActionBarOverlay, false)) {
+            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
+        }
+
+        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)) {
@@ -2172,11 +2374,15 @@
             } else {
                 layoutResource = com.android.internal.R.layout.screen_title_icons;
             }
+            // XXX Remove this once action bar supports these features.
+            removeFeature(FEATURE_ACTION_BAR);
             // System.out.println("Title Icons!");
         } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {
             // Special case for a window with only a progress bar (and title).
             // XXX Need to have a no-title version of embedded windows.
             layoutResource = com.android.internal.R.layout.screen_progress;
+            // XXX Remove this once action bar supports these features.
+            removeFeature(FEATURE_ACTION_BAR);
             // System.out.println("Progress!");
         } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
             // Special case for a window with a custom title.
@@ -2186,11 +2392,19 @@
             } else {
                 layoutResource = com.android.internal.R.layout.screen_custom_title;
             }
+            // XXX Remove this once action bar supports these features.
+            removeFeature(FEATURE_ACTION_BAR);
         } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
             // If no other features and not embedded, only need a title.
             // 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) {
+                if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
+                    layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
+                } else {
+                    layoutResource = com.android.internal.R.layout.screen_action_bar;
+                }
             } else {
                 layoutResource = com.android.internal.R.layout.screen_title;
             }
@@ -2275,6 +2489,22 @@
                 } else {
                     mTitleView.setText(mTitle);
                 }
+            } else {
+                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
+                if (mActionBar != null) {
+                    if (mActionBar.getTitle() == null) {
+                        mActionBar.setWindowTitle(mTitle);
+                    }
+                    // Post the panel invalidate for later; avoid application onCreateOptionsMenu
+                    // being called in the middle of onCreate or similar.
+                    mDecor.post(new Runnable() {
+                        public void run() {
+                            if (!isDestroyed()) {
+                                invalidatePanelMenu(FEATURE_ACTION_BAR);
+                            }
+                        }
+                    });
+                }
             }
         }
     }
@@ -2628,6 +2858,8 @@
 
         boolean refreshDecorView;
 
+        boolean refreshMenuContent;
+        
         boolean wasLastOpen;
         
         boolean wasLastExpanded;
@@ -2750,11 +2982,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 f21d357..33685ba 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -46,6 +46,7 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 
+import com.android.internal.R;
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.telephony.ITelephony;
@@ -88,6 +89,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
@@ -151,8 +153,11 @@
     // responsible for power management when displayed.
     static final int KEYGUARD_LAYER = 14;
     static final int KEYGUARD_DIALOG_LAYER = 15;
+    // the drag layer: input for drag-and-drop is associated with this window,
+    // which sits above all other focusable windows
+    static final int DRAG_LAYER = 16;
     // things in here CAN NOT take focus, but are shown on top of everything else.
-    static final int SYSTEM_OVERLAY_LAYER = 16;
+    static final int SYSTEM_OVERLAY_LAYER = 17;
 
     static final int APPLICATION_MEDIA_SUBLAYER = -2;
     static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
@@ -176,6 +181,7 @@
     Context mContext;
     IWindowManager mWindowManager;
     LocalPowerManager mPowerManager;
+    IStatusBarService mStatusBarService;
     Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
 
     // Vibrator pattern for haptic feedback of a long press.
@@ -198,6 +204,7 @@
     
     boolean mSafeMode;
     WindowState mStatusBar = null;
+    boolean mStatusBarCanHide;
     final ArrayList<WindowState> mStatusBarPanels = new ArrayList<WindowState>();
     WindowState mKeyguard = null;
     KeyguardViewMediator mKeyguardMediator;
@@ -264,6 +271,7 @@
     static final Rect mTmpVisibleFrame = new Rect();
     
     WindowState mTopFullscreenOpaqueWindowState;
+    boolean mTopIsFullscreen;
     boolean mForceStatusBar;
     boolean mHideLockScreen;
     boolean mDismissKeyguard;
@@ -293,6 +301,9 @@
 
     // Nothing to see here, move along...
     int mFancyRotationAnimation;
+    
+    // Enable 3D recents based on config settings.
+    private Boolean mUse3dRecents;
 
     ShortcutManager mShortcutManager;
     PowerManager.WakeLock mBroadcastWakeLock;
@@ -501,6 +512,27 @@
      * Create (if necessary) and launch the recent apps dialog
      */
     void showRecentAppsDialog() {
+        // We can't initialize this in init() since the configuration hasn't been loaded yet.
+        if (mUse3dRecents == null) {
+            mUse3dRecents = mContext.getResources().getBoolean(R.bool.config_enableRecentApps3D);
+        }
+        
+        // Use 3d Recents dialog
+        if (mUse3dRecents) {
+            try {
+                Intent intent = new Intent();
+                intent.setClassName("com.android.systemui", 
+                        "com.android.systemui.statusbar.RecentApplicationsActivity");
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
+                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                mContext.startActivity(intent);
+                return;
+            } catch (ActivityNotFoundException e) {
+                Log.e(TAG, "Failed to launch RecentAppsIntent", e);
+            }
+        }
+
+        // Fallback to dialog if we fail to launch the above.
         if (mRecentAppsDialog == null) {
             mRecentAppsDialog = new RecentApplicationsDialog(mContext);
         }
@@ -532,6 +564,7 @@
         mDeskDockIntent.addCategory(Intent.CATEGORY_DESK_DOCK);
         mDeskDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mBroadcastWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                 "PhoneWindowManager.mBroadcastWakeLock");
@@ -574,6 +607,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() {
@@ -810,6 +846,8 @@
             return TOAST_LAYER;
         case TYPE_WALLPAPER:
             return WALLPAPER_LAYER;
+        case TYPE_DRAG:
+            return DRAG_LAYER;
         }
         Log.e(TAG, "Unknown window type: " + type);
         return APPLICATION_LAYER;
@@ -986,6 +1024,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(
@@ -1244,7 +1287,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);
@@ -1277,10 +1320,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);
             }
         }
     }
@@ -1365,7 +1415,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
@@ -1397,7 +1447,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;
@@ -1526,8 +1576,8 @@
     /** {@inheritDoc} */
     public int finishAnimationLw() {
         int changes = 0;
-        
-        boolean hiding = false;
+
+        boolean topIsFullscreen = false;
         if (mStatusBar != null) {
             if (localLOGV) Log.i(TAG, "force=" + mForceStatusBar
                     + " top=" + mTopFullscreenOpaqueWindowState);
@@ -1535,17 +1585,25 @@
                 if (DEBUG_LAYOUT) Log.v(TAG, "Showing status bar");
                 if (mStatusBar.showLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT;
             } else if (mTopFullscreenOpaqueWindowState != null) {
-                //Log.i(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
-                //        + " shown frame: " + mTopFullscreenOpaqueWindowState.getShownFrameLw());
-                //Log.i(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs());
-                WindowManager.LayoutParams lp =
-                    mTopFullscreenOpaqueWindowState.getAttrs();
-                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;
+                final WindowManager.LayoutParams lp = mTopFullscreenOpaqueWindowState.getAttrs();
+                if (localLOGV) {
+                    Log.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
+                            + " shown frame: " + mTopFullscreenOpaqueWindowState.getShownFrameLw());
+                    Log.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
+                            + " lp.flags=0x" + Integer.toHexString(lp.flags));
+                }
+                topIsFullscreen = (lp.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
+                // The subtle difference between the window for mTopFullscreenOpaqueWindowState
+                // and mTopIsFullscreen is that that mTopIsFullscreen is set only if the window
+                // has the FLAG_FULLSCREEN set.  Not sure if there is another way that to be the
+                // case though.
+                if (topIsFullscreen) {
+                    if (mStatusBarCanHide) {
+                        if (DEBUG_LAYOUT) Log.v(TAG, "Hiding status bar");
+                        if (mStatusBar.hideLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT;
+                    } 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;
@@ -1553,21 +1611,36 @@
             }
         }
         
-        if (changes != 0 && hiding) {
-            IStatusBarService sbs = IStatusBarService.Stub.asInterface(ServiceManager.getService("statusbar"));
-            if (sbs != null) {
-                try {
-                    // Make sure the window shade is hidden.
-                    sbs.collapse();
-                } catch (RemoteException e) {
-                }
-            }
+        if (topIsFullscreen != mTopIsFullscreen) {
+            final boolean topIsFullscreenF = topIsFullscreen;
+            mTopIsFullscreen = topIsFullscreen;
+            mHandler.post(new Runnable() {
+                    public void run() {
+                        if (mStatusBarService == null) {
+                            // This is the one that can not go away, but it doesn't come up
+                            // before the window manager does, so don't fail if it doesn't
+                            // exist. This works as long as no fullscreen windows come up
+                            // before the status bar service does.
+                            mStatusBarService = IStatusBarService.Stub.asInterface(
+                                    ServiceManager.getService("statusbar"));
+                        }
+                        final IStatusBarService sbs = mStatusBarService;
+                        if (mStatusBarService != null) {
+                            try {
+                                sbs.setActiveWindowIsFullscreen(topIsFullscreenF);
+                            } catch (RemoteException e) {
+                                // This should be impossible because we're in the same process.
+                                mStatusBarService = null;
+                            }
+                        }
+                    }
+                });
         }
 
         // Hide the key guard if a visible window explicitly specifies that it wants to be displayed
         // when the screen is locked
         if (mKeyguard != null) {
-            if (localLOGV) Log.v(TAG, "finishLayoutLw::mHideKeyguard="+mHideLockScreen);
+            if (localLOGV) Log.v(TAG, "finishAnimationLw::mHideKeyguard="+mHideLockScreen);
             if (mDismissKeyguard && !mKeyguardMediator.isSecure()) {
                 if (mKeyguard.hideLw(true)) {
                     changes |= FINISH_LAYOUT_REDO_LAYOUT
diff --git a/policy/src/com/android/internal/policy/impl/StatusView.java b/policy/src/com/android/internal/policy/impl/StatusView.java
new file mode 100644
index 0000000..3f08cfd
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/StatusView.java
@@ -0,0 +1,255 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.internal.policy.impl;
+
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.Date;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+class StatusView {
+    private String mDateFormatString;
+
+    private TextView mCarrier;
+    private TextView mDate;
+
+    // are we showing battery information?
+    private boolean mShowingBatteryInfo = false;
+
+    // last known plugged in state
+    private boolean mPluggedIn = false;
+
+    // last known battery level
+    private int mBatteryLevel = 100;
+
+    private String mNextAlarm = null;
+
+    private String mInstructions = null;
+    private TextView mStatus1;
+    private TextView mStatus2;
+    private TextView mPropertyOf;
+
+    private boolean mHasStatus2;
+    private boolean mHasCarrier;
+    private boolean mHasDate;
+    private boolean mHasProperty;
+
+    private View mView;
+
+    private View findViewById(int id) {
+        return mView.findViewById(id);
+    }
+
+    private Context getContext() {
+        return mView.getContext();
+    }
+
+    void setInstructions(String instructions) {
+        mInstructions = instructions;
+    }
+
+    void setCarrierText(CharSequence carrierText) {
+        if (mCarrier != null) {
+            mCarrier.setText(carrierText);
+        }
+    }
+
+    void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
+        mShowingBatteryInfo = showBatteryInfo;
+        mPluggedIn = pluggedIn;
+        mBatteryLevel = batteryLevel;
+        updateStatusLines();
+    }
+
+    void onTimeChanged() {
+        refreshTimeAndDateDisplay();
+    }
+
+    public void onRingerModeChanged(int state) {
+    }
+
+    void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+        setCarrierText(LockScreen.getCarrierString(plmn, spn));
+    }
+
+    public StatusView(View view, KeyguardUpdateMonitor updateMonitor,
+                  LockPatternUtils lockPatternUtils) {
+        mView = view;
+        mCarrier = (TextView) findViewById(R.id.carrier);
+        mHasCarrier = (mCarrier != null);
+        mDate = (TextView) findViewById(R.id.date);
+        mHasDate = (mDate != null);
+        mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year);
+
+        refreshTimeAndDateDisplay();
+
+        mStatus1 = (TextView) findViewById(R.id.status1);
+        mStatus2 = (TextView) findViewById(R.id.status2);
+        mHasStatus2 = (mStatus2 != null);
+        mPropertyOf = (TextView) findViewById(R.id.propertyOf);
+        mHasProperty = (mPropertyOf != null);
+
+        resetStatusInfo(updateMonitor, lockPatternUtils);
+
+        // Required to get Marquee to work.
+        if (mHasCarrier) {
+            mCarrier.setSelected(true);
+            mCarrier.setTextColor(0xffffffff);
+        }
+
+    }
+
+    void resetStatusInfo(KeyguardUpdateMonitor updateMonitor, LockPatternUtils lockPatternUtils) {
+        mInstructions = null;
+        mShowingBatteryInfo = updateMonitor.shouldShowBatteryInfo();
+        mPluggedIn = updateMonitor.isDevicePluggedIn();
+        mBatteryLevel = updateMonitor.getBatteryLevel();
+        mNextAlarm = lockPatternUtils.getNextAlarm();
+        updateStatusLines();
+    }
+
+    void setInstructionText(int stringId) {
+        mStatus1.setText(stringId);
+        mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_lock, 0, 0, 0);
+        mStatus1.setVisibility(View.VISIBLE);
+    }
+
+    void setInstructionText(String string) {
+        mStatus1.setText(string);
+        mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_lock, 0, 0, 0);
+        mStatus1.setVisibility(View.VISIBLE);
+    }
+
+    void setCarrierText(int stringId) {
+        mCarrier.setText(stringId);
+    }
+    void setCarrierText(String string) {
+        mCarrier.setText(string);
+    }
+
+    /** Originated from PatternUnlockScreen **/
+    void updateStatusLines() {
+        if (mHasProperty) {
+            // TODO Get actual name & email
+            String name = "John Smith";
+            String email = "jsmith@gmail.com";
+            mPropertyOf.setText("Property of:\n" + name + "\n" + email);
+            mPropertyOf.setVisibility(View.VISIBLE);
+        }
+
+        if (!mHasStatus2) return;
+
+        if (mInstructions != null) {
+            // instructions only
+            mStatus1.setText(mInstructions);
+            if (TextUtils.isEmpty(mInstructions)) {
+                mStatus1.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+            } else {
+                mStatus1.setCompoundDrawablesWithIntrinsicBounds(
+                        R.drawable.ic_lock_idle_lock, 0, 0, 0);
+            }
+
+            mStatus1.setVisibility(View.VISIBLE);
+            mStatus2.setVisibility(View.INVISIBLE);
+        } else if (mShowingBatteryInfo && mNextAlarm == null) {
+            // battery only
+            if (mPluggedIn) {
+              if (mBatteryLevel >= 100) {
+                mStatus1.setText(getContext().getString(R.string.lockscreen_charged));
+              } else {
+                  mStatus1.setText(getContext().getString(R.string.lockscreen_plugged_in,
+                          mBatteryLevel));
+              }
+            } else {
+                mStatus1.setText(getContext().getString(R.string.lockscreen_low_battery));
+            }
+            mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0,
+                    0, 0);
+
+            mStatus1.setVisibility(View.VISIBLE);
+            mStatus2.setVisibility(View.INVISIBLE);
+
+        } else if (mNextAlarm != null && !mShowingBatteryInfo) {
+            // alarm only
+            mStatus1.setText(mNextAlarm);
+            mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0,
+                    0, 0);
+
+            mStatus1.setVisibility(View.VISIBLE);
+            mStatus2.setVisibility(View.INVISIBLE);
+        } else if (mNextAlarm != null && mShowingBatteryInfo) {
+            // both battery and next alarm
+            mStatus1.setText(mNextAlarm);
+            mStatus2.setText(getContext().getString(
+                    R.string.lockscreen_battery_short,
+                    Math.min(100, mBatteryLevel)));
+            mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0,
+                    0, 0);
+            if (mPluggedIn) {
+                mStatus2.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging,
+                        0, 0, 0);
+            } else {
+                mStatus2.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+            }
+
+            mStatus1.setVisibility(View.VISIBLE);
+            mStatus2.setVisibility(View.VISIBLE);
+        } else {
+            // nothing specific to show; show general instructions
+            mStatus1.setText(R.string.lockscreen_pattern_instructions);
+            mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_lock, 0,
+                    0, 0);
+
+            mStatus1.setVisibility(View.VISIBLE);
+            mStatus2.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    /** Originated from LockScreen **/
+    // TODO Merge with function above
+    void updateStatusLines(boolean showStatusLines, String charging, Drawable chargingIcon,
+            Drawable alarmIcon) {
+        if (!showStatusLines || (charging == null && mNextAlarm == null)) {
+            mStatus1.setVisibility(View.INVISIBLE);
+            mStatus2.setVisibility(View.INVISIBLE);
+        } else if (charging != null && mNextAlarm == null) {
+            // charging only
+            mStatus1.setVisibility(View.VISIBLE);
+            mStatus2.setVisibility(View.INVISIBLE);
+
+            mStatus1.setText(charging);
+            mStatus1.setCompoundDrawablesWithIntrinsicBounds(chargingIcon, null, null, null);
+        } else if (mNextAlarm != null && charging == null) {
+            // next alarm only
+            mStatus1.setVisibility(View.VISIBLE);
+            mStatus2.setVisibility(View.INVISIBLE);
+
+            mStatus1.setText(mNextAlarm);
+            mStatus1.setCompoundDrawablesWithIntrinsicBounds(alarmIcon, null, null, null);
+        } else if (charging != null && mNextAlarm != null) {
+            // both charging and next alarm
+            mStatus1.setVisibility(View.VISIBLE);
+            mStatus2.setVisibility(View.VISIBLE);
+
+            mStatus1.setText(charging);
+            mStatus1.setCompoundDrawablesWithIntrinsicBounds(chargingIcon, null, null, null);
+            mStatus2.setText(mNextAlarm);
+            mStatus2.setCompoundDrawablesWithIntrinsicBounds(alarmIcon, null, null, null);
+        }
+    }
+
+    void refreshTimeAndDateDisplay() {
+        if (mHasDate) {
+            mDate.setText(DateFormat.format(mDateFormatString, new Date()));
+        }
+    }
+
+}
diff --git a/preloaded-classes b/preloaded-classes
index 1d5fbc08..3317286 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -138,7 +138,6 @@
 android.database.MatrixCursor
 android.database.sqlite.SQLiteClosable
 android.database.sqlite.SQLiteCompiledSql
-android.database.sqlite.SQLiteContentHelper
 android.database.sqlite.SQLiteCursor
 android.database.sqlite.SQLiteDatabase
 android.database.sqlite.SQLiteDebug
@@ -563,8 +562,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 +579,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 +683,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 +724,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 +1127,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 425ca31..65d9ef7 100644
--- a/services/audioflinger/AudioPolicyManagerBase.cpp
+++ b/services/audioflinger/AudioPolicyManagerBase.cpp
@@ -1017,8 +1017,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;
 
@@ -1835,29 +1835,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/camera/libcameraservice/CameraHardwareStub.cpp b/services/camera/libcameraservice/CameraHardwareStub.cpp
index b3e0ee6..07b5a37 100644
--- a/services/camera/libcameraservice/CameraHardwareStub.cpp
+++ b/services/camera/libcameraservice/CameraHardwareStub.cpp
@@ -101,9 +101,9 @@
     mFakeCamera = 0; // paranoia
 }
 
-sp<IMemoryHeap> CameraHardwareStub::getPreviewHeap() const
+status_t CameraHardwareStub::setPreviewWindow(const sp<ANativeWindow>& buf)
 {
-    return mPreviewHeap;
+    return NO_ERROR;
 }
 
 sp<IMemoryHeap> CameraHardwareStub::getRawHeap() const
diff --git a/services/camera/libcameraservice/CameraHardwareStub.h b/services/camera/libcameraservice/CameraHardwareStub.h
index d3427ba..9b66a76 100644
--- a/services/camera/libcameraservice/CameraHardwareStub.h
+++ b/services/camera/libcameraservice/CameraHardwareStub.h
@@ -29,7 +29,7 @@
 
 class CameraHardwareStub : public CameraHardwareInterface {
 public:
-    virtual sp<IMemoryHeap> getPreviewHeap() const;
+    virtual status_t setPreviewWindow(const sp<ANativeWindow>& buf);
     virtual sp<IMemoryHeap> getRawHeap() const;
 
     virtual void        setCallbacks(notify_callback notify_cb,
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 58209fd..808c679 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -26,6 +26,7 @@
 #include <binder/MemoryBase.h>
 #include <binder/MemoryHeapBase.h>
 #include <cutils/atomic.h>
+#include <cutils/properties.h>
 #include <hardware/hardware.h>
 #include <media/AudioSystem.h>
 #include <media/mediaplayer.h>
@@ -320,6 +321,7 @@
     mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP;
     mOrientation = 0;
     mOrientationChanged = false;
+    mPlayShutterSound = true;
     cameraService->setCameraBusy(cameraId);
     cameraService->loadSound();
     LOG1("Client::Client X (pid %d)", callingPid);
@@ -337,18 +339,6 @@
     int callingPid = getCallingPid();
     LOG1("Client::~Client E (pid %d, this %p)", callingPid, this);
 
-    if (mSurface != 0 && !mUseOverlay) {
-        pthread_t thr;
-        // We unregister the buffers in a different thread because binder does
-        // not let us make sychronous transactions in a binder destructor (that
-        // is, upon our reaching a refcount of zero.)
-        pthread_create(&thr,
-                       NULL,  // attr
-                       unregister_surface,
-                       mSurface.get());
-        pthread_join(thr, NULL);
-    }
-
     // set mClientPid to let disconnet() tear down the hardware
     mClientPid = callingPid;
     disconnect();
@@ -466,6 +456,11 @@
     if (mUseOverlay) {
         mOverlayRef = 0;
     }
+    // Release the held ANativeWindow resources.
+    if (mPreviewWindow != 0) {
+        mPreviewWindow = 0;
+        mHardware->setPreviewWindow(mPreviewWindow);
+    }
     mHardware.clear();
 
     mCameraService->removeClient(mCameraClient);
@@ -476,8 +471,8 @@
 
 // ----------------------------------------------------------------------------
 
-// set the ISurface that the preview will use
-status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) {
+// set the Surface that the preview will use
+status_t CameraService::Client::setPreviewDisplay(const sp<Surface>& surface) {
     LOG1("setPreviewDisplay(%p) (pid %d)", surface.get(), getCallingPid());
     Mutex::Autolock lock(mLock);
     status_t result = checkPidAndHardware();
@@ -487,7 +482,7 @@
 
     // return if no change in surface.
     // asBinder() is safe on NULL (returns NULL)
-    if (surface->asBinder() == mSurface->asBinder()) {
+    if (getISurface(surface)->asBinder() == mSurface->asBinder()) {
         return result;
     }
 
@@ -498,44 +493,28 @@
             sp<Overlay> dummy;
             mHardware->setOverlay(dummy);
             mOverlayRef = 0;
-        } else {
-            mSurface->unregisterBuffers();
         }
     }
-    mSurface = surface;
+    if (surface != 0) {
+        mSurface = getISurface(surface);
+    } else {
+        mSurface = 0;
+    }
+    mPreviewWindow = surface;
     mOverlayRef = 0;
     // If preview has been already started, set overlay or register preview
     // buffers now.
     if (mHardware->previewEnabled()) {
         if (mUseOverlay) {
             result = setOverlay();
-        } else if (mSurface != 0) {
-            result = registerPreviewBuffers();
+        } else if (mPreviewWindow != 0) {
+            result = mHardware->setPreviewWindow(mPreviewWindow);
         }
     }
 
     return result;
 }
 
-status_t CameraService::Client::registerPreviewBuffers() {
-    int w, h;
-    CameraParameters params(mHardware->getParameters());
-    params.getPreviewSize(&w, &h);
-
-    // FIXME: don't use a hardcoded format here.
-    ISurface::BufferHeap buffers(w, h, w, h,
-                                 HAL_PIXEL_FORMAT_YCrCb_420_SP,
-                                 mOrientation,
-                                 0,
-                                 mHardware->getPreviewHeap());
-
-    status_t result = mSurface->registerBuffers(buffers);
-    if (result != NO_ERROR) {
-        LOGE("registerBuffers failed with status %d", result);
-    }
-    return result;
-}
-
 status_t CameraService::Client::setOverlay() {
     int w, h;
     CameraParameters params(mHardware->getParameters());
@@ -593,16 +572,10 @@
     if (checkPidAndHardware() != NO_ERROR) return;
 
     mPreviewCallbackFlag = callback_flag;
-
-    // If we don't use overlay, we always need the preview frame for display.
-    // If we do use overlay, we only need the preview frame if the user
-    // wants the data.
-    if (mUseOverlay) {
-        if(mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) {
-            enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
-        } else {
-            disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
-        }
+    if (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) {
+        enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
+    } else {
+        disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
     }
 }
 
@@ -627,14 +600,14 @@
 
     switch(mode) {
         case CAMERA_PREVIEW_MODE:
-            if (mSurface == 0) {
+            if (mSurface == 0 && mPreviewWindow == 0) {
                 LOG1("mSurface is not set yet.");
                 // still able to start preview in this case.
             }
             return startPreviewMode();
         case CAMERA_RECORDING_MODE:
-            if (mSurface == 0) {
-                LOGE("mSurface must be set before startRecordingMode.");
+            if (mSurface == 0 && mPreviewWindow == 0) {
+                LOGE("mSurface or mPreviewWindow must be set before startRecordingMode.");
                 return INVALID_OPERATION;
             }
             return startRecordingMode();
@@ -660,16 +633,9 @@
         if (result != NO_ERROR) return result;
         result = mHardware->startPreview();
     } else {
-        enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
+        // XXX: Set the orientation of the ANativeWindow.
+        mHardware->setPreviewWindow(mPreviewWindow);
         result = mHardware->startPreview();
-        if (result != NO_ERROR) return result;
-        // If preview display has been set, register preview buffers now.
-        if (mSurface != 0) {
-           // Unregister here because the surface may be previously registered
-           // with the raw (snapshot) heap.
-           mSurface->unregisterBuffers();
-           result = registerPreviewBuffers();
-        }
     }
     return result;
 }
@@ -707,13 +673,10 @@
     Mutex::Autolock lock(mLock);
     if (checkPidAndHardware() != NO_ERROR) return;
 
+
     disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
     mHardware->stopPreview();
 
-    if (mSurface != 0 && !mUseOverlay) {
-        mSurface->unregisterBuffers();
-    }
-
     mPreviewBuffer.clear();
 }
 
@@ -811,6 +774,35 @@
     return params;
 }
 
+// enable shutter sound
+status_t CameraService::Client::enableShutterSound(bool enable) {
+    LOG1("enableShutterSound (pid %d)", getCallingPid());
+
+    status_t result = checkPidAndHardware();
+    if (result != NO_ERROR) return result;
+
+    if (enable) {
+        mPlayShutterSound = true;
+        return OK;
+    }
+
+    // Disabling shutter sound may not be allowed. In that case only
+    // allow the mediaserver process to disable the sound.
+    char value[PROPERTY_VALUE_MAX];
+    property_get("ro.camera.sound.forced", value, "0");
+    if (strcmp(value, "0") != 0) {
+        // Disabling shutter sound is not allowed. Deny if the current
+        // process is not mediaserver.
+        if (getCallingPid() != getpid()) {
+            LOGE("Failed to disable shutter sound. Permission denied (pid %d)", getCallingPid());
+            return PERMISSION_DENIED;
+        }
+    }
+
+    mPlayShutterSound = false;
+    return OK;
+}
+
 status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) {
     LOG1("sendCommand (pid %d)", getCallingPid());
     int orientation;
@@ -844,6 +836,20 @@
             if (mOverlayRef != 0) mOrientationChanged = true;
         }
         return OK;
+    } else if (cmd == CAMERA_CMD_ENABLE_SHUTTER_SOUND) {
+        switch (arg1) {
+            case 0:
+                enableShutterSound(false);
+                break;
+            case 1:
+                enableShutterSound(true);
+                break;
+            default:
+                return BAD_VALUE;
+        }
+        return OK;
+    } else if (cmd == CAMERA_CMD_PLAY_RECORDING_SOUND) {
+        mCameraService->playSound(SOUND_RECORDING);
     }
 
     return mHardware->sendCommand(cmd, arg1, arg2);
@@ -1004,11 +1010,8 @@
 // "size" is the width and height of yuv picture for registerBuffer.
 // If it is NULL, use the picture size from parameters.
 void CameraService::Client::handleShutter(image_rect_type *size) {
-    mCameraService->playSound(SOUND_SHUTTER);
-
-    // Screen goes black after the buffer is unregistered.
-    if (mSurface != 0 && !mUseOverlay) {
-        mSurface->unregisterBuffers();
+    if (mPlayShutterSound) {
+        mCameraService->playSound(SOUND_SHUTTER);
     }
 
     sp<ICameraClient> c = mCameraClient;
@@ -1038,7 +1041,6 @@
             HAL_PIXEL_FORMAT_YCrCb_420_SP, mOrientation, 0,
             mHardware->getRawHeap());
 
-        mSurface->registerBuffers(buffers);
         IPCThreadState::self()->flushCommands();
     }
 
@@ -1051,12 +1053,6 @@
     size_t size;
     sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
 
-    if (!mUseOverlay) {
-        if (mSurface != 0) {
-            mSurface->postBuffer(offset);
-        }
-    }
-
     // local copy of the callback flags
     int flags = mPreviewCallbackFlag;
 
@@ -1077,9 +1073,7 @@
         mPreviewCallbackFlag &= ~(FRAME_CALLBACK_FLAG_ONE_SHOT_MASK |
                                   FRAME_CALLBACK_FLAG_COPY_OUT_MASK |
                                   FRAME_CALLBACK_FLAG_ENABLE_MASK);
-        if (mUseOverlay) {
-            disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
-        }
+        disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
     }
 
     if (c != 0) {
@@ -1116,11 +1110,6 @@
     size_t size;
     sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
 
-    // Put the YUV version of the snapshot in the preview display.
-    if (mSurface != 0 && !mUseOverlay) {
-        mSurface->postBuffer(offset);
-    }
-
     sp<ICameraClient> c = mCameraClient;
     mLock.unlock();
     if (c != 0) {
@@ -1278,4 +1267,12 @@
     return NO_ERROR;
 }
 
+sp<ISurface> CameraService::getISurface(const sp<Surface>& surface) {
+    if (surface != 0) {
+        return surface->getISurface();
+    } else {
+        return sp<ISurface>(0);
+    }
+}
+
 }; // namespace android
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 8f0ed75..d57364a 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -79,6 +79,12 @@
     sp<MediaPlayer>     mSoundPlayer[NUM_SOUNDS];
     int                 mSoundRef;  // reference count (release all MediaPlayer when 0)
 
+    // Used by Client objects to extract the ISurface from a Surface object.
+    // This is used because making Client a friend class of Surface would
+    // require including this header in Surface.h since Client is a nested
+    // class.
+    static sp<ISurface> getISurface(const sp<Surface>& surface);
+
     class Client : public BnCamera
     {
     public:
@@ -87,7 +93,7 @@
         virtual status_t        connect(const sp<ICameraClient>& client);
         virtual status_t        lock();
         virtual status_t        unlock();
-        virtual status_t        setPreviewDisplay(const sp<ISurface>& surface);
+        virtual status_t        setPreviewDisplay(const sp<Surface>& surface);
         virtual void            setPreviewCallbackFlag(int flag);
         virtual status_t        startPreview();
         virtual void            stopPreview();
@@ -132,6 +138,9 @@
         status_t                startPreviewMode();
         status_t                startRecordingMode();
 
+        // internal function used by sendCommand to enable/disable shutter sound.
+        status_t                enableShutterSound(bool enable);
+
         // these are static callback functions
         static void             notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2, void* user);
         static void             dataCallback(int32_t msgType, const sp<IMemory>& dataPtr, void* user);
@@ -167,10 +176,12 @@
         int                             mOrientation;     // Current display orientation
         // True if display orientation has been changed. This is only used in overlay.
         int                             mOrientationChanged;
+        bool                            mPlayShutterSound;
 
         // Ensures atomicity among the public methods
         mutable Mutex                   mLock;
         sp<ISurface>                    mSurface;
+        sp<ANativeWindow>               mPreviewWindow;
 
         // If the user want us to return a copy of the preview frame (instead
         // of the original one), we allocate mPreviewBuffer and reuse it if possible.
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 731fb22..5ef3d35 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -426,6 +426,40 @@
         }
     }
 
+    public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
+        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]);
+                updateAppWidgetInstanceLocked(id, views, true);
+            }
+        }
+    }
+
+    public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, 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, viewId);
+            }
+        }
+    }
+
     public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) {
         synchronized (mAppWidgetIds) {
             Provider p = lookupProviderLocked(provider);
@@ -443,11 +477,17 @@
     }
 
     void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) {
+        updateAppWidgetInstanceLocked(id, views, false);
+    }
+
+    void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) {
         // 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;
+
+            // We do not want to save this RemoteViews
+            if (!isPartialUpdate) id.views = views;
 
             // is anyone listening?
             if (id.host.callbacks != null) {
@@ -463,6 +503,25 @@
         }
     }
 
+    void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, 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) {
+            // 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, 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);
@@ -742,6 +801,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..bdf313c 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -16,42 +16,240 @@
 
 package com.android.server;
 
-import android.text.IClipboard;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.IClipboard;
+import android.content.IOnPrimaryClipChangedListener;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.util.HashSet;
 
 /**
  * Implementation of the clipboard for copy and paste.
  */
 public class ClipboardService extends IClipboard.Stub {
-    private CharSequence mClipboard = "";
+    private final Context mContext;
+    private final IActivityManager mAm;
+    private final PackageManager mPm;
+    private final IBinder mPermissionOwner;
+
+    private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
+            = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
+
+    private ClipData mPrimaryClip;
+
+    private final HashSet<String> mActivePermissionOwners
+            = new HashSet<String>();
 
     /**
      * Instantiates the clipboard.
      */
-    public ClipboardService(Context context) { }
+    public ClipboardService(Context context) {
+        mContext = context;
+        mAm = ActivityManagerNative.getDefault();
+        mPm = context.getPackageManager();
+        IBinder permOwner = null;
+        try {
+            permOwner = mAm.newUriPermissionOwner("clipboard");
+        } catch (RemoteException e) {
+            Slog.w("clipboard", "AM dead", e);
+        }
+        mPermissionOwner = permOwner;
+    }
 
-    // javadoc from interface
-    public void setClipboardText(CharSequence text) {
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            Slog.w("clipboard", "Exception: ", e);
+            throw e;
+        }
+        
+    }
+
+    public void setPrimaryClip(ClipData clip) {
         synchronized (this) {
-            if (text == null) {
-                text = "";
+            if (clip != null && clip.getItemCount() <= 0) {
+                throw new IllegalArgumentException("No items");
             }
+            checkDataOwnerLocked(clip, Binder.getCallingUid());
+            clearActiveOwnersLocked();
+            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 ClipData getPrimaryClip(String pkg) {
         synchronized (this) {
-            return mClipboard;
+            addActiveOwnerLocked(Binder.getCallingUid(), pkg);
+            return mPrimaryClip;
         }
     }
 
-    // javadoc from interface
+    public ClipDescription getPrimaryClipDescription() {
+        synchronized (this) {
+            return new ClipDescription(mPrimaryClip);
+        }
+    }
+
+    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;
+        }
+    }
+
+    private final void checkUriOwnerLocked(Uri uri, int uid) {
+        if (!"content".equals(uri.getScheme())) {
+            return;
+        }
+        long ident = Binder.clearCallingIdentity();
+        boolean allowed = false;
+        try {
+            // This will throw SecurityException for us.
+            mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
+        if (item.getUri() != null) {
+            checkUriOwnerLocked(item.getUri(), uid);
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            checkUriOwnerLocked(intent.getData(), uid);
+        }
+    }
+
+    private final void checkDataOwnerLocked(ClipData data, int uid) {
+        final int N = data.getItemCount();
+        for (int i=0; i<N; i++) {
+            checkItemOwnerLocked(data.getItem(i), uid);
+        }
+    }
+
+    private final void grantUriLocked(Uri uri, String pkg) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void grantItemLocked(ClipData.Item item, String pkg) {
+        if (item.getUri() != null) {
+            grantUriLocked(item.getUri(), pkg);
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            grantUriLocked(intent.getData(), pkg);
+        }
+    }
+
+    private final void addActiveOwnerLocked(int uid, String pkg) {
+        PackageInfo pi;
+        try {
+            pi = mPm.getPackageInfo(pkg, 0);
+            if (pi.applicationInfo.uid != uid) {
+                throw new SecurityException("Calling uid " + uid
+                        + " does not own package " + pkg);
+            }
+        } catch (NameNotFoundException e) {
+            throw new IllegalArgumentException("Unknown package " + pkg, e);
+        }
+        if (!mActivePermissionOwners.contains(pkg)) {
+            final int N = mPrimaryClip.getItemCount();
+            for (int i=0; i<N; i++) {
+                grantItemLocked(mPrimaryClip.getItem(i), pkg);
+            }
+            mActivePermissionOwners.add(pkg);
+        }
+    }
+
+    private final void revokeUriLocked(Uri uri) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void revokeItemLocked(ClipData.Item item) {
+        if (item.getUri() != null) {
+            revokeUriLocked(item.getUri());
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            revokeUriLocked(intent.getData());
+        }
+    }
+
+    private final void clearActiveOwnersLocked() {
+        mActivePermissionOwners.clear();
+        if (mPrimaryClip == null) {
+            return;
+        }
+        final int N = mPrimaryClip.getItemCount();
+        for (int i=0; i<N; i++) {
+            revokeItemLocked(mPrimaryClip.getItem(i));
         }
     }
 }
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index fc8b4a9..6095117 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -26,14 +26,18 @@
 import android.net.IConnectivityManager;
 import android.net.MobileDataStateTracker;
 import android.net.NetworkInfo;
+import android.net.LinkProperties;
 import android.net.NetworkStateTracker;
 import android.net.NetworkUtils;
 import android.net.wifi.WifiStateTracker;
+import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerThread;
 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;
@@ -47,8 +51,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.GregorianCalendar;
 import java.util.List;
 import java.net.InetAddress;
@@ -59,7 +68,7 @@
  */
 public class ConnectivityService extends IConnectivityManager.Stub {
 
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String TAG = "ConnectivityService";
 
     // how long to wait before switching back to a radio's default network
@@ -68,7 +77,6 @@
     private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
             "android.telephony.apn-restore";
 
-
     private Tethering mTethering;
     private boolean mTetheringConfigValid = false;
 
@@ -85,6 +93,8 @@
      */
     private List mNetRequestersPids[];
 
+    private WifiWatchdogService mWifiWatchdogService;
+
     // priority order of the nettrackers
     // (excluding dynamically set mNetworkPreference)
     // TODO - move mNetworkTypePreference into this
@@ -162,6 +172,13 @@
     private static final int EVENT_SET_MOBILE_DATA =
             MAX_NETWORK_STATE_TRACKER_EVENT + 7;
 
+    /**
+     * used internally to clear a wakelock when transitioning
+     * from one net to another
+     */
+    private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK =
+            MAX_NETWORK_STATE_TRACKER_EVENT + 8;
+
     private Handler mHandler;
 
     // list of DeathRecipients used to make sure features are turned off when
@@ -171,6 +188,13 @@
     private boolean mSystemReady;
     private Intent mInitialBroadcast;
 
+    private PowerManager.WakeLock mNetTransitionWakeLock;
+    private String mNetTransitionWakeLockCausedBy = "";
+    private int mNetTransitionWakeLockSerialNumber;
+    private int mNetTransitionWakeLockTimeout;
+
+    private InetAddress mDefaultDns;
+
     // used in DBG mode to track inet condition reports
     private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
     private ArrayList mInetLog;
@@ -210,52 +234,20 @@
     }
     RadioAttributes[] mRadioAttributes;
 
-    private static class ConnectivityThread extends Thread {
-        private Context mContext;
-
-        private ConnectivityThread(Context context) {
-            super("ConnectivityThread");
-            mContext = context;
+    public static synchronized ConnectivityService getInstance(Context context) {
+        if (sServiceInstance == null) {
+            sServiceInstance = new ConnectivityService(context);
         }
-
-        @Override
-        public void run() {
-            Looper.prepare();
-            synchronized (this) {
-                sServiceInstance = new ConnectivityService(mContext);
-                notifyAll();
-            }
-            Looper.loop();
-        }
-
-        public static ConnectivityService getServiceInstance(Context context) {
-            ConnectivityThread thread = new ConnectivityThread(context);
-            thread.start();
-
-            synchronized (thread) {
-                while (sServiceInstance == null) {
-                    try {
-                        // Wait until sServiceInstance has been initialized.
-                        thread.wait();
-                    } catch (InterruptedException ignore) {
-                        Slog.e(TAG,
-                            "Unexpected InterruptedException while waiting"+
-                            " for ConnectivityService thread");
-                    }
-                }
-            }
-
-            return sServiceInstance;
-        }
-    }
-
-    public static ConnectivityService getInstance(Context context) {
-        return ConnectivityThread.getServiceInstance(context);
+        return sServiceInstance;
     }
 
     private ConnectivityService(Context context) {
         if (DBG) Slog.v(TAG, "ConnectivityService starting up");
 
+        HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
+        handlerThread.start();
+        mHandler = new MyHandler(handlerThread.getLooper());
+
         // setup our unique device name
         String id = Settings.Secure.getString(context.getContentResolver(),
                 Settings.Secure.ANDROID_ID);
@@ -264,10 +256,28 @@
             SystemProperties.set("net.hostname", name);
         }
 
+        // read our default dns server ip
+        String dns = Settings.Secure.getString(context.getContentResolver(),
+                Settings.Secure.DEFAULT_DNS_SERVER);
+        if (dns == null || dns.length() == 0) {
+            dns = context.getResources().getString(
+                    com.android.internal.R.string.config_default_dns_server);
+        }
+        try {
+            mDefaultDns = InetAddress.getByName(dns);
+        } catch (UnknownHostException e) {
+            Slog.e(TAG, "Error setting defaultDns using " + dns);
+        }
+
         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();
 
         mNetworkPreference = getPersistedNetworkPreference();
 
@@ -364,18 +374,21 @@
             switch (mNetAttributes[netType].mRadio) {
             case ConnectivityManager.TYPE_WIFI:
                 if (DBG) Slog.v(TAG, "Starting Wifi Service.");
-                WifiStateTracker wst = new WifiStateTracker(context, mHandler);
-                WifiService wifiService = new WifiService(context, wst);
+                WifiStateTracker wst = new WifiStateTracker();
+                WifiService wifiService = new WifiService(context);
                 ServiceManager.addService(Context.WIFI_SERVICE, wifiService);
-                wifiService.startWifi();
+                wifiService.checkAndStartWifi();
                 mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
-                wst.startMonitoring();
+                wst.startMonitoring(context, mHandler);
+
+                //TODO: as part of WWS refactor, create only when needed
+                mWifiWatchdogService = new WifiWatchdogService(context);
 
                 break;
             case ConnectivityManager.TYPE_MOBILE:
-                mNetTrackers[netType] = new MobileDataStateTracker(context, mHandler,
-                    netType, mNetAttributes[netType].mName);
-                mNetTrackers[netType].startMonitoring();
+                mNetTrackers[netType] = new MobileDataStateTracker(netType,
+                        mNetAttributes[netType].mName);
+                mNetTrackers[netType].startMonitoring(context, mHandler);
                 if (noMobileData) {
                     if (DBG) Slog.d(TAG, "tearing down Mobile networks due to setting");
                     mNetTrackers[netType].teardown();
@@ -392,7 +405,8 @@
         mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) ||
                                   !mTethering.isDunRequired()) &&
                                  (mTethering.getTetherableUsbRegexs().length != 0 ||
-                                  mTethering.getTetherableWifiRegexs().length != 0) &&
+                                  mTethering.getTetherableWifiRegexs().length != 0 ||
+                                  mTethering.getTetherableBluetoothRegexs().length != 0) &&
                                  mTethering.getUpstreamIfaceRegexs().length != 0);
 
         if (DBG) {
@@ -528,6 +542,38 @@
         return result;
     }
 
+    /**
+     * Return LinkProperties for the active (i.e., connected) default
+     * network interface.  It is assumed that at most one default network
+     * is active at a time. If more than one is active, it is indeterminate
+     * which will be returned.
+     * @return the ip properties for the active network, or {@code null} if
+     * none is active
+     */
+    public LinkProperties getActiveLinkProperties() {
+        enforceAccessPermission();
+        for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
+            if (mNetAttributes[type] == null || !mNetAttributes[type].isDefault()) {
+                continue;
+            }
+            NetworkStateTracker t = mNetTrackers[type];
+            NetworkInfo info = t.getNetworkInfo();
+            if (info.isConnected()) {
+                return t.getLinkProperties();
+            }
+        }
+        return null;
+    }
+
+    public LinkProperties getLinkProperties(int networkType) {
+        enforceAccessPermission();
+        if (ConnectivityManager.isNetworkTypeValid(networkType)) {
+            NetworkStateTracker t = mNetTrackers[networkType];
+            if (t != null) return t.getLinkProperties();
+        }
+        return null;
+    }
+
     public boolean setRadios(boolean turnOn) {
         boolean result = true;
         enforceChangePermission();
@@ -676,14 +722,7 @@
                 network.reconnect();
                 return Phone.APN_REQUEST_STARTED;
             } else {
-                synchronized(this) {
-                    mFeatureUsers.add(f);
-                }
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK,
-                        f), getRestoreDefaultNetworkDelay());
-
-                return network.startUsingNetworkFeature(feature,
-                        getCallingPid(), getCallingUid());
+                return -1;
             }
         }
         return Phone.APN_TYPE_NOT_AVAILABLE;
@@ -802,8 +841,7 @@
             tracker.teardown();
             return 1;
         } else {
-            // do it the old fashioned way
-            return tracker.stopUsingNetworkFeature(feature, pid, uid);
+            return -1;
         }
     }
 
@@ -852,11 +890,37 @@
             }
             return false;
         }
-
         try {
-            InetAddress inetAddress = InetAddress.getByAddress(hostAddress);
-            return tracker.requestRouteToHost(inetAddress);
-        } catch (UnknownHostException e) {
+            InetAddress addr = InetAddress.getByAddress(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;
+        }
+
+        LinkProperties p = nt.getLinkProperties();
+        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, null);
+        } else {
+            if (DBG) Slog.e(TAG, "addHostRoute failed due to null interface name");
             return false;
         }
     }
@@ -975,6 +1039,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
@@ -1269,9 +1339,17 @@
                         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(
+                            EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+                            mNetTransitionWakeLockSerialNumber, 0),
+                            1000);
                 }
             }
             mActiveDefaultNetwork = type;
@@ -1285,36 +1363,14 @@
             //reportNetworkCondition(mActiveDefaultNetwork, 100);
         }
         thisNet.setTeardownRequested(false);
-        thisNet.updateNetworkSettings();
+        updateNetworkSettings(thisNet);
         handleConnectivityChange(type);
         sendConnectedBroadcast(info);
     }
 
-    private void handleScanResultsAvailable(NetworkInfo info) {
-        int networkType = info.getType();
-        if (networkType != ConnectivityManager.TYPE_WIFI) {
-            if (DBG) Slog.v(TAG, "Got ScanResultsAvailable for " +
-                    info.getTypeName() + " network. Don't know how to handle.");
-        }
-
-        mNetTrackers[networkType].interpretScanResultsAvailable();
-    }
-
-    private void handleNotificationChange(boolean visible, int id,
-            Notification notification) {
-        NotificationManager notificationManager = (NotificationManager) mContext
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-
-        if (visible) {
-            notificationManager.notify(id, notification);
-        } else {
-            notificationManager.cancel(id);
-        }
-    }
-
     /**
-     * After a change in the connectivity state of any network, We're mainly
-     * concerned with making sure that the list of DNS servers is setupup
+     * 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.
      */
@@ -1327,19 +1383,158 @@
 
         if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
             if (mNetAttributes[netType].isDefault()) {
-                mNetTrackers[netType].addDefaultRoute();
+                addDefaultRoute(mNetTrackers[netType]);
             } else {
-                mNetTrackers[netType].addPrivateDnsRoutes();
+                addPrivateDnsRoutes(mNetTrackers[netType]);
             }
         } else {
             if (mNetAttributes[netType].isDefault()) {
-                mNetTrackers[netType].removeDefaultRoute();
+                removeDefaultRoute(mNetTrackers[netType]);
             } else {
-                mNetTrackers[netType].removePrivateDnsRoutes();
+                removePrivateDnsRoutes(mNetTrackers[netType]);
             }
         }
     }
 
+    private void addPrivateDnsRoutes(NetworkStateTracker nt) {
+        boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet();
+        LinkProperties p = nt.getLinkProperties();
+        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, null);
+            }
+            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
+        LinkProperties p = nt.getLinkProperties();
+        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) {
+        LinkProperties p = nt.getLinkProperties();
+        if (p == null) return;
+        String interfaceName = p.getInterfaceName();
+        InetAddress defaultGatewayAddr = p.getGateway();
+
+        if ((interfaceName != null) && (defaultGatewayAddr != null )) {
+            if (!NetworkUtils.addDefaultRoute(interfaceName, defaultGatewayAddr) && DBG) {
+                NetworkInfo networkInfo = nt.getNetworkInfo();
+                Slog.d(TAG, "addDefaultRoute for " + networkInfo.getTypeName() +
+                        " (" + interfaceName + "), GatewayAddr=" + defaultGatewayAddr);
+            }
+        }
+    }
+
+
+    public void removeDefaultRoute(NetworkStateTracker nt) {
+        LinkProperties p = nt.getLinkProperties();
+        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.
@@ -1355,12 +1550,14 @@
             NetworkStateTracker nt = mNetTrackers[i];
             if (nt.getNetworkInfo().isConnected() &&
                     !nt.isTeardownRequested()) {
+                LinkProperties p = nt.getLinkProperties();
+                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();
                         }
@@ -1382,12 +1579,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());
         }
     }
 
@@ -1410,16 +1605,24 @@
         // add default net's dns entries
         NetworkStateTracker nt = mNetTrackers[netType];
         if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
-            String[] dnsList = nt.getNameServers();
+            LinkProperties p = nt.getLinkProperties();
+            if (p == null) return;
+            Collection<InetAddress> dnses = p.getDnses();
             if (mNetAttributes[netType].isDefault()) {
                 int j = 1;
-                for (String dns : dnsList) {
-                    if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) {
+                if (dnses.size() == 0 && mDefaultDns != null) {
+                    if (DBG) {
+                        Slog.d(TAG, "no dns provided - using " + mDefaultDns.getHostAddress());
+                    }
+                    SystemProperties.set("net.dns1", mDefaultDns.getHostAddress());
+                    j++;
+                } else {
+                    for (InetAddress dns : dnses) {
                         if (DBG) {
                             Slog.d(TAG, "adding dns " + dns + " for " +
                                     nt.getNetworkInfo().getTypeName());
                         }
-                        SystemProperties.set("net.dns" + j++, dns);
+                        SystemProperties.set("net.dns" + j++, dns.getHostAddress());
                     }
                 }
                 for (int k=j ; k<mNumDnsEntries; k++) {
@@ -1432,11 +1635,11 @@
                 List pids = mNetRequestersPids[netType];
                 for (int y=0; y< pids.size(); y++) {
                     Integer pid = (Integer)pids.get(y);
-                    writePidDns(dnsList, pid.intValue());
+                    writePidDns(dnses, pid.intValue());
                 }
             }
+            bumpDns();
         }
-        bumpDns();
     }
 
     private int getRestoreDefaultNetworkDelay() {
@@ -1491,6 +1694,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);
 
         if (mInetLog != null) {
@@ -1504,6 +1714,10 @@
 
     // must be stateless - things change under us.
     private class MyHandler extends Handler {
+        public MyHandler(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             NetworkInfo info;
@@ -1565,29 +1779,25 @@
                         handleConnect(info);
                     }
                     break;
-
-                case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE:
-                    info = (NetworkInfo) msg.obj;
-                    handleScanResultsAvailable(info);
-                    break;
-
-                case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED:
-                    handleNotificationChange(msg.arg1 == 1, msg.arg2,
-                            (Notification) msg.obj);
-                    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;
-
-                case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED:
-                    // fill me in
+                case 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;
                 case EVENT_RESTORE_DEFAULT_NETWORK:
                     FeatureUser u = (FeatureUser)msg.obj;
@@ -1681,6 +1891,15 @@
         }
     }
 
+    public String[] getTetherableBluetoothRegexs() {
+        enforceTetherAccessPermission();
+        if (isTetheringSupported()) {
+            return mTethering.getTetherableBluetoothRegexs();
+        } else {
+            return new String[0];
+        }
+    }
+
     // TODO - move iface listing, queries, etc to new module
     // javadoc from interface
     public String[] getTetherableIfaces() {
@@ -1709,6 +1928,25 @@
         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(
+                EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+                mNetTransitionWakeLockSerialNumber, 0),
+                mNetTransitionWakeLockTimeout);
+        return;
+    }
+
     // 100 percent is full good, 0 is full bad.
     public void reportInetCondition(int networkType, int percentage) {
         if (DBG) Slog.d(TAG, "reportNetworkCondition(" + networkType + ", " + percentage + ")");
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 21273cc..28126b9 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;
@@ -59,46 +62,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");
@@ -111,7 +134,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) {
@@ -124,8 +182,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();
@@ -144,19 +217,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:");
@@ -167,23 +270,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: "
@@ -208,7 +335,7 @@
             }
         }
     }
-    
+
     /**
      * Instantiates the service.
      */
@@ -225,7 +352,7 @@
         }
         return mIPowerManager;
     }
-    
+
     ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who) {
         ActiveAdmin admin = mAdminMap.get(who);
         if (admin != null
@@ -235,7 +362,7 @@
         }
         return null;
     }
-    
+
     ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
             throws SecurityException {
         final int callingUid = Binder.getCallingUid();
@@ -266,13 +393,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) {
@@ -284,19 +411,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);
@@ -305,7 +437,7 @@
         if (infos == null || infos.size() <= 0) {
             throw new IllegalArgumentException("Unknown admin: " + adminName);
         }
-        
+
         try {
             return new DeviceAdminInfo(mContext, infos.get(0));
         } catch (XmlPullParserException e) {
@@ -316,7 +448,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"));
@@ -332,7 +464,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);
@@ -343,26 +475,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();
@@ -440,6 +582,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);
@@ -479,10 +633,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;
@@ -501,12 +661,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;
@@ -523,17 +684,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);
@@ -555,13 +716,13 @@
             }
         }
     }
-    
+
     public boolean isAdminActive(ComponentName adminReceiver) {
         synchronized (this) {
             return getActiveAdminUncheckedLocked(adminReceiver) != null;
         }
     }
-    
+
     public List<ComponentName> getActiveAdmins() {
         synchronized (this) {
             final int N = mAdminList.size();
@@ -575,7 +736,7 @@
             return res;
         }
     }
-    
+
     public boolean packageHasActiveAdmins(String packageName) {
         synchronized (this) {
             final int N = mAdminList.size();
@@ -587,7 +748,7 @@
             return false;
         }
     }
-    
+
     public void removeActiveAdmin(ComponentName adminReceiver) {
         synchronized (this) {
             ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver);
@@ -606,10 +767,10 @@
             }
         }
     }
-    
+
     public void setPasswordQuality(ComponentName who, int quality) {
         validateQualityConstant(quality);
-        
+
         synchronized (this) {
             if (who == null) {
                 throw new NullPointerException("ComponentName is null");
@@ -622,16 +783,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);
@@ -642,7 +803,7 @@
             return mode;
         }
     }
-    
+
     public void setPasswordMinimumLength(ComponentName who, int length) {
         synchronized (this) {
             if (who == null) {
@@ -656,16 +817,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);
@@ -676,18 +837,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,
@@ -697,7 +1107,7 @@
             return mFailedPasswordAttempts;
         }
     }
-    
+
     public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) {
         synchronized (this) {
             // This API can only be called by an active device admin,
@@ -712,16 +1122,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);
@@ -735,7 +1145,7 @@
             return count;
         }
     }
-    
+
     public boolean resetPassword(String password, int flags) {
         int quality;
         synchronized (this) {
@@ -746,14 +1156,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) {
@@ -761,14 +1172,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();
@@ -786,10 +1269,10 @@
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
-        
+
         return true;
     }
-    
+
     public void setMaximumTimeToLock(ComponentName who, long timeMs) {
         synchronized (this) {
             if (who == null) {
@@ -799,16 +1282,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) {
@@ -820,16 +1303,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);
@@ -843,7 +1326,7 @@
             return time;
         }
     }
-    
+
     public void lockNow() {
         synchronized (this) {
             // This API can only be called by an active device admin,
@@ -860,7 +1343,7 @@
             }
         }
     }
-    
+
     void wipeDataLocked(int flags) {
         try {
             RecoverySystem.rebootWipeUserData(mContext);
@@ -868,7 +1351,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,
@@ -883,11 +1366,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) {
@@ -910,20 +1393,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,
@@ -934,11 +1427,11 @@
             }
         }
     }
-    
+
     public void reportFailedPasswordAttempt() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        
+
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -955,11 +1448,11 @@
             }
         }
     }
-    
+
     public void reportSuccessfulPasswordAttempt() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        
+
         synchronized (this) {
             if (mFailedPasswordAttempts != 0 || mPasswordOwner >= 0) {
                 long ident = Binder.clearCallingIdentity();
@@ -975,7 +1468,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)
@@ -986,12 +1567,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++) {
@@ -1002,11 +1583,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/InputManager.java b/services/java/com/android/server/InputManager.java
index 29ca9a4..fe306b3 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -77,6 +77,8 @@
     private static native InputDevice nativeGetInputDevice(int deviceId);
     private static native void nativeGetInputConfiguration(Configuration configuration);
     private static native int[] nativeGetInputDeviceIds();
+    private static native boolean nativeTransferTouchFocus(InputChannel fromChannel,
+            InputChannel toChannel);
     private static native String nativeDump();
     
     // Input event injection constants defined in InputDispatcher.h.
@@ -320,6 +322,29 @@
         nativeSetInputDispatchMode(enabled, frozen);
     }
     
+    /**
+     * Atomically transfers touch focus from one window to another as identified by
+     * their input channels.  It is possible for multiple windows to have
+     * touch focus if they support split touch dispatch
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
+     * method only transfers touch focus of the specified window without affecting
+     * other windows that may also have touch focus at the same time.
+     * @param fromChannel The channel of a window that currently has touch focus.
+     * @param toChannel The channel of the window that should receive touch focus in
+     * place of the first.
+     * @return True if the transfer was successful.  False if the window with the
+     * specified channel did not actually have touch focus at the time of the request.
+     */
+    public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
+        if (fromChannel == null) {
+            throw new IllegalArgumentException("fromChannel must not be null.");
+        }
+        if (toChannel == null) {
+            throw new IllegalArgumentException("toChannel must not be null.");
+        }
+        return nativeTransferTouchFocus(fromChannel, toChannel);
+    }
+    
     public void dump(PrintWriter pw) {
         String dumpStr = nativeDump();
         if (dumpStr != null) {
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index ecad3cc..9a5423c 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -61,18 +61,21 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
+import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
 import android.util.EventLog;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
 import android.view.IWindowManager;
 import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -93,6 +96,7 @@
     static final String TAG = "InputManagerService";
 
     static final int MSG_SHOW_IM_PICKER = 1;
+    static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
 
     static final int MSG_UNBIND_INPUT = 1000;
     static final int MSG_BIND_INPUT = 1010;
@@ -109,8 +113,11 @@
 
     static final long TIME_TO_RECONNECT = 10*1000;
 
+    private static final int NOT_A_SUBTYPE_ID = -1;
+
     final Context mContext;
     final Handler mHandler;
+    final InputMethodSettings mSettings;
     final SettingsObserver mSettingsObserver;
     final StatusBarManagerService mStatusBar;
     final IWindowManager mIWindowManager;
@@ -120,13 +127,8 @@
 
     // All known input methods.  mMethodMap also serves as the global
     // lock for this class.
-    final ArrayList<InputMethodInfo> mMethodList
-            = new ArrayList<InputMethodInfo>();
-    final HashMap<String, InputMethodInfo> mMethodMap
-            = new HashMap<String, InputMethodInfo>();
-
-    final TextUtils.SimpleStringSplitter mStringColonSplitter
-            = new TextUtils.SimpleStringSplitter(':');
+    final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
+    final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
 
     class SessionState {
         final ClientState client;
@@ -224,6 +226,12 @@
     String mCurId;
 
     /**
+     * The current subtype of the current input method.
+     */
+    private InputMethodSubtype mCurrentSubtype;
+
+
+    /**
      * Set to true if our ServiceConnection is currently actively bound to
      * a service (whether or not we have gotten its IBinder back yet).
      */
@@ -292,6 +300,7 @@
     AlertDialog mSwitchingDialog;
     InputMethodInfo[] mIms;
     CharSequence[] mItems;
+    int[] mSubtypeIds;
 
     class SettingsObserver extends ContentObserver {
         SettingsObserver(Handler handler) {
@@ -299,6 +308,8 @@
             ContentResolver resolver = mContext.getContentResolver();
             resolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
         }
 
         @Override public void onChange(boolean selfChange) {
@@ -351,9 +362,9 @@
                                     if (!doit) {
                                         return true;
                                     }
-                                    
                                     Settings.Secure.putString(mContext.getContentResolver(),
                                             Settings.Secure.DEFAULT_INPUT_METHOD, "");
+                                    resetSelectedInputMethodSubtype();
                                     chooseNewDefaultIMELocked();
                                     return true;
                                 }
@@ -409,16 +420,15 @@
                             if (!chooseNewDefaultIMELocked()) {
                                 changed = true;
                                 curIm = null;
-                                curInputMethodId = "";
                                 Slog.i(TAG, "Unsetting current input method");
                                 Settings.Secure.putString(mContext.getContentResolver(),
-                                        Settings.Secure.DEFAULT_INPUT_METHOD,
-                                        curInputMethodId);
+                                        Settings.Secure.DEFAULT_INPUT_METHOD, "");
+                                resetSelectedInputMethodSubtype();
                             }
                         }
                     }
                 }
-                
+
                 if (curIm == null) {
                     // We currently don't have a default input method... is
                     // one now available?
@@ -469,27 +479,18 @@
         mStatusBar = statusBar;
         statusBar.setIconVisibility("ime", false);
 
+        // mSettings should be created before buildInputMethodListLocked
+        mSettings = new InputMethodSettings(context.getContentResolver(), mMethodMap, mMethodList);
         buildInputMethodListLocked(mMethodList, mMethodMap);
+        mSettings.enableAllIMEsIfThereIsNoEnabledIME();
 
-        final String enabledStr = Settings.Secure.getString(
-                mContext.getContentResolver(),
-                Settings.Secure.ENABLED_INPUT_METHODS);
-        Slog.i(TAG, "Enabled input methods: " + enabledStr);
-        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");
+        if (TextUtils.isEmpty(Settings.Secure.getString(
+                mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
             InputMethodInfo defIm = null;
-            StringBuilder sb = new StringBuilder(256);
-            final int N = mMethodList.size();
-            for (int i=0; i<N; i++) {
-                InputMethodInfo imi = mMethodList.get(i);
-                Slog.i(TAG, "Adding: " + imi.getId());
-                if (i > 0) sb.append(':');
-                sb.append(imi.getId());
+            for (InputMethodInfo imi: mMethodList) {
                 if (defIm == null && imi.getIsDefaultResourceId() != 0) {
                     try {
-                        Resources res = mContext.createPackageContext(
+                        Resources res = context.createPackageContext(
                                 imi.getPackageName(), 0).getResources();
                         if (res.getBoolean(imi.getIsDefaultResourceId())) {
                             defIm = imi;
@@ -500,15 +501,14 @@
                     }
                 }
             }
-            if (defIm == null && N > 0) {
+            if (defIm == null && mMethodList.size() > 0) {
                 defIm = mMethodList.get(0);
                 Slog.i(TAG, "No default found, using " + defIm.getId());
             }
-            Settings.Secure.putString(mContext.getContentResolver(),
-                    Settings.Secure.ENABLED_INPUT_METHODS, sb.toString());
             if (defIm != null) {
                 Settings.Secure.putString(mContext.getContentResolver(),
                         Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId());
+                putSelectedInputMethodSubtype(defIm, NOT_A_SUBTYPE_ID);
             }
         }
 
@@ -552,31 +552,10 @@
 
     public List<InputMethodInfo> getEnabledInputMethodList() {
         synchronized (mMethodMap) {
-            return getEnabledInputMethodListLocked();
+            return mSettings.getEnabledInputMethodListLocked();
         }
     }
 
-    List<InputMethodInfo> getEnabledInputMethodListLocked() {
-        final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
-
-        final String enabledStr = Settings.Secure.getString(
-                mContext.getContentResolver(),
-                Settings.Secure.ENABLED_INPUT_METHODS);
-        if (enabledStr != null) {
-            final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
-            splitter.setString(enabledStr);
-
-            while (splitter.hasNext()) {
-                InputMethodInfo info = mMethodMap.get(splitter.next());
-                if (info != null) {
-                    res.add(info);
-                }
-            }
-        }
-
-        return res;
-    }
-
     public void addClient(IInputMethodClient client,
             IInputContext inputContext, int uid, int pid) {
         synchronized (mMethodMap) {
@@ -965,10 +944,10 @@
         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
         // enabled.
         String id = Settings.Secure.getString(mContext.getContentResolver(),
-            Settings.Secure.DEFAULT_INPUT_METHOD);
+                Settings.Secure.DEFAULT_INPUT_METHOD);
         if (id != null && id.length() > 0) {
             try {
-                setInputMethodLocked(id);
+                setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
                 mCurMethodId = null;
@@ -981,21 +960,44 @@
         }
     }
 
-    void setInputMethodLocked(String id) {
+    /* package */ void setInputMethodLocked(String id, int subtypeId) {
         InputMethodInfo info = mMethodMap.get(id);
         if (info == null) {
             throw new IllegalArgumentException("Unknown id: " + id);
         }
 
         if (id.equals(mCurMethodId)) {
+            if (subtypeId != NOT_A_SUBTYPE_ID) {
+                InputMethodSubtype subtype = info.getSubtypes().get(subtypeId);
+                if (subtype != mCurrentSubtype) {
+                    synchronized (mMethodMap) {
+                        if (mCurMethod != null) {
+                            try {
+                                putSelectedInputMethodSubtype(info, subtypeId);
+                                mCurMethod.changeInputMethodSubtype(subtype);
+                            } catch (RemoteException e) {
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
             return;
         }
 
         final long ident = Binder.clearCallingIdentity();
         try {
             mCurMethodId = id;
+            // Set a subtype to this input method.
+            // subtypeId the name of a subtype which will be set.
+            if (putSelectedInputMethodSubtype(info, subtypeId)) {
+                mCurrentSubtype = info.getSubtypes().get(subtypeId);
+            } else {
+                mCurrentSubtype = null;
+            }
+
             Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.DEFAULT_INPUT_METHOD, id);
+                    Settings.Secure.DEFAULT_INPUT_METHOD, id);
 
             if (ActivityManagerNative.isSystemReady()) {
                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -1231,7 +1233,22 @@
         }
     }
 
+    public void showInputMethodSubtypePickerFromClient(IInputMethodClient client) {
+        synchronized (mMethodMap) {
+            if (mCurClient == null || client == null
+                    || mCurClient.client.asBinder() != client.asBinder()) {
+                Slog.w(TAG, "Ignoring showInputSubtypeMethodDialogFromClient of: " + client);
+            }
+
+            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
+        }
+    }
+
     public void setInputMethod(IBinder token, String id) {
+        setInputMethodWithSubtype(token, id, NOT_A_SUBTYPE_ID);
+    }
+
+    private void setInputMethodWithSubtype(IBinder token, String id, int subtypeId) {
         synchronized (mMethodMap) {
             if (token == null) {
                 if (mContext.checkCallingOrSelfPermission(
@@ -1249,7 +1266,7 @@
 
             long ident = Binder.clearCallingIdentity();
             try {
-                setInputMethodLocked(id);
+                setInputMethodLocked(id, subtypeId);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1315,6 +1332,10 @@
                 showInputMethodMenu();
                 return true;
 
+            case MSG_SHOW_IM_SUBTYPE_PICKER:
+                showInputMethodSubtypeMenu();
+                return true;
+
             // ---------------------------------------------------------
 
             case MSG_UNBIND_INPUT:
@@ -1414,7 +1435,7 @@
     }
 
     private boolean chooseNewDefaultIMELocked() {
-        List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
+        List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
         if (enabled != null && enabled.size() > 0) {
             // We'd prefer to fall back on a system IME, since that is safer.
             int i=enabled.size();
@@ -1425,9 +1446,10 @@
                     break;
                 }
             }
+            InputMethodInfo imi = enabled.get(i);
             Settings.Secure.putString(mContext.getContentResolver(),
-                    Settings.Secure.DEFAULT_INPUT_METHOD,
-                    enabled.get(i).getId());
+                    Settings.Secure.DEFAULT_INPUT_METHOD, imi.getId());
+            putSelectedInputMethodSubtype(imi, NOT_A_SUBTYPE_ID);
             return true;
         }
 
@@ -1498,7 +1520,15 @@
 
     // ----------------------------------------------------------------------
 
-    void showInputMethodMenu() {
+    private void showInputMethodMenu() {
+        showInputMethodMenuInternal(false);
+    }
+
+    private void showInputMethodSubtypeMenu() {
+        showInputMethodMenuInternal(true);
+    }
+
+    private void showInputMethodMenuInternal(boolean showSubtypes) {
         if (DEBUG) Slog.v(TAG, "Show switching menu");
 
         final Context context = mContext;
@@ -1507,48 +1537,84 @@
 
         String lastInputMethodId = Settings.Secure.getString(context
                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
         if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
 
         final List<InputMethodInfo> immis = getEnabledInputMethodList();
+        ArrayList<Integer> subtypeIds = new ArrayList<Integer>();
 
         if (immis == null) {
             return;
         }
-        
+
         synchronized (mMethodMap) {
             hideInputMethodMenuLocked();
 
             int N = immis.size();
 
-            final Map<CharSequence, InputMethodInfo> imMap =
-                new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance());
+            final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap =
+                new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance());
 
             for (int i = 0; i < N; ++i) {
                 InputMethodInfo property = immis.get(i);
                 if (property == null) {
                     continue;
                 }
-                imMap.put(property.loadLabel(pm), property);
+                // TODO: Show only enabled subtypes
+                ArrayList<InputMethodSubtype> subtypes = property.getSubtypes();
+                CharSequence label = property.loadLabel(pm);
+                if (showSubtypes && subtypes.size() > 0) {
+                    for (int j = 0; j < subtypes.size(); ++j) {
+                        InputMethodSubtype subtype = subtypes.get(j);
+                        CharSequence title;
+                        int nameResId = subtype.getNameResId();
+                        int modeResId = subtype.getModeResId();
+                        if (nameResId != 0) {
+                            title = pm.getText(property.getPackageName(), nameResId,
+                                    property.getServiceInfo().applicationInfo);
+                        } else {
+                            CharSequence language = subtype.getLocale();
+                            CharSequence mode = modeResId == 0 ? null
+                                    : pm.getText(property.getPackageName(), modeResId,
+                                            property.getServiceInfo().applicationInfo);
+                            // TODO: Use more friendly Title and UI
+                            title = label + "," + (mode == null ? "" : mode) + ","
+                                    + (language == null ? "" : language);
+                        }
+                        imMap.put(title, new Pair<InputMethodInfo, Integer>(property, j));
+                    }
+                } else {
+                    imMap.put(label,
+                            new Pair<InputMethodInfo, Integer>(property, NOT_A_SUBTYPE_ID));
+                    subtypeIds.add(0);
+                }
             }
 
             N = imMap.size();
             mItems = imMap.keySet().toArray(new CharSequence[N]);
-            mIms = imMap.values().toArray(new InputMethodInfo[N]);
-
+            mIms = new InputMethodInfo[N];
+            mSubtypeIds = new int[N];
             int checkedItem = 0;
             for (int i = 0; i < N; ++i) {
+                Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]);
+                mIms[i] = value.first;
+                mSubtypeIds[i] = value.second;
                 if (mIms[i].getId().equals(lastInputMethodId)) {
-                    checkedItem = i;
-                    break;
+                    int subtypeId = mSubtypeIds[i];
+                    if ((subtypeId == NOT_A_SUBTYPE_ID)
+                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
+                            || (subtypeId == lastInputMethodSubtypeId)) {
+                        checkedItem = i;
+                    }
                 }
             }
-    
+
             AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
                 public void onClick(DialogInterface dialog, int which) {
                     hideInputMethodMenu();
                 }
             };
-    
+
             TypedArray a = context.obtainStyledAttributes(null,
                     com.android.internal.R.styleable.DialogPreference,
                     com.android.internal.R.attr.alertDialogStyle, 0);
@@ -1562,18 +1628,24 @@
                     .setIcon(a.getDrawable(
                             com.android.internal.R.styleable.DialogPreference_dialogTitle));
             a.recycle();
-    
+
             mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
                     new AlertDialog.OnClickListener() {
                         public void onClick(DialogInterface dialog, int which) {
                             synchronized (mMethodMap) {
-                                if (mIms == null || mIms.length <= which) {
+                                if (mIms == null || mIms.length <= which
+                                        || mSubtypeIds == null || mSubtypeIds.length <= which) {
                                     return;
                                 }
                                 InputMethodInfo im = mIms[which];
+                                int subtypeId = mSubtypeIds[which];
                                 hideInputMethodMenu();
                                 if (im != null) {
-                                    setInputMethodLocked(im.getId());
+                                    if ((subtypeId < 0)
+                                            || (subtypeId >= im.getSubtypes().size())) {
+                                        subtypeId = NOT_A_SUBTYPE_ID;
+                                    }
+                                    setInputMethodLocked(im.getId(), subtypeId);
                                 }
                             }
                         }
@@ -1630,80 +1702,247 @@
         // Make sure this is a valid input method.
         InputMethodInfo imm = mMethodMap.get(id);
         if (imm == null) {
-            if (imm == null) {
-                throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
-            }
+            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
         }
 
-        StringBuilder builder = new StringBuilder(256);
+        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+                .getEnabledInputMethodsAndSubtypeListLocked();
 
-        boolean removed = false;
-        String firstId = null;
+        if (enabled) {
+            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
+                if (pair.first.equals(id)) {
+                    // We are enabling this input method, but it is already enabled.
+                    // Nothing to do. The previous state was enabled.
+                    return true;
+                }
+            }
+            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
+            // Previous state was disabled.
+            return false;
+        } else {
+            StringBuilder builder = new StringBuilder();
+            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+                    builder, enabledInputMethodsList, id)) {
+                // Disabled input method is currently selected, switch to another one.
+                String selId = Settings.Secure.getString(mContext.getContentResolver(),
+                        Settings.Secure.DEFAULT_INPUT_METHOD);
+                if (id.equals(selId)) {
+                    Settings.Secure.putString(
+                            mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD,
+                            enabledInputMethodsList.size() > 0
+                                    ? enabledInputMethodsList.get(0).first : "");
+                    resetSelectedInputMethodSubtype();
+                }
+                // Previous state was enabled.
+                return true;
+            } else {
+                // We are disabling the input method but it is already disabled.
+                // Nothing to do.  The previous state was disabled.
+                return false;
+            }
+        }
+    }
 
-        // Look through the currently enabled input methods.
-        String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
-                Settings.Secure.ENABLED_INPUT_METHODS);
-        if (enabledStr != null) {
-            final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
-            splitter.setString(enabledStr);
-            while (splitter.hasNext()) {
-                String curId = splitter.next();
-                if (curId.equals(id)) {
-                    if (enabled) {
-                        // We are enabling this input method, but it is
-                        // already enabled.  Nothing to do.  The previous
-                        // state was enabled.
-                        return true;
-                    }
-                    // We are disabling this input method, and it is
-                    // currently enabled.  Skip it to remove from the
-                    // new list.
-                    removed = true;
-                } else if (!enabled) {
-                    // We are building a new list of input methods that
-                    // doesn't contain the given one.
-                    if (firstId == null) firstId = curId;
-                    if (builder.length() > 0) builder.append(':');
-                    builder.append(curId);
+    private void resetSelectedInputMethodSubtype() {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
+    }
+
+    private boolean putSelectedInputMethodSubtype(InputMethodInfo imi, int subtypeId) {
+        ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
+        if (subtypeId >= 0 && subtypeId < subtypes.size()) {
+            Settings.Secure.putInt(mContext.getContentResolver(),
+                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+                    subtypes.get(subtypeId).hashCode());
+            return true;
+        } else {
+            resetSelectedInputMethodSubtype();
+            return false;
+        }
+    }
+
+    private int getSelectedInputMethodSubtypeId(String id) {
+        InputMethodInfo imi = mMethodMap.get(id);
+        if (imi == null) {
+            return NOT_A_SUBTYPE_ID;
+        }
+        ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
+        int subtypeId;
+        try {
+            subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
+        } catch (SettingNotFoundException e) {
+            return NOT_A_SUBTYPE_ID;
+        }
+        for (int i = 0; i < subtypes.size(); ++i) {
+            InputMethodSubtype ims = subtypes.get(i);
+            if (subtypeId == ims.hashCode()) {
+                return i;
+            }
+        }
+        return NOT_A_SUBTYPE_ID;
+    }
+
+    /**
+     * @return Return the current subtype of this input method.
+     */
+    public InputMethodSubtype getCurrentInputMethodSubtype() {
+        return mCurrentSubtype;
+    }
+
+    /**
+     * Utility class for putting and getting settings for InputMethod
+     * TODO: Move all putters and getters of settings to this class.
+     */
+    private static class InputMethodSettings {
+        // The string for enabled input method is saved as follows:
+        // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
+        private static final char INPUT_METHOD_SEPARATER = ':';
+        private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
+        private final TextUtils.SimpleStringSplitter mStringColonSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
+
+        private final TextUtils.SimpleStringSplitter mStringSemiColonSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
+
+        private final ContentResolver mResolver;
+        private final HashMap<String, InputMethodInfo> mMethodMap;
+        private final ArrayList<InputMethodInfo> mMethodList;
+
+        private String mEnabledInputMethodsStrCache;
+
+        private static void buildEnabledInputMethodsSettingString(
+                StringBuilder builder, Pair<String, ArrayList<String>> pair) {
+            String id = pair.first;
+            ArrayList<String> subtypes = pair.second;
+            builder.append(id);
+            if (subtypes.size() > 0) {
+                builder.append(subtypes.get(0));
+                for (int i = 1; i < subtypes.size(); ++i) {
+                    builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypes.get(i));
                 }
             }
         }
 
-        if (!enabled) {
-            if (!removed) {
-                // We are disabling the input method but it is already
-                // disabled.  Nothing to do.  The previous state was
-                // disabled.
-                return false;
-            }
-            // Update the setting with the new list of input methods.
-            Settings.Secure.putString(mContext.getContentResolver(),
-                    Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
-            // We the disabled input method is currently selected, switch
-            // to another one.
-            String selId = Settings.Secure.getString(mContext.getContentResolver(),
-                    Settings.Secure.DEFAULT_INPUT_METHOD);
-            if (id.equals(selId)) {
-                Settings.Secure.putString(mContext.getContentResolver(),
-                        Settings.Secure.DEFAULT_INPUT_METHOD,
-                        firstId != null ? firstId : "");
-            }
-            // Previous state was enabled.
-            return true;
+        public InputMethodSettings(
+                ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap,
+                ArrayList<InputMethodInfo> methodList) {
+            mResolver = resolver;
+            mMethodMap = methodMap;
+            mMethodList = methodList;
         }
 
-        // Add in the newly enabled input method.
-        if (enabledStr == null || enabledStr.length() == 0) {
-            enabledStr = id;
-        } else {
-            enabledStr = enabledStr + ':' + id;
+        public List<InputMethodInfo> getEnabledInputMethodListLocked() {
+            return createEnabledInputMethodListLocked(
+                    getEnabledInputMethodsAndSubtypeListLocked());
         }
 
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
+        // At the initial boot, the settings for input methods are not set,
+        // so we need to enable IME in that case.
+        public void enableAllIMEsIfThereIsNoEnabledIME() {
+            if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
+                StringBuilder sb = new StringBuilder();
+                final int N = mMethodList.size();
+                for (int i = 0; i < N; i++) {
+                    InputMethodInfo imi = mMethodList.get(i);
+                    Slog.i(TAG, "Adding: " + imi.getId());
+                    if (i > 0) sb.append(':');
+                    sb.append(imi.getId());
+                }
+                putEnabledInputMethodsStr(sb.toString());
+            }
+        }
 
-        // Previous state was disabled.
-        return false;
+        public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+            ArrayList<Pair<String, ArrayList<String>>> imsList
+                    = new ArrayList<Pair<String, ArrayList<String>>>();
+            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+                return imsList;
+            }
+            mStringColonSplitter.setString(enabledInputMethodsStr);
+            while (mStringColonSplitter.hasNext()) {
+                String nextImsStr = mStringColonSplitter.next();
+                mStringSemiColonSplitter.setString(nextImsStr);
+                if (mStringSemiColonSplitter.hasNext()) {
+                    ArrayList<String> subtypeHashes = new ArrayList<String>();
+                    // The first element is ime id.
+                    String imeId = mStringSemiColonSplitter.next();
+                    while (mStringSemiColonSplitter.hasNext()) {
+                        subtypeHashes.add(mStringSemiColonSplitter.next());
+                    }
+                    imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
+                }
+            }
+            return imsList;
+        }
+
+        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
+            if (reloadInputMethodStr) {
+                getEnabledInputMethodsStr();
+            }
+            if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
+                // Add in the newly enabled input method.
+                putEnabledInputMethodsStr(id);
+            } else {
+                putEnabledInputMethodsStr(
+                        mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
+            }
+        }
+
+        /**
+         * Build and put a string of EnabledInputMethods with removing specified Id.
+         * @return the specified id was removed or not.
+         */
+        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+            boolean isRemoved = false;
+            boolean needsAppendSeparator = false;
+            for (Pair<String, ArrayList<String>> ims: imsList) {
+                String curId = ims.first;
+                if (curId.equals(id)) {
+                    // We are disabling this input method, and it is
+                    // currently enabled.  Skip it to remove from the
+                    // new list.
+                    isRemoved = true;
+                } else {
+                    if (needsAppendSeparator) {
+                        builder.append(INPUT_METHOD_SEPARATER);
+                    } else {
+                        needsAppendSeparator = true;
+                    }
+                    buildEnabledInputMethodsSettingString(builder, ims);
+                }
+            }
+            if (isRemoved) {
+                // Update the setting with the new list of input methods.
+                putEnabledInputMethodsStr(builder.toString());
+            }
+            return isRemoved;
+        }
+
+        private List<InputMethodInfo> createEnabledInputMethodListLocked(
+                List<Pair<String, ArrayList<String>>> imsList) {
+            final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
+            for (Pair<String, ArrayList<String>> ims: imsList) {
+                InputMethodInfo info = mMethodMap.get(ims.first);
+                if (info != null) {
+                    res.add(info);
+                }
+            }
+            return res;
+        }
+
+        private void putEnabledInputMethodsStr(String str) {
+            Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
+            mEnabledInputMethodsStrCache = str;
+        }
+
+        private String getEnabledInputMethodsStr() {
+            mEnabledInputMethodsStrCache = Settings.Secure.getString(
+                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
+            return mEnabledInputMethodsStrCache;
+        }
     }
 
     // ----------------------------------------------------------------------
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 361cd3b..19ea4e1 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -475,14 +475,17 @@
         mEnabledProviders.add(passiveProvider.getName());
 
         // initialize external network location and geocoder services
-        if (mNetworkLocationProviderPackageName != null) {
+        PackageManager pm = mContext.getPackageManager();
+        if (mNetworkLocationProviderPackageName != null &&
+                pm.resolveService(new Intent(mNetworkLocationProviderPackageName), 0) != null) {
             mNetworkLocationProvider =
                 new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
                         mNetworkLocationProviderPackageName, mLocationHandler);
             addProvider(mNetworkLocationProvider);
         }
 
-        if (mGeocodeProviderPackageName != null) {
+        if (mGeocodeProviderPackageName != null &&
+                pm.resolveService(new Intent(mGeocodeProviderPackageName), 0) != null) {
             mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName);
         }
 
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 5bf2a42..7870b06 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -148,6 +148,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.
@@ -431,7 +433,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));
@@ -502,11 +506,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);
+            }
         }
 
         // Remove all OBB mappings and listeners from this path
@@ -1043,6 +1049,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/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index 7b68d68..cf87a9d 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -132,11 +132,12 @@
                                     Slog.e(TAG, String.format(
                                             "Error handling '%s'", event), ex);
                                 }
-                            }
-                            try {
-                                mResponseQueue.put(event);
-                            } catch (InterruptedException ex) {
-                                Slog.e(TAG, "Failed to put response onto queue", ex);
+                            } else {
+                                try {
+                                    mResponseQueue.put(event);
+                                } catch (InterruptedException ex) {
+                                    Slog.e(TAG, "Failed to put response onto queue", ex);
+                                }
                             }
                         } catch (NumberFormatException nfe) {
                             Slog.w(TAG, String.format("Bad msg (%s)", event));
@@ -219,6 +220,7 @@
      */
     public synchronized ArrayList<String> doCommand(String cmd)
             throws NativeDaemonConnectorException  {
+        mResponseQueue.clear();
         sendCommand(cmd);
 
         ArrayList<String> response = new ArrayList<String>();
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index f0acdc0..8dbd3e7 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -226,10 +226,10 @@
                 throw new UnknownHostException(addrString);
             }
 
-            int a = Integer.parseInt(parts[0])      ;
-            int b = Integer.parseInt(parts[1]) <<  8;
-            int c = Integer.parseInt(parts[2]) << 16;
-            int d = Integer.parseInt(parts[3]) << 24;
+            int a = Integer.parseInt(parts[0]) << 24;
+            int b = Integer.parseInt(parts[1]) << 16;
+            int c = Integer.parseInt(parts[2]) <<  8;
+            int d = Integer.parseInt(parts[3])      ;
 
             return a | b | c | d;
         } catch (NumberFormatException ex) {
diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java
new file mode 100644
index 0000000..52f84eb
--- /dev/null
+++ b/services/java/com/android/server/NetworkTimeUpdateService.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 com.android.internal.telephony.TelephonyIntents;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.SntpClient;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Monitors the network time and updates the system time if it is out of sync
+ * and there hasn't been any NITZ update from the carrier recently.
+ * If looking up the network time fails for some reason, it tries a few times with a short
+ * interval and then resets to checking on longer intervals.
+ * <p>
+ * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't
+ * available.
+ * </p>
+ */
+public class NetworkTimeUpdateService {
+
+    private static final String TAG = "NetworkTimeUpdateService";
+    private static final boolean DBG = false;
+
+    private static final int EVENT_AUTO_TIME_CHANGED = 1;
+    private static final int EVENT_POLL_NETWORK_TIME = 2;
+
+    /** Normal polling frequency */
+    private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs
+    /** Try-again polling interval, in case the network request failed */
+    private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds
+    /** Number of times to try again */
+    private static final int TRY_AGAIN_TIMES_MAX = 3;
+    /** How long to wait for the NTP server to respond. */
+    private static final int MAX_NTP_FETCH_WAIT_MS = 20 * 1000;
+    /** If the time difference is greater than this threshold, then update the time. */
+    private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000;
+
+    private static final String ACTION_POLL =
+            "com.android.server.NetworkTimeUpdateService.action.POLL";
+    private static final String PROPERTIES_FILE = "/etc/gps.conf";
+    private static int POLL_REQUEST = 0;
+
+    private static final long NOT_SET = -1;
+    private long mNitzTimeSetTime = NOT_SET;
+    // TODO: Have a way to look up the timezone we are in
+    private long mNitzZoneSetTime = NOT_SET;
+
+    private Context mContext;
+    // NTP lookup is done on this thread and handler
+    private Handler mHandler;
+    private HandlerThread mThread;
+    private AlarmManager mAlarmManager;
+    private PendingIntent mPendingPollIntent;
+    private SettingsObserver mSettingsObserver;
+    // Address of the NTP server
+    private String mNtpServer;
+    // The last time that we successfully fetched the NTP time.
+    private long mLastNtpFetchTime = NOT_SET;
+    // Keeps track of how many quick attempts were made to fetch NTP time.
+    // During bootup, the network may not have been up yet, or it's taking time for the
+    // connection to happen.
+    private int mTryAgainCounter;
+
+    public NetworkTimeUpdateService(Context context) {
+        mContext = context;
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        Intent pollIntent = new Intent(ACTION_POLL, null);
+        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
+    }
+
+    /** Initialize the receivers and initiate the first NTP request */
+    public void systemReady() {
+        mNtpServer = getNtpServerAddress();
+        if (mNtpServer == null) {
+            Slog.e(TAG, "NTP server address not found, not syncing to NTP time");
+            return;
+        }
+
+        registerForTelephonyIntents();
+        registerForAlarms();
+
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+        mHandler = new MyHandler(mThread.getLooper());
+        // Check the network time on the new thread
+        mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
+
+        mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
+        mSettingsObserver.observe(mContext);
+    }
+
+    private String getNtpServerAddress() {
+        String serverAddress = null;
+        FileInputStream stream = null;
+        try {
+            Properties properties = new Properties();
+            File file = new File(PROPERTIES_FILE);
+            stream = new FileInputStream(file);
+            properties.load(stream);
+            serverAddress = properties.getProperty("NTP_SERVER", null);
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (Exception e) {}
+            }
+        }
+        return serverAddress;
+    }
+
+    private void registerForTelephonyIntents() {
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
+        mContext.registerReceiver(mNitzReceiver, intentFilter);
+    }
+
+    private void registerForAlarms() {
+        mContext.registerReceiver(
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
+                }
+            }, new IntentFilter(ACTION_POLL));
+    }
+
+    private void onPollNetworkTime(int event) {
+        // If Automatic time is not set, don't bother.
+        if (!isAutomaticTimeRequested()) return;
+
+        final long refTime = SystemClock.elapsedRealtime();
+        // If NITZ time was received less than POLLING_INTERVAL_MS time ago,
+        // no need to sync to NTP.
+        if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) {
+            resetAlarm(POLLING_INTERVAL_MS);
+            return;
+        }
+        final long currentTime = System.currentTimeMillis();
+        if (DBG) Log.d(TAG, "System time = " + currentTime);
+        // Get the NTP time
+        if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS
+                || event == EVENT_AUTO_TIME_CHANGED) {
+            if (DBG) Log.d(TAG, "Before Ntp fetch");
+            long ntp = getNtpTime();
+            if (DBG) Log.d(TAG, "Ntp = " + ntp);
+            if (ntp > 0) {
+                mTryAgainCounter = 0;
+                mLastNtpFetchTime = SystemClock.elapsedRealtime();
+                if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS) {
+                    // Set the system time
+                    if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
+                    // Make sure we don't overflow, since it's going to be converted to an int
+                    if (ntp / 1000 < Integer.MAX_VALUE) {
+                        SystemClock.setCurrentTimeMillis(ntp);
+                    }
+                } else {
+                    if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
+                }
+            } else {
+                // Try again shortly
+                mTryAgainCounter++;
+                if (mTryAgainCounter <= TRY_AGAIN_TIMES_MAX) {
+                    resetAlarm(POLLING_INTERVAL_SHORTER_MS);
+                } else {
+                    // Try much later
+                    mTryAgainCounter = 0;
+                    resetAlarm(POLLING_INTERVAL_MS);
+                }
+                return;
+            }
+        }
+        resetAlarm(POLLING_INTERVAL_MS);
+    }
+
+    /**
+     * Cancel old alarm and starts a new one for the specified interval.
+     *
+     * @param interval when to trigger the alarm, starting from now.
+     */
+    private void resetAlarm(long interval) {
+        mAlarmManager.cancel(mPendingPollIntent);
+        long now = SystemClock.elapsedRealtime();
+        long next = now + interval;
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
+    }
+
+    private long getNtpTime() {
+        SntpClient client = new SntpClient();
+        if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT_MS)) {
+            return client.getNtpTime();
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Checks if the user prefers to automatically set the time.
+     */
+    private boolean isAutomaticTimeRequested() {
+        return Settings.System.getInt(mContext.getContentResolver(), Settings.System.AUTO_TIME, 0)
+                != 0;
+    }
+
+    /** Receiver for Nitz time events */
+    private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
+                mNitzTimeSetTime = SystemClock.elapsedRealtime();
+            } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
+                mNitzZoneSetTime = SystemClock.elapsedRealtime();
+            }
+        }
+    };
+
+    /** Handler to do the network accesses on */
+    private class MyHandler extends Handler {
+
+        public MyHandler(Looper l) {
+            super(l);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_AUTO_TIME_CHANGED:
+                case EVENT_POLL_NETWORK_TIME:
+                    onPollNetworkTime(msg.what);
+                    break;
+            }
+        }
+    }
+
+    /** Observer to watch for changes to the AUTO_TIME setting */
+    private static class SettingsObserver extends ContentObserver {
+
+        private int mMsg;
+        private Handler mHandler;
+
+        SettingsObserver(Handler handler, int msg) {
+            super(handler);
+            mHandler = handler;
+            mMsg = msg;
+        }
+
+        void observe(Context context) {
+            ContentResolver resolver = context.getContentResolver();
+            resolver.registerContentObserver(Settings.System.getUriFor(Settings.System.AUTO_TIME),
+                    false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            mHandler.obtainMessage(mMsg).sendToTarget();
+        }
+    }
+}
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 518cfd9..5afabbd 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;
@@ -285,6 +286,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 dbf92ba..c4d2d4d 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -9489,7 +9489,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/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index e77ed69..29a9a7e 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -2536,7 +2536,8 @@
             }
             mKeylightDelay = LONG_KEYLIGHT_DELAY;
             if (totalDelay < 0) {
-                mScreenOffDelay = Integer.MAX_VALUE;
+                // negative number means stay on as long as possible.
+                mScreenOffDelay = mMaximumScreenOffTimeout;
             } else if (mKeylightDelay < totalDelay) {
                 // subtract the time that the keylight delay. This will give us the
                 // remainder of the time that we need to sleep to get the accurate
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/ShutdownActivity.java b/services/java/com/android/server/ShutdownActivity.java
index 64b9c5d..c9d4d01 100644
--- a/services/java/com/android/server/ShutdownActivity.java
+++ b/services/java/com/android/server/ShutdownActivity.java
@@ -27,19 +27,26 @@
 public class ShutdownActivity extends Activity {
 
     private static final String TAG = "ShutdownActivity";
+    private boolean mReboot;
     private boolean mConfirm;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mConfirm = getIntent().getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
+        Intent intent = getIntent();
+        mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());
+        mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
         Slog.i(TAG, "onCreate(): confirm=" + mConfirm);
 
         Handler h = new Handler();
         h.post(new Runnable() {
             public void run() {
-                ShutdownThread.shutdown(ShutdownActivity.this, mConfirm);
+                if (mReboot) {
+                    ShutdownThread.reboot(ShutdownActivity.this, null, mConfirm);
+                } else {
+                    ShutdownThread.shutdown(ShutdownActivity.this, mConfirm);
+                }
             }
         });
     }
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java
index 4177432..b1baec5 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/StatusBarManagerService.java
@@ -68,6 +68,10 @@
     ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
     int mDisabled = 0;
 
+    Object mLock = new Object();
+    // We usually call it lights out mode, but double negatives are annoying
+    boolean mLightsOn = true;
+
     private class DisableRecord implements IBinder.DeathRecipient {
         String pkg;
         int what;
@@ -84,6 +88,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);
@@ -241,6 +246,56 @@
         }
     }
 
+    /**
+     * This is used for the automatic version of lights-out mode.  Only call this from
+     * the window manager.
+     *
+     * @see setLightsOn(boolean)
+     */
+    public void setActiveWindowIsFullscreen(boolean fullscreen) {
+        // We could get away with a separate permission here, but STATUS_BAR is
+        // signatureOrSystem which is probably good enough.  There is no public API
+        // for this, so the question is a security issue, not an API compatibility issue.
+        enforceStatusBar();
+
+        synchronized (mLock) {
+            updateLightsOnLocked(!fullscreen);
+        }
+    }
+
+    /**
+     * This is used for the user-controlled version of lights-out mode.  Only call this from
+     * the status bar itself.
+     *
+     * We have two different functions here, because I think we're going to want to
+     * tweak the behavior when the user keeps turning lights-out mode off and the
+     * app keeps trying to turn it on.  For now they can just fight it out.  Having
+     * these two separte inputs will allow us to keep that change local to here.  --joeo
+     */
+    public void setLightsOn(boolean lightsOn) {
+        enforceStatusBarService();
+
+        synchronized (mLock) {
+            updateLightsOnLocked(lightsOn);
+        }
+    }
+
+    private void updateLightsOnLocked(final boolean lightsOn) {
+        if (mLightsOn != lightsOn) {
+            mLightsOn = lightsOn;
+            mHandler.post(new Runnable() {
+                    public void run() {
+                        if (mBar != null) {
+                            try {
+                                mBar.setLightsOn(lightsOn);
+                            } catch (RemoteException ex) {
+                            }
+                        }
+                    }
+                });
+        }
+    }
+
     private void enforceStatusBar() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR,
                 "StatusBarManagerService");
@@ -261,7 +316,8 @@
     // Callbacks from the status bar service.
     // ================================================================================
     public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
-            List<IBinder> notificationKeys, List<StatusBarNotification> notifications) {
+            List<IBinder> notificationKeys, List<StatusBarNotification> notifications,
+            boolean lightsOn[]) {
         enforceStatusBarService();
 
         Slog.i(TAG, "registerStatusBar bar=" + bar);
@@ -275,6 +331,9 @@
                 notifications.add(e.getValue());
             }
         }
+        synchronized (mLock) {
+            lightsOn[0] = mLightsOn;
+        }
     }
 
     /**
@@ -302,6 +361,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 7f42429..80dcc98 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -24,27 +24,33 @@
 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.content.res.Configuration;
 import android.database.ContentObserver;
-import android.database.Cursor;
 import android.media.AudioService;
-import android.os.*;
-import android.provider.Contacts.People;
+import android.os.Build;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StrictMode;
+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.view.Display;
+import android.view.WindowManager;
+import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
-import android.accounts.AccountManagerService;
 
 import java.io.File;
 import java.util.Timer;
@@ -52,11 +58,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() {
@@ -103,6 +106,7 @@
         UiModeManagerService uiMode = null;
         RecognitionManagerService recognition = null;
         ThrottleService throttle = null;
+        NetworkTimeUpdateService networkTimeUpdater = null;
 
         // Critical services...
         try {
@@ -211,6 +215,7 @@
         NotificationManagerService notification = null;
         WallpaperManagerService wallpaper = null;
         LocationManagerService location = null;
+        CountryDetectorService countryDetector = null;
 
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
             try {
@@ -322,6 +327,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));
@@ -329,11 +342,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,
@@ -432,6 +440,25 @@
             } 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);
+            }
+
+            try {
+                Slog.i(TAG, "NetworkTimeUpdateService");
+                networkTimeUpdater = new NetworkTimeUpdateService(context);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting NetworkTimeUpdate service");
+            }
         }
 
         // make sure the ADB_ENABLED setting value matches the secure property value
@@ -473,6 +500,16 @@
             statusBar.systemReady();
         }
         wm.systemReady();
+
+        // Update the configuration for this context by hand, because we're going
+        // to start using it before the config change done in wm.systemReady() will
+        // propagate to it.
+        Configuration config = wm.computeNewConfiguration();
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+        w.getDefaultDisplay().getMetrics(metrics);
+        context.getResources().updateConfiguration(config, metrics);
+
         power.systemReady();
         try {
             pm.systemReady();
@@ -492,6 +529,8 @@
         final InputMethodManagerService immF = imm;
         final RecognitionManagerService recognitionF = recognition;
         final LocationManagerService locationF = location;
+        final CountryDetectorService countryDetectorF = countryDetector;
+        final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
 
         // 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
@@ -519,7 +558,9 @@
                 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();
+                if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady();
             }
         });
 
@@ -533,37 +574,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;
@@ -587,7 +598,7 @@
             timer.schedule(new TimerTask() {
                 @Override
                 public void run() {
-                    SamplingProfilerIntegration.writeSnapshot("system_server");
+                    SamplingProfilerIntegration.writeSnapshot("system_server", null);
                 }
             }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
         }
@@ -595,7 +606,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 7e23422..2b4845b 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -19,7 +19,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.net.NetworkUtils;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -35,6 +36,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;
@@ -89,9 +91,11 @@
 
     private String mDataConnectionApn = "";
 
-    private String[] mDataConnectionApnTypes = null;
+    private ArrayList<String> mConnectedApns;
 
-    private String mDataConnectionInterfaceName = "";
+    private LinkProperties mDataConnectionLinkProperties;
+
+    private LinkCapabilities mDataConnectionLinkCapabilities;
 
     private Bundle mCellLocation = new Bundle();
 
@@ -121,6 +125,7 @@
         }
         mContext = context;
         mBatteryStats = BatteryStatsService.getService();
+        mConnectedApns = new ArrayList<String>();
     }
 
     public void listen(String pkgForDebug, IPhoneStateListener callback, int events,
@@ -233,19 +238,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);
     }
@@ -256,8 +262,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);
                 }
@@ -270,10 +275,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);
                 }
@@ -283,10 +288,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);
     }
@@ -295,18 +301,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);
         }
     }
 
@@ -314,18 +321,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);
         }
     }
 
@@ -333,57 +341,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 gateway) {
+            String reason, String apn, String apnType, LinkProperties linkProperties,
+            LinkCapabilities linkCapabilities, int networkType) {
         if (!checkNotifyPermission("notifyDataConnection()" )) {
             return;
         }
         synchronized (mRecords) {
-            mDataConnectionState = state;
-            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);
+            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;
+            mDataConnectionLinkProperties = linkProperties;
+            mDataConnectionLinkCapabilities = linkCapabilities;
+            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, gateway);
+                apnType, linkProperties, linkCapabilities);
     }
 
-    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();
@@ -395,7 +427,7 @@
             }
         }
         */
-        broadcastDataConnectionFailed(reason);
+        broadcastDataConnectionFailed(reason, apnType);
     }
 
     public void notifyCellLocation(Bundle cellLocation) {
@@ -404,8 +436,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);
                 }
@@ -416,7 +447,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) {
@@ -462,11 +493,11 @@
             pw.println("  mDataConnectionPossible=" + mDataConnectionPossible);
             pw.println("  mDataConnectionReason=" + mDataConnectionReason);
             pw.println("  mDataConnectionApn=" + mDataConnectionApn);
-            pw.println("  mDataConnectionInterfaceName=" + mDataConnectionInterfaceName);
+            pw.println("  mDataConnectionLinkProperties=" + mDataConnectionLinkProperties);
+            pw.println("  mDataConnectionLinkCapabilities=" + mDataConnectionLinkCapabilities);
             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));
             }
         }
@@ -537,7 +568,8 @@
 
     private void broadcastDataConnectionStateChanged(int state,
             boolean isDataConnectivityPossible,
-            String reason, String apn, String[] apnTypes, String interfaceName, String gateway) {
+            String reason, String apn, String apnType, LinkProperties linkProperties,
+            LinkCapabilities linkCapabilities) {
         // 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.
@@ -550,29 +582,26 @@
         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 (linkProperties != null) {
+            intent.putExtra(Phone.DATA_LINK_PROPERTIES_KEY, linkProperties);
+            String iface = linkProperties.getInterfaceName();
+            if (iface != null) {
+                intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface);
             }
         }
-        intent.putExtra(Phone.DATA_APN_TYPES_KEY, types);
-        intent.putExtra(Phone.DATA_IFACE_NAME_KEY, interfaceName);
-        int gatewayAddr = 0;
-        if (gateway != null) {
-            gatewayAddr = NetworkUtils.v4StringToInt(gateway);
+        if (linkCapabilities != null) {
+            intent.putExtra(Phone.DATA_LINK_CAPABILITIES_KEY, linkCapabilities);
         }
-        intent.putExtra(Phone.DATA_GATEWAY_KEY, gatewayAddr);
-
+        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 3d95bf0..540c7fe 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,109 +16,89 @@
 
 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.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.database.ContentObserver;
 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.WifiStateMachine;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.ConnectivityManager;
 import android.net.InterfaceConfiguration;
-import android.net.NetworkStateTracker;
 import android.net.DhcpInfo;
-import android.net.NetworkUtils;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
 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.os.WorkSource;
 import android.provider.Settings;
-import android.util.Slog;
 import android.text.TextUtils;
+import android.util.Slog;
 
 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 final WifiStateTracker mWifiStateTracker;
-    /* TODO: fetch a configurable interface */
-    private static final String SOFTAP_IFACE = "wl0.1";
+    private static final boolean DBG = true;
+
+    private final WifiStateMachine mWifiStateMachine;
 
     private Context mContext;
-    private int mWifiApState;
 
     private AlarmManager mAlarmManager;
     private PendingIntent mIdleIntent;
+    private BluetoothA2dp mBluetoothA2dp;
     private static final int IDLE_REQUEST = 0;
     private boolean mScreenOff;
     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
-    private int mFullHighPerfLocksAcquired;
-    private int mFullHighPerfLocksReleased;
     private int mFullLocksAcquired;
     private int mFullLocksReleased;
     private int mScanLocksAcquired;
@@ -131,9 +111,7 @@
 
     private final IBatteryStats mBatteryStats;
 
-    private INetworkManagementService nwService;
     ConnectivityManager mCm;
-    private WifiWatchdogService mWifiWatchdogService = null;
     private String[] mWifiRegexs;
 
     /**
@@ -145,68 +123,6 @@
      */
     private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */
 
-    private static final String WAKELOCK_TAG = "*wifi*";
-
-    /**
-     * 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 static final int MESSAGE_ENABLE_NETWORKS    = 9;
-    private static final int MESSAGE_START_SCAN         = 10;
-
-
-    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;
-
-    /**
-     * Temporary for computing UIDS that are responsible for starting WIFI.
-     * Protected by mWifiStateTracker lock.
-     */
-    private final WorkSource mTmpWorkSource = new WorkSource();
-
-    /*
-     * 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).
@@ -216,47 +132,85 @@
     private static final String ACTION_DEVICE_IDLE =
             "com.android.server.WifiManager.action.DEVICE_IDLE";
 
-    WifiService(Context context, WifiStateTracker tracker) {
+    private boolean mIsReceiverRegistered = false;
+
+
+    NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
+
+    // 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.
+     */
+    private static final int ICON_NETWORKS_AVAILABLE =
+            com.android.internal.R.drawable.stat_notify_wifi_in_range;
+    /**
+     * When a notification is shown, we wait this amount before possibly showing it again.
+     */
+    private final long NOTIFICATION_REPEAT_DELAY_MS;
+    /**
+     * Whether the user has set the setting to show the 'available networks' notification.
+     */
+    private boolean mNotificationEnabled;
+    /**
+     * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
+     */
+    private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
+    /**
+     * The {@link System#currentTimeMillis()} must be at least this value for us
+     * to show the notification again.
+     */
+    private long mNotificationRepeatTime;
+    /**
+     * The Notification object given to the NotificationManager.
+     */
+    private Notification mNotification;
+    /**
+     * Whether the notification is being shown, as set by us. That is, if the
+     * user cancels the notification, we will not receive the callback so this
+     * will still be true. We only guarantee if this is false, then the
+     * notification is not showing.
+     */
+    private boolean mNotificationShown;
+    /**
+     * The number of continuous scans that must occur before consider the
+     * supplicant in a scanning state. This allows supplicant to associate with
+     * remembered networks that are in the scan results.
+     */
+    private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
+    /**
+     * The number of scans since the last network state change. When this
+     * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
+     * supplicant to actually be scanning. When the network state changes to
+     * something other than scanning, we reset this to 0.
+     */
+    private int mNumScansSinceNetworkStateChange;
+
+    /**
+     * Temporary for computing UIDS that are responsible for starting WIFI.
+     * Protected by mWifiStateTracker lock.
+     */
+    private final WorkSource mTmpWorkSource = new WorkSource();
+
+    WifiService(Context context) {
         mContext = context;
-        mWifiStateTracker = tracker;
-        mWifiStateTracker.enableRssiPolling(true);
+        mWifiStateMachine = new WifiStateMachine(mContext);
+        mWifiStateMachine.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);
@@ -271,14 +225,50 @@
                 @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));
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                            // reset & clear notification on any wifi state change
+                            resetNotification();
+                        } else if (intent.getAction().equals(
+                                WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+                                    WifiManager.EXTRA_NETWORK_INFO);
+                            // reset & clear notification on a network connect & disconnect
+                            switch(mNetworkInfo.getDetailedState()) {
+                                case CONNECTED:
+                                case DISCONNECTED:
+                                    resetNotification();
+                                    break;
+                            }
+                        } else if (intent.getAction().equals(
+                                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+                            checkAndSetNotification();
+                        }
+                    }
+                }, filter);
+
+        // Setting is in seconds
+        NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(),
+                Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
+        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
+        mNotificationEnabledSettingObserver.register();
     }
 
     /**
@@ -287,7 +277,7 @@
      *
      * This function is used only at boot time
      */
-    public void startWifi() {
+    public void checkAndStartWifi() {
         /* Start if Wi-Fi is enabled or the saved state indicates Wi-Fi was on */
         boolean wifiEnabled = !isAirplaneModeOn()
                 && (getPersistedWifiEnabled() || testAndClearWifiSavedState());
@@ -304,7 +294,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) {
@@ -324,17 +317,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;
                 }
@@ -370,18 +360,13 @@
         Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, enabled ? 1 : 0);
     }
 
-    NetworkStateTracker getNetworkStateTracker() {
-        return mWifiStateTracker;
-    }
-
     /**
      * 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 mWifiStateMachine.syncPingSupplicant();
     }
 
     /**
@@ -389,160 +374,7 @@
      */
     public void startScan(boolean forceActive) {
         enforceChangePermission();
-        if (mWifiHandler == null) return;
-
-        Message.obtain(mWifiHandler, MESSAGE_START_SCAN, forceActive ? 1 : 0, 0).sendToTarget();
-    }
-
-    /**
-     * 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();
-            } else if (wifiState == WIFI_STATE_DISABLED) {
-                mBatteryStats.noteWifiOff();
-            }
-        } 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);
+        mWifiStateMachine.startScan(forceActive);
     }
 
     private void enforceAccessPermission() {
@@ -563,6 +395,50 @@
     }
 
     /**
+     * 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 mWifiStateMachine.setWifiEnabled\n");
+        }
+
+        // set a flag if the user is enabling Wifi while in airplane mode
+        if (enable && isAirplaneModeOn() && isAirplaneToggleable()) {
+            mAirplaneModeOverwridden.set(true);
+        }
+
+        if (enable) {
+            reportStartWorkSource();
+        }
+        mWifiStateMachine.setWifiEnabled(enable);
+
+        /*
+         * Caller might not have WRITE_SECURE_SETTINGS,
+         * only CHANGE_WIFI_STATE is enforced
+         */
+        long ident = Binder.clearCallingIdentity();
+        persistWifiEnabled(enable);
+        Binder.restoreCallingIdentity(ident);
+
+        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},
@@ -572,66 +448,59 @@
      */
     public int getWifiEnabledState() {
         enforceAccessPermission();
-        return mWifiStateTracker.getWifiState();
-    }
-
-    /**
-     * 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();
+        return mWifiStateMachine.syncGetWifiState();
     }
 
     /**
      * 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) {
-
+        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);
+            }
+            /*
+             * Caller might not have WRITE_SECURE_SETTINGS,
+             * only CHANGE_WIFI_STATE is enforced
+             */
             long ident = Binder.clearCallingIdentity();
-            sWakeLock.acquire();
+            setWifiApConfiguration(wifiConfig);
             Binder.restoreCallingIdentity(ident);
-
-            mLastApEnableUid = Binder.getCallingUid();
-            sendAccessPointMessage(enabled, wifiConfig, Binder.getCallingUid());
         }
 
+        mWifiStateMachine.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 mWifiStateMachine.syncGetWifiApState();
+    }
+
+    /**
+     * 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 +518,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 +538,27 @@
     }
 
     /**
-     * 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()}
      */
-    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 void disconnect() {
+        enforceChangePermission();
+        mWifiStateMachine.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()}
      */
-    public int getWifiApEnabledState() {
-        enforceAccessPermission();
-        return mWifiApState;
+    public void reconnect() {
+        enforceChangePermission();
+        mWifiStateMachine.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();
-            } else if (wifiAPState == WIFI_AP_STATE_DISABLED) {
-                mBatteryStats.noteWifiOff();
-            }
-        } 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()}
+     */
+    public void reassociate() {
+        enforceChangePermission();
+        mWifiStateMachine.reassociateCommand();
     }
 
     /**
@@ -810,217 +567,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 mWifiStateMachine.syncGetConfiguredNetworks();
     }
 
     /**
@@ -1030,280 +577,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 mWifiStateMachine.syncAddOrUpdateNetwork(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,8 +588,7 @@
      */
     public boolean removeNetwork(int netId) {
         enforceChangePermission();
-
-        return mWifiStateTracker.removeNetwork(netId);
+        return mWifiStateMachine.syncRemoveNetwork(netId);
     }
 
     /**
@@ -1324,14 +600,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 mWifiStateMachine.syncEnableNetwork(netId, disableOthers);
     }
 
     /**
@@ -1342,8 +611,7 @@
      */
     public boolean disableNetwork(int netId) {
         enforceChangePermission();
-
-        return mWifiStateTracker.disableNetwork(netId);
+        return mWifiStateMachine.syncDisableNetwork(netId);
     }
 
     /**
@@ -1356,7 +624,7 @@
          * Make sure we have the latest information, by sending
          * a status request to the supplicant.
          */
-        return mWifiStateTracker.requestConnectionInfo();
+        return mWifiStateMachine.syncRequestConnectionInfo();
     }
 
     /**
@@ -1366,180 +634,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 mWifiStateMachine.syncGetScanResultsList();
     }
 
     /**
      * 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 mWifiStateMachine.syncSaveConfig();
     }
 
     /**
@@ -1554,7 +661,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 +683,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);
+
+        mWifiStateMachine.setNumAllowedChannels(numChannels);
+
+        return true;
     }
 
     /**
@@ -1615,7 +709,7 @@
          * Wi-Fi is not currently enabled), get the value from
          * Settings.
          */
-        numChannels = mWifiStateTracker.getNumAllowedChannels();
+        numChannels = mWifiStateMachine.getNumAllowedChannels();
         if (numChannels < 0) {
             numChannels = Settings.Secure.getInt(mContext.getContentResolver(),
                     Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS,
@@ -1641,7 +735,86 @@
      */
     public DhcpInfo getDhcpInfo() {
         enforceAccessPermission();
-        return mWifiStateTracker.getDhcpInfo();
+        return mWifiStateMachine.syncGetDhcpInfo();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#startWifi}
+     *
+     */
+    public void startWifi() {
+        enforceChangePermission();
+        /* TODO: may be add permissions for access only to connectivity service
+         * TODO: if a start issued, keep wifi alive until a stop issued irrespective
+         * of WifiLock & device idle status unless wifi enabled status is toggled
+         */
+
+        mWifiStateMachine.setDriverStart(true);
+        mWifiStateMachine.reconnectCommand();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#stopWifi}
+     *
+     */
+    public void stopWifi() {
+        enforceChangePermission();
+        /* TODO: may be add permissions for access only to connectivity service
+         * TODO: if a stop is issued, wifi is brought up only by startWifi
+         * unless wifi enabled status is toggled
+         */
+        mWifiStateMachine.setDriverStart(false);
+    }
+
+
+    /**
+     * see {@link android.net.wifi.WifiManager#addToBlacklist}
+     *
+     */
+    public void addToBlacklist(String bssid) {
+        enforceChangePermission();
+
+        mWifiStateMachine.addToBlacklist(bssid);
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#clearBlacklist}
+     *
+     */
+    public void clearBlacklist() {
+        enforceChangePermission();
+
+        mWifiStateMachine.clearBlacklist();
+    }
+
+    public void connectNetworkWithId(int networkId) {
+        enforceChangePermission();
+        mWifiStateMachine.connectNetwork(networkId);
+    }
+
+    public void connectNetworkWithConfig(WifiConfiguration config) {
+        enforceChangePermission();
+        mWifiStateMachine.connectNetwork(config);
+    }
+
+    public void saveNetwork(WifiConfiguration config) {
+        enforceChangePermission();
+        mWifiStateMachine.saveNetwork(config);
+    }
+
+    public void forgetNetwork(int netId) {
+        enforceChangePermission();
+        mWifiStateMachine.forgetNetwork(netId);
+    }
+
+    public void startWpsPbc(String bssid) {
+        enforceChangePermission();
+        mWifiStateMachine.startWpsPbc(bssid);
+    }
+
+    public void startWpsPin(String bssid, int apPin) {
+        enforceChangePermission();
+        mWifiStateMachine.startWpsPin(bssid, apPin);
     }
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -1663,17 +836,11 @@
                 // Once the screen is on, we are not keeping WIFI running
                 // because of any locks so clear that tracking immediately.
                 reportStartWorkSource();
-                mWifiStateTracker.enableRssiPolling(true);
-                /* DHCP or other temporary failures in the past can prevent
-                 * a disabled network from being connected to, enable on screen on
-                 */
-                if (mWifiStateTracker.isAnyNetworkDisabled()) {
-                    sendEnableNetworksMessage();
-                }
+                mWifiStateMachine.enableRssiPolling(true);
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 Slog.d(TAG, "ACTION_SCREEN_OFF");
                 mScreenOff = true;
-                mWifiStateTracker.enableRssiPolling(false);
+                mWifiStateMachine.enableRssiPolling(false);
                 /*
                  * Set a timer to put Wi-Fi to sleep, but only if the screen is off
                  * AND the "stay on while plugged in" setting doesn't match the
@@ -1681,11 +848,11 @@
                  * or plugged in to AC).
                  */
                 if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) {
-                    WifiInfo info = mWifiStateTracker.requestConnectionInfo();
+                    WifiInfo info = mWifiStateMachine.syncRequestConnectionInfo();
                     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");
@@ -1714,7 +881,6 @@
                  * the already-set timer.
                  */
                 int pluggedType = intent.getIntExtra("plugged", 0);
-                Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType);
                 if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) &&
                         !shouldWifiStayAwake(stayAwakeConditions, pluggedType)) {
                     long triggerTime = System.currentTimeMillis() + idleMillis;
@@ -1724,17 +890,10 @@
                     return;
                 }
                 mPluggedType = pluggedType;
-            } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
-                BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
-                Set<BluetoothDevice> sinks = a2dp.getConnectedSinks();
-                boolean isBluetoothPlaying = false;
-                for (BluetoothDevice sink : sinks) {
-                    if (a2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) {
-                        isBluetoothPlaying = true;
-                    }
-                }
-                mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying);
-
+            } else if (action.equals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED)) {
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+                                               BluetoothA2dp.STATE_NOT_PLAYING);
+                mWifiStateMachine.setBluetoothScanMode(state == BluetoothA2dp.STATE_PLAYING);
             } else {
                 return;
             }
@@ -1772,7 +931,7 @@
          * of {@code 0} isn't really a plugged type, but rather an indication that the
          * device isn't plugged in at all, there is no bit value corresponding to a
          * {@code pluggedType} value of {@code 0}. That is why we shift by
-         * {@code pluggedType&nbsp;&#8212;&nbsp;1} instead of by {@code pluggedType}.
+         * {@code pluggedType - 1} instead of by {@code pluggedType}.
          * @param stayAwakeConditions a bit string specifying which "plugged types" should
          * keep the device (and hence Wi-Fi) awake.
          * @param pluggedType the type of plug (USB, AC, or none) for which the check is
@@ -1785,103 +944,47 @@
         }
     };
 
-    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(int lockMode) {
-        Message.obtain(mWifiHandler, MESSAGE_START_WIFI, lockMode, 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 sendEnableNetworksMessage() {
-        Message.obtain(mWifiHandler, MESSAGE_ENABLE_NETWORKS).sendToTarget();
-    }
-
-    private void reportStartWorkSource() {
-        synchronized (mWifiStateTracker) {
-            mTmpWorkSource.clear();
-            if (mDeviceIdle) {
-                for (int i=0; i<mLocks.mList.size(); i++) {
-                    mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource);
-                }
+    private synchronized void reportStartWorkSource() {
+        mTmpWorkSource.clear();
+        if (mDeviceIdle) {
+            for (int i=0; i<mLocks.mList.size(); i++) {
+                mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource);
             }
-            mWifiStateTracker.updateBatteryWorkSourceLocked(mTmpWorkSource);
-            sWakeLock.setWorkSource(mTmpWorkSource);
         }
+        mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
     }
 
     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 lockHeld;
-        synchronized (mLocks) {
-            lockHeld = mLocks.hasLocks();
-        }
-
-        int strongestLockMode = WifiManager.WIFI_MODE_FULL;
+        boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden.get();
+        boolean lockHeld = mLocks.hasLocks();
+        int strongestLockMode;
         boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode;
         boolean wifiShouldBeStarted = !mDeviceIdle || lockHeld;
-
-        if (lockHeld) {
+        if (mDeviceIdle && lockHeld) {
             strongestLockMode = mLocks.getStrongestLockMode();
-        }
-        /* If device is not idle, lockmode cannot be scan only */
-        if (!mDeviceIdle && strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY) {
+        } else {
             strongestLockMode = WifiManager.WIFI_MODE_FULL;
         }
 
-        synchronized (mWifiHandler) {
-            if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLING) && !airplaneMode) {
-                return;
-            }
+        /* Disable tethering when airplane mode is enabled */
+        if (airplaneMode) {
+            mWifiStateMachine.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);
-                } 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) {
+                reportStartWorkSource();
+                mWifiStateMachine.setWifiEnabled(true);
+                mWifiStateMachine.setScanOnlyMode(
+                        strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
+                mWifiStateMachine.setDriverStart(true);
             } else {
-                sWakeLock.acquire();
-                sendEnableMessage(false, false, mLastEnableUid);
+                mWifiStateMachine.requestCmWakeLock();
+                mWifiStateMachine.setDriverStart(false);
             }
+        } else {
+            mWifiStateMachine.setWifiEnabled(false);
         }
     }
 
@@ -1891,7 +994,7 @@
         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
         intentFilter.addAction(ACTION_DEVICE_IDLE);
-        intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
+        intentFilter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
         mContext.registerReceiver(mReceiver, intentFilter);
     }
 
@@ -1919,97 +1022,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:
-                    reportStartWorkSource();
-                    mWifiStateTracker.setScanOnlyMode(msg.arg1 == WifiManager.WIFI_MODE_SCAN_ONLY);
-                    mWifiStateTracker.restart();
-                    mWifiStateTracker.setHighPerfMode(msg.arg1 ==
-                            WifiManager.WIFI_MODE_FULL_HIGH_PERF);
-                    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;
-
-                case MESSAGE_ENABLE_NETWORKS:
-                    mWifiStateTracker.enableAllNetworks(getConfiguredNetworks());
-                    break;
-
-                case MESSAGE_START_SCAN:
-                    boolean forceActive = (msg.arg1 == 1);
-                    switch (mWifiStateTracker.getSupplicantState()) {
-                        case DISCONNECTED:
-                        case INACTIVE:
-                        case SCANNING:
-                        case DORMANT:
-                            break;
-                        default:
-                            mWifiStateTracker.setScanResultHandling(
-                                    WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY);
-                            break;
-                    }
-                    mWifiStateTracker.scan(forceActive);
-                    break;
-            }
-        }
-    }
-
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -2019,17 +1031,17 @@
                     + ", uid=" + Binder.getCallingUid());
             return;
         }
-        pw.println("Wi-Fi is " + stateName(mWifiStateTracker.getWifiState()));
+        pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName());
         pw.println("Stay-awake conditions: " +
                 Settings.System.getInt(mContext.getContentResolver(),
                                        Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0));
         pw.println();
 
         pw.println("Internal state:");
-        pw.println(mWifiStateTracker);
+        pw.println(mWifiStateMachine);
         pw.println();
         pw.println("Latest scan results:");
-        List<ScanResult> scanResults = mWifiStateTracker.getScanResultsList();
+        List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
         if (scanResults != null && scanResults.size() != 0) {
             pw.println("  BSSID              Frequency   RSSI  Flags             SSID");
             for (ScanResult r : scanResults) {
@@ -2043,33 +1055,14 @@
         }
         pw.println();
         pw.println("Locks acquired: " + mFullLocksAcquired + " full, " +
-                mFullHighPerfLocksAcquired + " full high perf, " +
                 mScanLocksAcquired + " scan");
         pw.println("Locks released: " + mFullLocksReleased + " full, " +
-                mFullHighPerfLocksReleased + " full high perf, " +
                 mScanLocksReleased + " scan");
         pw.println();
         pw.println("Locks held:");
         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, WorkSource ws) {
             super(lockMode, tag, binder, ws);
@@ -2101,15 +1094,11 @@
             if (mList.isEmpty()) {
                 return WifiManager.WIFI_MODE_FULL;
             }
-
-            if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
-                return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+            for (WifiLock l : mList) {
+                if (l.mMode == WifiManager.WIFI_MODE_FULL) {
+                    return WifiManager.WIFI_MODE_FULL;
+                }
             }
-
-            if (mFullLocksAcquired > mFullLocksReleased) {
-                return WifiManager.WIFI_MODE_FULL;
-            }
-
             return WifiManager.WIFI_MODE_SCAN_ONLY;
         }
 
@@ -2147,7 +1136,7 @@
     }
 
     void enforceWakeSourcePermission(int uid, int pid) {
-        if (uid == Process.myUid()) {
+        if (uid == android.os.Process.myUid()) {
             return;
         }
         mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
@@ -2156,11 +1145,7 @@
 
     public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
-        if (lockMode != WifiManager.WIFI_MODE_FULL &&
-                lockMode != WifiManager.WIFI_MODE_SCAN_ONLY &&
-                lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
-            Slog.e(TAG, "Illegal argument, lockMode= " + lockMode);
-            if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
+        if (lockMode != WifiManager.WIFI_MODE_FULL && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY) {
             return false;
         }
         if (ws != null) {
@@ -2183,10 +1168,6 @@
             case WifiManager.WIFI_MODE_FULL:
                 mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
                 break;
-            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-                /* Treat high power as a full lock for battery stats */
-                mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
-                break;
             case WifiManager.WIFI_MODE_SCAN_ONLY:
                 mBatteryStats.noteScanWifiLockAcquiredFromSource(wifiLock.mWorkSource);
                 break;
@@ -2198,10 +1179,6 @@
             case WifiManager.WIFI_MODE_FULL:
                 mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
                 break;
-            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-                /* Treat high power as a full lock for battery stats */
-                mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
-                break;
             case WifiManager.WIFI_MODE_SCAN_ONLY:
                 mBatteryStats.noteScanWifiLockReleasedFromSource(wifiLock.mWorkSource);
                 break;
@@ -2220,9 +1197,6 @@
             case WifiManager.WIFI_MODE_FULL:
                 ++mFullLocksAcquired;
                 break;
-            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-                ++mFullHighPerfLocksAcquired;
-                break;
             case WifiManager.WIFI_MODE_SCAN_ONLY:
                 ++mScanLocksAcquired;
                 break;
@@ -2291,9 +1265,6 @@
                     case WifiManager.WIFI_MODE_FULL:
                         ++mFullLocksReleased;
                         break;
-                    case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-                        ++mFullHighPerfLocksReleased;
-                        break;
                     case WifiManager.WIFI_MODE_SCAN_ONLY:
                         ++mScanLocksReleased;
                         break;
@@ -2365,7 +1336,7 @@
             if (mMulticasters.size() != 0) {
                 return;
             } else {
-                mWifiStateTracker.startPacketFiltering();
+                mWifiStateMachine.startPacketFiltering();
             }
         }
     }
@@ -2380,7 +1351,7 @@
             // our new size == 1 (first call), but this function won't
             // be called often and by making the stopPacket call each
             // time we're less fragile and self-healing.
-            mWifiStateTracker.stopPacketFiltering();
+            mWifiStateMachine.stopPacketFiltering();
         }
 
         int uid = Binder.getCallingUid();
@@ -2417,7 +1388,7 @@
             removed.unlinkDeathRecipient();
         }
         if (mMulticasters.size() == 0) {
-            mWifiStateTracker.startPacketFiltering();
+            mWifiStateMachine.startPacketFiltering();
         }
 
         Long ident = Binder.clearCallingIdentity();
@@ -2436,4 +1407,144 @@
             return (mMulticasters.size() > 0);
         }
     }
+
+    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;
+
+        State state = mNetworkInfo.getState();
+        if ((state == NetworkInfo.State.DISCONNECTED)
+                || (state == NetworkInfo.State.UNKNOWN)) {
+            // Look for an open network
+            List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
+            if (scanResults != null) {
+                int numOpenNetworks = 0;
+                for (int i = scanResults.size() - 1; i >= 0; i--) {
+                    ScanResult scanResult = scanResults.get(i);
+
+                    if (TextUtils.isEmpty(scanResult.capabilities)) {
+                        numOpenNetworks++;
+                    }
+                }
+
+                if (numOpenNetworks > 0) {
+                    if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
+                        /*
+                         * We've scanned continuously at least
+                         * NUM_SCANS_BEFORE_NOTIFICATION times. The user
+                         * probably does not have a remembered network in range,
+                         * since otherwise supplicant would have tried to
+                         * associate and thus resetting this counter.
+                         */
+                        setNotificationVisible(true, numOpenNetworks, false, 0);
+                    }
+                    return;
+                }
+            }
+        }
+
+        // No open networks in range, remove the notification
+        setNotificationVisible(false, 0, false, 0);
+    }
+
+    /**
+     * Clears variables related to tracking whether a notification has been
+     * shown recently and clears the current notification.
+     */
+    private void resetNotification() {
+        mNotificationRepeatTime = 0;
+        mNumScansSinceNetworkStateChange = 0;
+        setNotificationVisible(false, 0, false, 0);
+    }
+
+    /**
+     * Display or don't display a notification that there are open Wi-Fi networks.
+     * @param visible {@code true} if notification should be visible, {@code false} otherwise
+     * @param numNetworks the number networks seen
+     * @param force {@code true} to force notification to be shown/not-shown,
+     * even if it is already shown/not-shown.
+     * @param delay time in milliseconds after which the notification should be made
+     * visible or invisible.
+     */
+    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;
+        }
+
+        NotificationManager notificationManager = (NotificationManager) mContext
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+
+        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 object.
+                mNotification = new Notification();
+                mNotification.when = 0;
+                mNotification.icon = ICON_NETWORKS_AVAILABLE;
+                mNotification.flags = Notification.FLAG_AUTO_CANCEL;
+                mNotification.contentIntent = PendingIntent.getActivity(mContext, 0,
+                        new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0);
+            }
+
+            CharSequence title = mContext.getResources().getQuantityText(
+                    com.android.internal.R.plurals.wifi_available, numNetworks);
+            CharSequence details = mContext.getResources().getQuantityText(
+                    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;
+
+            notificationManager.notify(ICON_NETWORKS_AVAILABLE, mNotification);
+        } else {
+            notificationManager.cancel(ICON_NETWORKS_AVAILABLE);
+        }
+
+        mNotificationShown = visible;
+    }
+
+    private class NotificationEnabledSettingObserver extends ContentObserver {
+
+        public NotificationEnabledSettingObserver(Handler handler) {
+            super(handler);
+        }
+
+        public void register() {
+            ContentResolver cr = mContext.getContentResolver();
+            cr.registerContentObserver(Settings.Secure.getUriFor(
+                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
+            mNotificationEnabled = getValue();
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+
+            mNotificationEnabled = getValue();
+            resetNotification();
+        }
+
+        private boolean getValue() {
+            return Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+        }
+    }
+
+
 }
diff --git a/services/java/com/android/server/WifiStateTracker.java b/services/java/com/android/server/WifiStateTracker.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/java/com/android/server/WifiStateTracker.java
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index 445dd03..46d6bef 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -27,7 +27,6 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiStateTracker;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -77,7 +76,6 @@
     
     private Context mContext;
     private ContentResolver mContentResolver;
-    private WifiStateTracker mWifiStateTracker;
     private WifiManager mWifiManager;
     
     /**
@@ -108,10 +106,9 @@
     /** Whether the current AP check should be canceled. */
     private boolean mShouldCancel;
     
-    WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) {
+    WifiWatchdogService(Context context) {
         mContext = context;
         mContentResolver = context.getContentResolver();
-        mWifiStateTracker = wifiStateTracker;
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         
         createThread();
@@ -275,12 +272,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 +749,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();
+        mWifiManager.reassociate();
     }
 
     private void blacklistAp(String bssid) {
@@ -762,10 +760,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");
-        }
+        mWifiManager.addToBlacklist(bssid);
 
         if (D) {
             myLogD("Blacklisting " + bssid);
@@ -860,10 +855,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");
-            }
+            mWifiManager.clearBlacklist();
             
             if (V) {
                 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
@@ -934,7 +926,7 @@
      * should revert anything done by the watchdog monitoring.
      */
     private void handleReset() {
-        mWifiStateTracker.clearBlacklist();
+        mWifiManager.clearBlacklist();
         setIdleState(true);
     }
     
@@ -1151,7 +1143,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 fa85515..30aed69 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -39,6 +40,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.policy.impl.PhoneWindowManager;
+import com.android.internal.view.BaseInputHandler;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodClient;
 import com.android.internal.view.IInputMethodManager;
@@ -50,6 +52,8 @@
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -92,6 +96,7 @@
 import android.util.SparseIntArray;
 import android.util.TypedValue;
 import android.view.Display;
+import android.view.DragEvent;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.IApplicationToken;
@@ -103,6 +108,8 @@
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.InputHandler;
+import android.view.InputQueue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -158,7 +165,7 @@
     static final boolean DEBUG_STARTING_WINDOW = false;
     static final boolean DEBUG_REORDER = false;
     static final boolean DEBUG_WALLPAPER = false;
-    static final boolean DEBUG_FREEZE = false;
+    static final boolean DEBUG_DRAG = true;
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean HIDE_STACK_CRAWLS = true;
 
@@ -486,6 +493,330 @@
     boolean mTurnOnScreen;
 
     /**
+     * Drag/drop state
+     */
+    class DragState {
+        IBinder mToken;
+        Surface mSurface;
+        boolean mLocalOnly;
+        ClipData mData;
+        ClipDescription mDataDescription;
+        float mThumbOffsetX, mThumbOffsetY;
+        InputChannel mServerChannel, mClientChannel;
+        WindowState mTargetWindow;
+        ArrayList<WindowState> mNotifiedWindows;
+        boolean mDragEnded;
+
+        private final Rect tmpRect = new Rect();
+
+        DragState(IBinder token, Surface surface, boolean localOnly) {
+            mToken = token;
+            mSurface = surface;
+            mLocalOnly = localOnly;
+            mNotifiedWindows = new ArrayList<WindowState>();
+        }
+
+        void reset() {
+            if (mSurface != null) {
+                mSurface.destroy();
+            }
+            mSurface = null;
+            mLocalOnly = false;
+            mToken = null;
+            mData = null;
+            mThumbOffsetX = mThumbOffsetY = 0;
+            mNotifiedWindows = null;
+        }
+
+        void register() {
+            if (DEBUG_DRAG) Slog.d(TAG, "registering drag input channel");
+            if (mClientChannel != null) {
+                Slog.e(TAG, "Duplicate register of drag input channel");
+            } else {
+                InputChannel[] channels = InputChannel.openInputChannelPair("drag");
+                mServerChannel = channels[0];
+                mClientChannel = channels[1];
+                mInputManager.registerInputChannel(mServerChannel);
+                InputQueue.registerInputChannel(mClientChannel, mDragInputHandler,
+                        mH.getLooper().getQueue());
+            }
+        }
+
+        void unregister() {
+            if (DEBUG_DRAG) Slog.d(TAG, "unregistering drag input channel");
+            if (mClientChannel == null) {
+                Slog.e(TAG, "Unregister of nonexistent drag input channel");
+            } else {
+                mInputManager.unregisterInputChannel(mServerChannel);
+                InputQueue.unregisterInputChannel(mClientChannel);
+                mClientChannel.dispose();
+                mClientChannel = null;
+                mServerChannel = null;
+            }
+        }
+
+        /* call out to each visible window/session informing it about the drag
+         */
+        void broadcastDragStartedLw() {
+            // Cache a base-class instance of the clip metadata so that parceling
+            // works correctly in calling out to the apps.
+            mDataDescription = new ClipDescription(mData);
+            mNotifiedWindows.clear();
+
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "broadcasting DRAG_STARTED of " + mDataDescription);
+            }
+
+            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
+                    mDataDescription, null);
+            final int N = mWindows.size();
+            for (int i = 0; i < N; i++) {
+                // sendDragStartedLw() clones evt for local-process dispatch
+                sendDragStartedLw(mWindows.get(i), evt);
+            }
+            evt.recycle();
+        }
+
+        /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the
+         * designated window is potentially a drop recipient.  There are race situations
+         * around DRAG_ENDED broadcast, so we make sure that once we've declared that
+         * the drag has ended, we never send out another DRAG_STARTED for this drag action.
+         *
+         * This method clones the 'event' parameter if it's being delivered to the same
+         * process, so it's safe for the caller to call recycle() on the event afterwards.
+         */
+        private void sendDragStartedLw(WindowState newWin, DragEvent event) {
+            if (!mDragEnded && newWin.isPotentialDragTarget()) {
+                try {
+                    // clone for local callees since dispatch will recycle the event
+                    if (Process.myPid() == newWin.mSession.mPid) {
+                        event = DragEvent.obtain(event);
+                    }
+                    newWin.mClient.dispatchDragEvent(event);
+                    // track each window that we've notified that the drag is starting
+                    mNotifiedWindows.add(newWin);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to drag-start window " + newWin);
+                }
+            }
+        }
+
+        /* helper - construct and send a DRAG_STARTED event only if the window has not
+         * previously been notified, i.e. it became visible after the drag operation
+         * was begun.  This is a rare case.
+         */
+        private void sendDragStartedIfNeededLw(WindowState newWin) {
+            // If we have sent the drag-started, we needn't do so again
+            for (WindowState ws : mNotifiedWindows) {
+                if (ws == newWin) {
+                    return;
+                }
+            }
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "sending DRAG_STARTED to new window " + newWin);
+            }
+            DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
+                    mDataDescription, null);
+            // sendDragStartedLw() clones 'event' if the window is process-local
+            sendDragStartedLw(newWin, event);
+            event.recycle();
+        }
+
+        void broadcastDragEnded() {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "broadcasting DRAG_ENDED");
+            }
+            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, 0, 0, null, null);
+            synchronized (mWindowMap) {
+                for (WindowState ws: mNotifiedWindows) {
+                    try {
+                        ws.mClient.dispatchDragEvent(evt);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Unable to drag-end window " + ws);
+                    }
+                }
+                mNotifiedWindows.clear();
+                mDragEnded = true;
+            }
+            evt.recycle();
+        }
+
+        void notifyMoveLw(float x, float y) {
+            final int myPid = Process.myPid();
+
+            // Move the surface to the given touch
+            mSurface.openTransaction();
+            mSurface.setPosition((int)(x - mThumbOffsetX), (int)(y - mThumbOffsetY));
+            mSurface.closeTransaction();
+
+            // Tell the affected window
+            WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+            try {
+                // have we dragged over a new window?
+                if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
+                    if (DEBUG_DRAG) {
+                        Slog.d(TAG, "sending DRAG_EXITED to " + mTargetWindow);
+                    }
+                    // force DRAG_EXITED_EVENT if appropriate
+                    DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED,
+                            0, 0, null, null);
+                    mTargetWindow.mClient.dispatchDragEvent(evt);
+                    if (myPid != mTargetWindow.mSession.mPid) {
+                        evt.recycle();
+                    }
+                }
+                if (touchedWin != null) {
+                    if (DEBUG_DRAG) {
+                        Slog.d(TAG, "sending DRAG_LOCATION to " + touchedWin);
+                    }
+                    DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION,
+                            x, y, null, null);
+                    touchedWin.mClient.dispatchDragEvent(evt);
+                    if (myPid != touchedWin.mSession.mPid) {
+                        evt.recycle();
+                    }
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "can't send drag notification to windows");
+            }
+            mTargetWindow = touchedWin;
+        }
+
+        // Tell the drop target about the data, and then broadcast the drag-ended notification
+        void notifyDropLw(float x, float y) {
+            WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+            if (touchedWin != null) {
+                if (DEBUG_DRAG) {
+                    Slog.d(TAG, "sending DROP to " + touchedWin);
+                }
+                final int myPid = Process.myPid();
+                DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP, x, y, null, mData);
+                try {
+                    touchedWin.mClient.dispatchDragEvent(evt);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "can't send drop notification to win " + touchedWin);
+                }
+                if (myPid != touchedWin.mSession.mPid) {
+                    evt.recycle();
+                }
+            }
+        }
+
+        // Find the visible, touch-deliverable window under the given point
+        private WindowState getTouchedWinAtPointLw(float xf, float yf) {
+            WindowState touchedWin = null;
+            final int x = (int) xf;
+            final int y = (int) yf;
+            final ArrayList<WindowState> windows = mWindows;
+            final int N = windows.size();
+            for (int i = N - 1; i >= 0; i--) {
+                WindowState child = windows.get(i);
+                final int flags = child.mAttrs.flags;
+                if (!child.isVisibleLw()) {
+                    // not visible == don't tell about drags
+                    continue;
+                }
+                if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+                    // not touchable == don't tell about drags
+                    continue;
+                }
+                // account for the window's decor etc
+                tmpRect.set(child.mFrame);
+                if (child.mTouchableInsets == ViewTreeObserver
+                            .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) {
+                    // The point is inside of the window if it is
+                    // inside the frame, AND the content part of that
+                    // frame that was given by the application.
+                    tmpRect.left += child.mGivenContentInsets.left;
+                    tmpRect.top += child.mGivenContentInsets.top;
+                    tmpRect.right -= child.mGivenContentInsets.right;
+                    tmpRect.bottom -= child.mGivenContentInsets.bottom;
+                } else if (child.mTouchableInsets == ViewTreeObserver
+                            .InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) {
+                    // The point is inside of the window if it is
+                    // inside the frame, AND the visible part of that
+                    // frame that was given by the application.
+                    tmpRect.left += child.mGivenVisibleInsets.left;
+                    tmpRect.top += child.mGivenVisibleInsets.top;
+                    tmpRect.right -= child.mGivenVisibleInsets.right;
+                    tmpRect.bottom -= child.mGivenVisibleInsets.bottom;
+                }
+                final int touchFlags = flags &
+                    (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+                if (tmpRect.contains(x, y) || touchFlags == 0) {
+                    // Found it
+                    touchedWin = child;
+                    break;
+                }
+            }
+
+            return touchedWin;
+        }
+    }
+
+    DragState mDragState = null;
+    private final InputHandler mDragInputHandler = new BaseInputHandler() {
+        @Override
+        public void handleMotion(MotionEvent event, Runnable finishedCallback) {
+            boolean endDrag = false;
+            final float newX = event.getRawX();
+            final float newY = event.getRawY();
+
+            try {
+                if (mDragState != null) {
+                    switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN: {
+                        if (DEBUG_DRAG) {
+                            Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer");
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_MOVE: {
+                        synchronized (mWindowMap) {
+                            // move the surface and tell the involved window(s) where we are
+                            mDragState.notifyMoveLw(newX, newY);
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_UP: {
+                        if (DEBUG_DRAG) Slog.d(TAG, "Got UP on move channel; dropping at "
+                                + newX + "," + newY);
+                        synchronized (mWindowMap) {
+                            mDragState.notifyDropLw(newX, newY);
+                        }
+                        endDrag = true;
+                    } break;
+
+                    case MotionEvent.ACTION_CANCEL: {
+                        if (DEBUG_DRAG) Slog.d(TAG, "Drag cancelled!");
+                        endDrag = true;
+                    } break;
+                    }
+
+                    if (endDrag) {
+                        if (DEBUG_DRAG) Slog.d(TAG, "Drag ended; tearing down state");
+                        // tell all the windows that the drag has ended
+                        mDragState.broadcastDragEnded();
+
+                        // stop intercepting input
+                        mDragState.unregister();
+                        mInputMonitor.updateInputWindowsLw();
+
+                        // free our resources and drop all the object references
+                        mDragState.reset();
+                        mDragState = null;
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception caught by drag handleMotion", e);
+            } finally {
+                finishedCallback.run();
+            }
+        }
+    };
+
+    /**
      * Whether the UI is currently running in touch mode (not showing
      * navigational focus because the user is directly pressing the screen).
      */
@@ -1789,18 +2120,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())) {
@@ -1906,7 +2230,7 @@
 
             res = WindowManagerImpl.ADD_OKAY;
 
-            final long origId = Binder.clearCallingIdentity();
+            origId = Binder.clearCallingIdentity();
 
             if (addToken) {
                 mTokenMap.put(attrs.token, token);
@@ -1983,14 +2307,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;
@@ -4436,8 +4756,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;
                 }
             }
@@ -5055,7 +5374,60 @@
         mPolicy.adjustConfigurationLw(config);
         return true;
     }
-    
+
+    // -------------------------------------------------------------
+    // Drag and drop
+    // -------------------------------------------------------------
+
+    IBinder prepareDragSurface(IWindow window, SurfaceSession session,
+            boolean localOnly, int width, int height, Surface outSurface) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG, "prepare drag surface: w=" + width + " h=" + height
+                    + " local=" + localOnly + " win=" + window
+                    + " asbinder=" + window.asBinder());
+        }
+
+        final int callerPid = Binder.getCallingPid();
+        final long origId = Binder.clearCallingIdentity();
+        IBinder token = null;
+
+        try {
+            synchronized (mWindowMap) {
+                try {
+                    // !!! TODO: fail if the given window does not currently have touch focus?
+
+                    if (mDragState == null) {
+                        Surface surface = new Surface(session, callerPid, "drag surface", 0,
+                                width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+                        outSurface.copyFrom(surface);
+                        token = new Binder();
+                        mDragState = new DragState(token, surface, localOnly);
+                        mDragState.mSurface = surface;
+                        mDragState.mLocalOnly = localOnly;
+                        token = mDragState.mToken = new Binder();
+
+                        // 5 second timeout for this window to actually begin the drag
+                        mH.removeMessages(H.DRAG_START_TIMEOUT, window);
+                        Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, window.asBinder());
+                        mH.sendMessageDelayed(msg, 5000);
+                    } else {
+                        Slog.w(TAG, "Drag already in progress");
+                    }
+                } catch (Surface.OutOfResourcesException e) {
+                    Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e);
+                    if (mDragState != null) {
+                        mDragState.reset();
+                        mDragState = null;
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return token;
+    }
+
     // -------------------------------------------------------------
     // Input Events and Focus Management
     // -------------------------------------------------------------
@@ -5154,7 +5526,42 @@
             
             return null;
         }
-        
+
+        private void addDragInputWindow(InputWindowList windowList) {
+            final InputWindow inputWindow = windowList.add();
+            inputWindow.inputChannel = mDragState.mServerChannel;
+            inputWindow.name = "drag";
+            inputWindow.layoutParamsFlags = 0;
+            inputWindow.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
+            inputWindow.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+            inputWindow.visible = true;
+            inputWindow.canReceiveKeys = false;
+            inputWindow.hasFocus = true;
+            inputWindow.hasWallpaper = false;
+            inputWindow.paused = false;
+            inputWindow.layer = mPolicy.windowTypeToLayerLw(inputWindow.layoutParamsType)
+                    * TYPE_LAYER_MULTIPLIER
+                    + TYPE_LAYER_OFFSET;
+            inputWindow.ownerPid = Process.myPid();
+            inputWindow.ownerUid = Process.myUid();
+
+            // The drag window covers the entire display
+            inputWindow.frameLeft = 0;
+            inputWindow.frameTop = 0;
+            inputWindow.frameRight = mDisplay.getWidth();
+            inputWindow.frameBottom = mDisplay.getHeight();
+
+            inputWindow.visibleFrameLeft = inputWindow.frameLeft;
+            inputWindow.visibleFrameTop = inputWindow.frameTop;
+            inputWindow.visibleFrameRight = inputWindow.frameRight;
+            inputWindow.visibleFrameBottom = inputWindow.frameBottom;
+
+            inputWindow.touchableAreaLeft = inputWindow.frameLeft;
+            inputWindow.touchableAreaTop = inputWindow.frameTop;
+            inputWindow.touchableAreaRight = inputWindow.frameRight;
+            inputWindow.touchableAreaBottom = inputWindow.frameBottom;
+        }
+
         /* Updates the cached window information provided to the input dispatcher. */
         public void updateInputWindowsLw() {
             // Populate the input window list with information about all of the windows that
@@ -5163,6 +5570,16 @@
             // out to be difficult because only the native code knows for sure which window
             // currently has touch focus.
             final ArrayList<WindowState> windows = mWindows;
+
+            // If there's a drag in flight, provide a pseudowindow to catch drag input
+            final boolean inDrag = (mDragState != null);
+            if (inDrag) {
+                if (DEBUG_DRAG) {
+                    Log.d(TAG, "Inserting drag window");
+                }
+                addDragInputWindow(mTempInputWindows);
+            }
+
             final int N = windows.size();
             for (int i = N - 1; i >= 0; i--) {
                 final WindowState child = windows.get(i);
@@ -5178,7 +5595,13 @@
                 final boolean isVisible = child.isVisibleLw();
                 final boolean hasWallpaper = (child == mWallpaperTarget)
                         && (type != WindowManager.LayoutParams.TYPE_KEYGUARD);
-                
+
+                // If there's a drag in progress and 'child' is a potential drop target,
+                // make sure it's been told about the drag
+                if (inDrag && isVisible) {
+                    mDragState.sendDragStartedIfNeededLw(child);
+                }
+
                 // Add a window to our list of input windows.
                 final InputWindow inputWindow = mTempInputWindows.add();
                 inputWindow.inputChannel = child.mInputChannel;
@@ -5569,6 +5992,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();
     }
 
@@ -5734,6 +6173,86 @@
             }
         }
 
+        /* Drag/drop */
+        public IBinder prepareDrag(IWindow window, boolean localOnly,
+                int width, int height, Surface outSurface) {
+            return prepareDragSurface(window, mSurfaceSession, localOnly,
+                    width, height, outSurface);
+        }
+
+        public boolean performDrag(IWindow window, IBinder dragToken,
+                float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+                ClipData data) {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "perform drag: win=" + window + " data=" + data);
+            }
+
+            synchronized (mWindowMap) {
+                if (mDragState == null) {
+                    Slog.w(TAG, "No drag prepared");
+                    throw new IllegalStateException("performDrag() without prepareDrag()");
+                }
+
+                if (dragToken != mDragState.mToken) {
+                    Slog.w(TAG, "Performing mismatched drag");
+                    throw new IllegalStateException("performDrag() does not match prepareDrag()");
+                }
+
+                WindowState callingWin = windowForClientLocked(null, window, false);
+                if (callingWin == null) {
+                    Slog.w(TAG, "Bad requesting window " + window);
+                    return false;  // !!! TODO: throw here?
+                }
+
+                // !!! TODO: if input is not still focused on the initiating window, fail
+                // the drag initiation (e.g. an alarm window popped up just as the application
+                // called performDrag()
+
+                mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
+
+                // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
+                // will let us eliminate the (touchX,touchY) parameters from the API.
+
+                mDragState.register();
+                mInputMonitor.updateInputWindowsLw();
+                mInputManager.transferTouchFocus(callingWin.mInputChannel,
+                        mDragState.mServerChannel);
+
+                mDragState.mData = data;
+                mDragState.broadcastDragStartedLw();
+
+                // remember the thumb offsets for later
+                mDragState.mThumbOffsetX = thumbCenterX;
+                mDragState.mThumbOffsetY = thumbCenterY;
+
+                // Make the surface visible at the proper location
+                final Surface surface = mDragState.mSurface;
+                surface.openTransaction();
+                try {
+                    surface.setPosition((int)(touchX - thumbCenterX),
+                            (int)(touchY - thumbCenterY));
+                    surface.setAlpha(.5f);
+                    surface.show();
+                } finally {
+                    surface.closeTransaction();
+                }
+            }
+
+            return true;    // success!
+        }
+
+        public void dragRecipientEntered(IWindow window) {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "Drag into new candidate view @ " + window);
+            }
+        }
+
+        public void dragRecipientExited(IWindow window) {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "Drag from old candidate view @ " + window);
+            }
+        }
+
         public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
             synchronized(mWindowMap) {
                 long ident = Binder.clearCallingIdentity();
@@ -6849,7 +7368,7 @@
             final AppWindowToken atoken = mAppToken;
             return mSurface != null && !mAttachedHidden
                     && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
-                    && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending))
+                    && !mDrawPending && !mCommitDrawPending
                     && !mExiting && !mDestroying;
         }
 
@@ -6875,6 +7394,15 @@
         }
 
         /**
+         * Can this window possibly be a drag/drop target?  The test here is
+         * a combination of the above "visible now" with the check that the
+         * Input Manager uses when discarding windows from input consideration.
+         */
+        boolean isPotentialDragTarget() {
+            return isVisibleNow() && (mInputChannel != null) && !mRemoved;
+        }
+
+        /**
          * Same as isVisible(), but we also count it as visible between the
          * call to IWindowSession.add() and the first relayout().
          */
@@ -6953,14 +7481,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;
         }
 
         /**
@@ -7777,6 +8303,7 @@
         public static final int APP_FREEZE_TIMEOUT = 17;
         public static final int SEND_NEW_CONFIGURATION = 18;
         public static final int REPORT_WINDOWS_CHANGE = 19;
+        public static final int DRAG_START_TIMEOUT = 20;
 
         private Session mLastReportedHold;
 
@@ -8119,6 +8646,20 @@
                     break;
                 }
 
+                case DRAG_START_TIMEOUT: {
+                    IBinder win = (IBinder)msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG, "Timeout starting drag by win " + win);
+                    }
+                    synchronized (mWindowMap) {
+                        // !!! TODO: ANR the app that has failed to start the drag in time
+                        if (mDragState != null) {
+                            mDragState.reset();
+                            mDragState = null;
+                        }
+                    }
+                }
+
             }
         }
     }
@@ -8461,6 +9002,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();
@@ -9249,12 +9795,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);
@@ -9655,30 +10195,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) {
@@ -9967,8 +10503,6 @@
             mFreezeGcPending = now;
         }
 
-        if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException());
-        
         mDisplayFrozen = true;
         
         mInputMonitor.freezeInputDispatchingLw();
@@ -9995,8 +10529,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 b2cbbb6..d008c90 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -2451,6 +2451,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);
@@ -4346,8 +4350,10 @@
             return -1;
         }
 
-        if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                "Checking grant " + targetPkg + " permission to " + uri);
+        if (targetPkg != null) {
+            if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                    "Checking grant " + targetPkg + " permission to " + uri);
+        }
         
         final IPackageManager pm = AppGlobals.getPackageManager();
 
@@ -4376,23 +4382,45 @@
         }
 
         int targetUid;
-        try {
-            targetUid = pm.getPackageUid(targetPkg);
-            if (targetUid < 0) {
-                if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                        "Can't grant URI permission no uid for: " + targetPkg);
+        if (targetPkg != null) {
+            try {
+                targetUid = pm.getPackageUid(targetPkg);
+                if (targetUid < 0) {
+                    if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                            "Can't grant URI permission no uid for: " + targetPkg);
+                    return -1;
+                }
+            } catch (RemoteException ex) {
                 return -1;
             }
-        } catch (RemoteException ex) {
-            return -1;
+        } else {
+            targetUid = -1;
         }
 
-        // First...  does the target actually need this permission?
-        if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
-            // No need to grant the target this permission.
-            if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                    "Target " + targetPkg + " already has full permission to " + uri);
-            return -1;
+        if (targetUid >= 0) {
+            // First...  does the target actually need this permission?
+            if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
+                // No need to grant the target this permission.
+                if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                        "Target " + targetPkg + " already has full permission to " + uri);
+                return -1;
+            }
+        } else {
+            // First...  there is no target package, so can anyone access it?
+            boolean allowed = pi.exported;
+            if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+                if (pi.readPermission != null) {
+                    allowed = false;
+                }
+            }
+            if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+                if (pi.writePermission != null) {
+                    allowed = false;
+                }
+            }
+            if (allowed) {
+                return -1;
+            }
         }
 
         // Second...  is the provider allowing granting of URI permissions?
@@ -4422,16 +4450,25 @@
 
         // Third...  does the caller itself have permission to access
         // this uri?
-        if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
-            if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
-                throw new SecurityException("Uid " + callingUid
-                        + " does not have permission to uri " + uri);
+        if (callingUid != Process.myUid()) {
+            if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+                if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
+                    throw new SecurityException("Uid " + callingUid
+                            + " does not have permission to uri " + uri);
+                }
             }
         }
 
         return targetUid;
     }
 
+    public int checkGrantUriPermission(int callingUid, String targetPkg,
+            Uri uri, int modeFlags) {
+        synchronized(this) {
+            return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
+        }
+    }
+
     void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg,
             Uri uri, int modeFlags, UriPermissionOwner owner) {
         modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -4474,6 +4511,10 @@
 
     void grantUriPermissionLocked(int callingUid,
             String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
+        if (targetPkg == null) {
+            throw new NullPointerException("targetPkg");
+        }
+
         int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
         if (targetUid < 0) {
             return;
@@ -4492,6 +4533,10 @@
                 + " from " + intent + "; flags=0x"
                 + Integer.toHexString(intent != null ? intent.getFlags() : 0));
 
+        if (targetPkg == null) {
+            throw new NullPointerException("targetPkg");
+        }
+
         if (intent == null) {
             return -1;
         }
@@ -5956,6 +6001,35 @@
         }
     }
 
+    public void setImmersive(IBinder token, boolean immersive) {
+        synchronized(this) {
+            int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1;
+            if (index < 0) {
+                throw new IllegalArgumentException();
+            }
+            ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index);
+            r.immersive = immersive;
+        }
+    }
+
+    public boolean isImmersive(IBinder token) {
+        synchronized (this) {
+            int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1;
+            if (index < 0) {
+                throw new IllegalArgumentException();
+            }
+            ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index);
+            return r.immersive;
+        }
+    }
+
+    public boolean isTopActivityImmersive() {
+        synchronized (this) {
+            ActivityRecord r = mMainStack.topRunningActivityLocked(null);
+            return (r != null) ? r.immersive : false;
+        }
+    }
+
     public final void enterSafeMode() {
         synchronized(this) {
             // It only makes sense to do this before the system is ready
@@ -7288,6 +7362,12 @@
                     dumpServicesLocked(fd, pw, args, opti, true);
                 }
                 return;
+            } else {
+                // Dumping a single activity?
+                if (dumpActivity(fd, pw, cmd, args, opti, dumpAll)) {
+                    return;
+                }
+                pw.println("Bad activity command: " + cmd);
             }
         }
         
@@ -7700,6 +7780,82 @@
         }
     }
 
+    /**
+     * There are three things that cmd can be:
+     *  - a flattened component name that matched an existing activity
+     *  - the cmd arg isn't the flattened component name of an existing activity:
+     *    dump all activity whose component contains the cmd as a substring
+     *  - A hex number of the ActivityRecord object instance.
+     */
+    protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+            int opti, boolean dumpAll) {
+        String[] newArgs;
+        ComponentName componentName = ComponentName.unflattenFromString(name);
+        int objectId = 0;
+        try {
+            objectId = Integer.parseInt(name, 16);
+            name = null;
+            componentName = null;
+        } catch (RuntimeException e) {
+        }
+        newArgs = new String[args.length - opti];
+        if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti);
+
+        ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
+        synchronized (this) {
+            for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) {
+                if (componentName != null) {
+                    if (r1.intent.getComponent().equals(componentName)) {
+                        activities.add(r1);
+                    }
+                } else if (name != null) {
+                    if (r1.intent.getComponent().flattenToString().contains(name)) {
+                        activities.add(r1);
+                    }
+                } else if (System.identityHashCode(this) == objectId) {
+                    activities.add(r1);
+                }
+            }
+        }
+
+        if (activities.size() <= 0) {
+            return false;
+        }
+
+        for (int i=0; i<activities.size(); i++) {
+            dumpActivity(fd, pw, activities.get(i), newArgs, dumpAll);
+        }
+        return true;
+    }
+
+    /**
+     * Invokes IApplicationThread.dumpActivity() on the thread of the specified activity if
+     * there is a thread associated with the activity.
+     */
+    private void dumpActivity(FileDescriptor fd, PrintWriter pw, ActivityRecord r, String[] args,
+            boolean dumpAll) {
+        pw.println("  Activity " + r.intent.getComponent().flattenToString());
+        if (dumpAll) {
+            synchronized (this) {
+                pw.print("  * "); pw.println(r);
+                r.dump(pw, "    ");
+            }
+            pw.println("");
+        }
+        if (r.app != null && r.app.thread != null) {
+            try {
+                // flush anything that is already in the PrintWriter since the thread is going
+                // to write to the file descriptor directly
+                pw.flush();
+                r.app.thread.dumpActivity(fd, r, args);
+                pw.print("\n");
+                pw.flush();
+            } catch (RemoteException e) {
+                pw.println("got a RemoteException while dumping the activity");
+            }
+        }
+    }
+
     boolean dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll) {
         boolean needSep = false;
@@ -12568,7 +12724,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/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 53b08d0..6bd89cc 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -105,6 +105,7 @@
     boolean idle;           // has the activity gone idle?
     boolean hasBeenLaunched;// has this activity ever been launched?
     boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
+    boolean immersive;      // immersive mode (don't interrupt if possible)
 
     String stringName;      // for caching of toString().
     
@@ -161,6 +162,7 @@
         pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused);
                 pw.print(" inHistory="); pw.print(inHistory);
                 pw.print(" persistent="); pw.print(persistent);
+                pw.print(" immersive="); pw.print(immersive);
                 pw.print(" launchMode="); pw.println(launchMode);
         pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen);
                 pw.print(" visible="); pw.print(visible);
@@ -292,6 +294,8 @@
             } else {
                 isHomeActivity = false;
             }
+
+            immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0;
         } else {
             realActivity = null;
             taskAffinity = null;
@@ -303,6 +307,7 @@
             packageName = null;
             fullscreen = true;
             isHomeActivity = false;
+            immersive = false;
         }
     }
 
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 73a5435..0a98ebd 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -16,7 +16,9 @@
 
 package com.android.server.am;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -43,6 +45,8 @@
     
     final BatteryStatsImpl mStats;
     Context mContext;
+    private boolean mBluetoothPendingStats;
+    private BluetoothHeadset mBluetoothHeadset;
 
     BatteryStatsService(String filename) {
         mStats = new BatteryStatsImpl(filename);
@@ -283,16 +287,43 @@
 
     public void noteBluetoothOn() {
         enforceCallingPermission();
-        BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+                                    BluetoothProfile.HEADSET);
+        }
         synchronized (mStats) {
-            mStats.noteBluetoothOnLocked();
-            mStats.setBtHeadset(headset);
+            if (mBluetoothHeadset != null) {
+                mStats.noteBluetoothOnLocked();
+                mStats.setBtHeadset(mBluetoothHeadset);
+            } else {
+                mBluetoothPendingStats = true;
+            }
         }
     }
-    
+
+    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+        new BluetoothProfile.ServiceListener() {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mBluetoothHeadset = (BluetoothHeadset) proxy;
+            synchronized (mStats) {
+                if (mBluetoothPendingStats) {
+                    mStats.noteBluetoothOnLocked();
+                    mStats.setBtHeadset(mBluetoothHeadset);
+                    mBluetoothPendingStats = false;
+                }
+            }
+        }
+
+        public void onServiceDisconnected(int profile) {
+            mBluetoothHeadset = null;
+        }
+    };
+
     public void noteBluetoothOff() {
         enforceCallingPermission();
         synchronized (mStats) {
+            mBluetoothPendingStats = false;
             mStats.noteBluetoothOffLocked();
         }
     }
diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java
index 99c82e6..68a2e0f 100644
--- a/services/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/java/com/android/server/am/UriPermissionOwner.java
@@ -45,7 +45,7 @@
     }
 
     Binder getExternalTokenLocked() {
-        if (externalToken != null) {
+        if (externalToken == null) {
             externalToken = new ExternalToken();
         }
         return externalToken;
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 1bc5e4b..bfac346 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -19,8 +19,8 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.bluetooth.BluetoothPan;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -32,7 +32,6 @@
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.NetworkInfo;
-import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -54,6 +53,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.Set;
 /**
  * @hide
@@ -66,7 +66,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
@@ -75,6 +76,7 @@
     // TODO - remove both of these - should be part of interface inspection/selection stuff
     private String[] mTetherableUsbRegexs;
     private String[] mTetherableWifiRegexs;
+    private String[] mTetherableBluetoothRegexs;
     private String[] mUpstreamIfaceRegexs;
 
     private Looper mLooper;
@@ -87,13 +89,27 @@
     private static final String USB_NEAR_IFACE_ADDR      = "192.168.42.129";
     private static final String USB_NETMASK              = "255.255.255.0";
 
-    // FYI - the default wifi is 192.168.43.1 and 255.255.255.0
+    // USB is  192.168.42.1 and 255.255.255.0
+    // Wifi is 192.168.43.1 and 255.255.255.0
+    // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
+    // with 255.255.255.0
 
     private String[] mDhcpRange;
     private static final String DHCP_DEFAULT_RANGE1_START = "192.168.42.2";
     private static final String DHCP_DEFAULT_RANGE1_STOP  = "192.168.42.254";
     private static final String DHCP_DEFAULT_RANGE2_START = "192.168.43.2";
     private static final String DHCP_DEFAULT_RANGE2_STOP  = "192.168.43.254";
+    private static final String DHCP_DEFAULT_RANGE3_START = "192.168.44.2";
+    private static final String DHCP_DEFAULT_RANGE3_STOP  = "192.168.44.254";
+    private static final String DHCP_DEFAULT_RANGE4_START = "192.168.45.2";
+    private static final String DHCP_DEFAULT_RANGE4_STOP  = "192.168.45.254";
+    private static final String DHCP_DEFAULT_RANGE5_START = "192.168.46.2";
+    private static final String DHCP_DEFAULT_RANGE5_STOP  = "192.168.46.254";
+    private static final String DHCP_DEFAULT_RANGE6_START = "192.168.47.2";
+    private static final String DHCP_DEFAULT_RANGE6_STOP  = "192.168.47.254";
+    private static final String DHCP_DEFAULT_RANGE7_START = "192.168.48.2";
+    private static final String DHCP_DEFAULT_RANGE7_STOP  = "192.168.48.254";
+
 
     private String[] mDnsServers;
     private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
@@ -161,11 +177,21 @@
         mDhcpRange = context.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_dhcp_range);
         if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
-            mDhcpRange = new String[4];
+            mDhcpRange = new String[14];
             mDhcpRange[0] = DHCP_DEFAULT_RANGE1_START;
             mDhcpRange[1] = DHCP_DEFAULT_RANGE1_STOP;
             mDhcpRange[2] = DHCP_DEFAULT_RANGE2_START;
             mDhcpRange[3] = DHCP_DEFAULT_RANGE2_STOP;
+            mDhcpRange[4] = DHCP_DEFAULT_RANGE3_START;
+            mDhcpRange[5] = DHCP_DEFAULT_RANGE3_STOP;
+            mDhcpRange[6] = DHCP_DEFAULT_RANGE4_START;
+            mDhcpRange[7] = DHCP_DEFAULT_RANGE4_STOP;
+            mDhcpRange[8] = DHCP_DEFAULT_RANGE5_START;
+            mDhcpRange[9] = DHCP_DEFAULT_RANGE5_STOP;
+            mDhcpRange[10] = DHCP_DEFAULT_RANGE6_START;
+            mDhcpRange[11] = DHCP_DEFAULT_RANGE6_STOP;
+            mDhcpRange[12] = DHCP_DEFAULT_RANGE7_START;
+            mDhcpRange[13] = DHCP_DEFAULT_RANGE7_STOP;
         }
         mDunRequired = false; // resample when we turn on
 
@@ -173,6 +199,8 @@
                 com.android.internal.R.array.config_tether_usb_regexs);
         mTetherableWifiRegexs = context.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_wifi_regexs);
+        mTetherableBluetoothRegexs = context.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_bluetooth_regexs);
         mUpstreamIfaceRegexs = context.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_upstream_regexs);
 
@@ -183,7 +211,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)) {
@@ -191,6 +219,8 @@
         } else if (isUsb(iface)) {
             found = true;
             usb = true;
+        } else if (isBluetooth(iface)) {
+            found = true;
         }
         if (found == false) return;
 
@@ -225,6 +255,12 @@
         return false;
     }
 
+    public boolean isBluetooth(String iface) {
+        for (String regex : mTetherableBluetoothRegexs) {
+            if (iface.matches(regex)) return true;
+        }
+        return false;
+    }
     public void interfaceAdded(String iface) {
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
@@ -237,29 +273,34 @@
             found = true;
             usb = true;
         }
+        if (isBluetooth(iface)) {
+            found = 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);
@@ -330,13 +371,14 @@
 
         boolean wifiTethered = false;
         boolean usbTethered = false;
+        boolean bluetoothTethered = false;
 
         synchronized (mIfaces) {
             Set ifaces = mIfaces.keySet();
             for (Object iface : ifaces) {
                 TetherInterfaceSM sm = mIfaces.get(iface);
                 if (sm != null) {
-                    if(sm.isErrored()) {
+                    if (sm.isErrored()) {
                         erroredList.add((String)iface);
                     } else if (sm.isAvailable()) {
                         availableList.add((String)iface);
@@ -345,6 +387,8 @@
                             usbTethered = true;
                         } else if (isWifi((String)iface)) {
                             wifiTethered = true;
+                      } else if (isBluetooth((String)iface)) {
+                            bluetoothTethered = true;
                         }
                         activeList.add((String)iface);
                     }
@@ -359,17 +403,25 @@
         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) {
+            if (wifiTethered || bluetoothTethered) {
                 showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
             } else {
                 showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_usb);
             }
         } else if (wifiTethered) {
-            showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+            if (bluetoothTethered) {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
+            } else {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+            }
+        } else if (bluetoothTethered) {
+            showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_bluetooth);
         } else {
             clearTetheredNotification();
         }
@@ -400,7 +452,7 @@
         CharSequence message = r.getText(com.android.internal.R.string.
                 tethered_notification_message);
 
-        if(mTetheredNotification == null) {
+        if (mTetheredNotification == null) {
             mTetheredNotification = new Notification();
             mTetheredNotification.when = 0;
         }
@@ -457,7 +509,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;
@@ -488,9 +540,9 @@
         }
     }
 
-    // toggled when we enter/leave the fully teathered state
+    // toggled when we enter/leave the fully tethered 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);
 
@@ -515,7 +567,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);
@@ -571,6 +623,10 @@
         return mTetherableWifiRegexs;
     }
 
+    public String[] getTetherableBluetoothRegexs() {
+        return mTetherableBluetoothRegexs;
+    }
+
     public String[] getUpstreamIfaceRegexs() {
         return mUpstreamIfaceRegexs;
     }
@@ -762,7 +818,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:
@@ -803,7 +859,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?
@@ -855,7 +911,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();
@@ -866,7 +922,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) {
@@ -909,7 +965,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);
@@ -982,7 +1038,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)) {
@@ -1220,8 +1276,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) {
@@ -1229,7 +1287,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();
@@ -1241,11 +1299,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();
@@ -1267,7 +1325,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;
@@ -1284,19 +1342,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);
@@ -1326,7 +1384,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:
@@ -1354,8 +1412,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..e9ce3ce
--- /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 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;
+
+import java.util.Locale;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * 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.isPresent();
+    }
+}
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_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index 8e7be44..4f1fab7 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -1278,6 +1278,27 @@
     env->SetIntField(configObj, gConfigurationClassInfo.navigation, config.navigation);
 }
 
+static jboolean android_server_InputManager_nativeTransferTouchFocus(JNIEnv* env,
+        jclass clazz, jobject fromChannelObj, jobject toChannelObj) {
+    if (checkInputManagerUnitialized(env)) {
+        LOGD("input manager uninitialized; bailing");
+        return false;
+    }
+
+    sp<InputChannel> fromChannel =
+            android_view_InputChannel_getInputChannel(env, fromChannelObj);
+    sp<InputChannel> toChannel =
+            android_view_InputChannel_getInputChannel(env, toChannelObj);
+
+    if (fromChannel == NULL || toChannel == NULL) {
+        LOGD("bailing because from=%p to=%p", fromChannel, toChannel);
+        return false;
+    }
+
+    return gNativeInputManager->getInputManager()->getDispatcher()->
+            transferTouchFocus(fromChannel, toChannel);
+}
+
 static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) {
     if (checkInputManagerUnitialized(env)) {
         return NULL;
@@ -1326,6 +1347,8 @@
             (void*) android_server_InputManager_nativeGetInputDeviceIds },
     { "nativeGetInputConfiguration", "(Landroid/content/res/Configuration;)V",
             (void*) android_server_InputManager_nativeGetInputConfiguration },
+    { "nativeTransferTouchFocus", "(Landroid/view/InputChannel;Landroid/view/InputChannel;)Z",
+            (void*) android_server_InputManager_nativeTransferTouchFocus },
     { "nativeDump", "()Ljava/lang/String;",
             (void*) android_server_InputManager_nativeDump },
 };
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/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index a14bfb5..e4825d0 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -5,6 +5,7 @@
     clz.cpp.arm \
     DisplayHardware/DisplayHardware.cpp \
     DisplayHardware/DisplayHardwareBase.cpp \
+    DisplayHardware/HWComposer.cpp \
     BlurFilter.cpp.arm \
     GLExtensions.cpp \
     Layer.cpp \
@@ -21,7 +22,7 @@
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
 
 ifeq ($(TARGET_BOARD_PLATFORM), omap3)
-	LOCAL_CFLAGS += -DNO_RGBX_8888
+	LOCAL_CFLAGS += -DNO_RGBX_8888 -DHAS_PUSH_BUFFERS
 endif
 
 # need "-lrt" on Linux simulator to pick up clock_gettime
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 0515110..bd348bf 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -36,11 +36,11 @@
 
 #include "DisplayHardware/DisplayHardware.h"
 
-#include <hardware/copybit.h>
 #include <hardware/overlay.h>
 #include <hardware/gralloc.h>
 
 #include "GLExtensions.h"
+#include "HWComposer.h"
 
 using namespace android;
 
@@ -76,7 +76,7 @@
         const sp<SurfaceFlinger>& flinger,
         uint32_t dpy)
     : DisplayHardwareBase(flinger, dpy),
-      mFlags(0)
+      mFlags(0), mHwc(0)
 {
     init(dpy);
 }
@@ -262,6 +262,17 @@
 
     // Unbind the context from this thread
     eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+
+    // initialize the H/W composer
+    mHwc = new HWComposer();
+    if (mHwc->initCheck() == NO_ERROR) {
+        mHwc->setFrameBuffer(mDisplay, mSurface);
+    }
+}
+
+HWComposer& DisplayHardware::getHwComposer() const {
+    return *mHwc;
 }
 
 /*
@@ -281,6 +292,9 @@
 void DisplayHardware::releaseScreen() const
 {
     DisplayHardwareBase::releaseScreen();
+    if (mHwc->initCheck() == NO_ERROR) {
+        mHwc->release();
+    }
 }
 
 void DisplayHardware::acquireScreen() const
@@ -321,7 +335,12 @@
     }
     
     mPageFlipCount++;
-    eglSwapBuffers(dpy, surface);
+
+    if (mHwc->initCheck() == NO_ERROR) {
+        mHwc->commit();
+    } else {
+        eglSwapBuffers(dpy, surface);
+    }
     checkEGLErrors("eglSwapBuffers");
 
     // for debugging
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
index 2d7900c..75b55df 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
@@ -34,12 +34,11 @@
 #include "DisplayHardware/DisplayHardwareBase.h"
 
 struct overlay_control_device_t;
-struct framebuffer_device_t;
-struct copybit_image_t;
 
 namespace android {
 
 class FramebufferNativeWindow;
+class HWComposer;
 
 class DisplayHardware : public DisplayHardwareBase
 {
@@ -80,6 +79,9 @@
     uint32_t getPageFlipCount() const;
     EGLDisplay getEGLDisplay() const { return mDisplay; }
     overlay_control_device_t* getOverlayEngine() const { return mOverlayEngine; }
+
+    // Hardware Composer
+    HWComposer& getHwComposer() const;
     
     status_t compositionComplete() const;
     
@@ -110,6 +112,8 @@
     GLint           mMaxViewportDims;
     GLint           mMaxTextureSize;
     
+    HWComposer*     mHwc;
+
     sp<FramebufferNativeWindow> mNativeWindow;
     overlay_control_device_t* mOverlayEngine;
 };
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
new file mode 100644
index 0000000..ff887e4
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/String8.h>
+
+#include <hardware/hardware.h>
+
+#include <cutils/log.h>
+
+#include <EGL/egl.h>
+
+#include "HWComposer.h"
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+HWComposer::HWComposer()
+    : mModule(0), mHwc(0), mList(0), mCapacity(0),
+      mDpy(EGL_NO_DISPLAY), mSur(EGL_NO_SURFACE)
+{
+    int err = hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule);
+    LOGW_IF(err, "%s module not found", HWC_HARDWARE_MODULE_ID);
+    if (err == 0) {
+        err = hwc_open(mModule, &mHwc);
+        LOGE_IF(err, "%s device failed to initialize (%s)",
+                HWC_HARDWARE_COMPOSER, strerror(-err));
+    }
+}
+
+HWComposer::~HWComposer() {
+    free(mList);
+    if (mHwc) {
+        hwc_close(mHwc);
+    }
+}
+
+status_t HWComposer::initCheck() const {
+    return mHwc ? NO_ERROR : NO_INIT;
+}
+
+void HWComposer::setFrameBuffer(EGLDisplay dpy, EGLSurface sur) {
+    mDpy = (hwc_display_t)dpy;
+    mSur = (hwc_surface_t)sur;
+}
+
+status_t HWComposer::createWorkList(size_t numLayers) {
+    if (mHwc) {
+        if (!mList || mCapacity < numLayers) {
+            free(mList);
+            size_t size = sizeof(hwc_layer_list) + numLayers*sizeof(hwc_layer_t);
+            mList = (hwc_layer_list_t*)malloc(size);
+            mCapacity = numLayers;
+        }
+        mList->flags = HWC_GEOMETRY_CHANGED;
+        mList->numHwLayers = numLayers;
+    }
+    return NO_ERROR;
+}
+
+status_t HWComposer::prepare() const {
+    int err = mHwc->prepare(mHwc, mList);
+    return (status_t)err;
+}
+
+status_t HWComposer::commit() const {
+    int err = mHwc->set(mHwc, mDpy, mSur, mList);
+    mList->flags &= ~HWC_GEOMETRY_CHANGED;
+    return (status_t)err;
+}
+
+status_t HWComposer::release() const {
+    int err = mHwc->set(mHwc, NULL, NULL, NULL);
+    return (status_t)err;
+}
+
+size_t HWComposer::getNumLayers() const {
+    return mList ? mList->numHwLayers : 0;
+}
+
+hwc_layer_t* HWComposer::getLayers() const {
+    return mList ? mList->hwLayers : 0;
+}
+
+void HWComposer::dump(String8& result, char* buffer, size_t SIZE) const {
+    if (mHwc && mList) {
+        result.append("Hardware Composer state:\n");
+
+        snprintf(buffer, SIZE, "  numHwLayers=%u, flags=%08x\n",
+                mList->numHwLayers, mList->flags);
+        result.append(buffer);
+
+        for (size_t i=0 ; i<mList->numHwLayers ; i++) {
+            const hwc_layer_t& l(mList->hwLayers[i]);
+            snprintf(buffer, SIZE, "  %8s | %08x | %08x | %02x | %04x | [%5d,%5d,%5d,%5d] |  [%5d,%5d,%5d,%5d]\n",
+                    l.compositionType ? "OVERLAY" : "FB",
+                    l.hints, l.flags, l.transform, l.blending,
+                    l.sourceCrop.left, l.sourceCrop.top, l.sourceCrop.right, l.sourceCrop.bottom,
+                    l.displayFrame.left, l.displayFrame.top, l.displayFrame.right, l.displayFrame.bottom);
+            result.append(buffer);
+        }
+    }
+}
+
+// ---------------------------------------------------------------------------
+}; // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
new file mode 100644
index 0000000..5a9e9eb
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SF_HWCOMPOSER_H
+#define ANDROID_SF_HWCOMPOSER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <EGL/egl.h>
+
+#include <hardware/hwcomposer.h>
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+class String8;
+
+class HWComposer
+{
+public:
+
+    HWComposer();
+    ~HWComposer();
+
+    status_t initCheck() const;
+
+    // tells the HAL what the framebuffer is
+    void setFrameBuffer(EGLDisplay dpy, EGLSurface sur);
+
+    // create a work list for numLayers layer
+    status_t createWorkList(size_t numLayers);
+
+    // Asks the HAL what it can do
+    status_t prepare() const;
+
+    // commits the list
+    status_t commit() const;
+
+    // release hardware resources
+    status_t release() const;
+
+    size_t getNumLayers() const;
+    hwc_layer_t* getLayers() const;
+
+    // for debugging
+    void dump(String8& out, char* scratch, size_t SIZE) const;
+
+private:
+    hw_module_t const*      mModule;
+    hwc_composer_device_t*  mHwc;
+    hwc_layer_list_t*       mList;
+    size_t                  mCapacity;
+    hwc_display_t           mDpy;
+    hwc_surface_t           mSur;
+};
+
+
+// ---------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_SF_HWCOMPOSER_H
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index a060d31..fb76720 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -35,6 +35,7 @@
 #include "Layer.h"
 #include "SurfaceFlinger.h"
 #include "DisplayHardware/DisplayHardware.h"
+#include "DisplayHardware/HWComposer.h"
 
 
 #define DEBUG_RESIZE    0
@@ -181,6 +182,62 @@
     return NO_ERROR;
 }
 
+void Layer::setGeometry(hwc_layer_t* hwcl)
+{
+    hwcl->compositionType = HWC_FRAMEBUFFER;
+    hwcl->hints = 0;
+    hwcl->flags = 0;
+    hwcl->transform = 0;
+    hwcl->blending = HWC_BLENDING_NONE;
+
+    // we can't do alpha-fade with the hwc HAL
+    const State& s(drawingState());
+    if (s.alpha < 0xFF) {
+        hwcl->flags = HWC_SKIP_LAYER;
+        return;
+    }
+
+    // we can only handle simple transformation
+    if (mOrientation & Transform::ROT_INVALID) {
+        hwcl->flags = HWC_SKIP_LAYER;
+        return;
+    }
+
+    hwcl->transform = mOrientation;
+
+    if (needsBlending()) {
+        hwcl->blending = mPremultipliedAlpha ?
+                HWC_BLENDING_PREMULT : HWC_BLENDING_COVERAGE;
+    }
+
+    hwcl->displayFrame.left   = mTransformedBounds.left;
+    hwcl->displayFrame.top    = mTransformedBounds.top;
+    hwcl->displayFrame.right  = mTransformedBounds.right;
+    hwcl->displayFrame.bottom = mTransformedBounds.bottom;
+
+    hwcl->visibleRegionScreen.rects =
+            reinterpret_cast<hwc_rect_t const *>(
+                    visibleRegionScreen.getArray(
+                            &hwcl->visibleRegionScreen.numRects));
+}
+
+void Layer::setPerFrameData(hwc_layer_t* hwcl) {
+    sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer());
+    if (buffer == NULL) {
+        // this situation can happen if we ran out of memory for instance.
+        // not much we can do. continue to use whatever texture was bound
+        // to this context.
+        hwcl->handle = NULL;
+        return;
+    }
+    hwcl->handle = const_cast<native_handle_t*>(buffer->handle);
+    // TODO: set the crop value properly
+    hwcl->sourceCrop.left   = 0;
+    hwcl->sourceCrop.top    = 0;
+    hwcl->sourceCrop.right  = buffer->width;
+    hwcl->sourceCrop.bottom = buffer->height;
+}
+
 void Layer::reloadTexture(const Region& dirty)
 {
     sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer());
@@ -321,6 +378,7 @@
         Mutex::Autolock _l(mLock);
 
         // zero means default
+        const bool fixedSize = reqWidth && reqHeight;
         if (!reqFormat) reqFormat = mFormat;
         if (!reqWidth)  reqWidth = mWidth;
         if (!reqHeight) reqHeight = mHeight;
@@ -334,6 +392,7 @@
             mReqWidth  = reqWidth;
             mReqHeight = reqHeight;
             mReqFormat = reqFormat;
+            mFixedSize = fixedSize;
 
             lcblk->reallocateAllExcept(index);
         }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 263c372..caa6d17 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -68,6 +68,8 @@
     bool isFixedSize() const;
 
     // LayerBase interface
+    virtual void setGeometry(hwc_layer_t* hwcl);
+    virtual void setPerFrameData(hwc_layer_t* hwcl);
     virtual void drawForSreenShot() const;
     virtual void onDraw(const Region& clip) const;
     virtual uint32_t doTransaction(uint32_t transactionFlags);
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 758b408..14191cb 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -309,6 +309,15 @@
     }
 }
 
+void LayerBase::setGeometry(hwc_layer_t* hwcl) {
+    hwcl->flags |= HWC_SKIP_LAYER;
+}
+
+void LayerBase::setPerFrameData(hwc_layer_t* hwcl) {
+    hwcl->compositionType = HWC_FRAMEBUFFER;
+    hwcl->handle = NULL;
+}
+
 void LayerBase::draw(const Region& clip) const
 {
     // reset GL state
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index d688f65..bdee05b 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -35,6 +35,8 @@
 
 #include <pixelflinger/pixelflinger.h>
 
+#include <hardware/hwcomposer.h>
+
 #include "Transform.h"
 
 namespace android {
@@ -108,6 +110,10 @@
 
     virtual const char* getTypeId() const { return "LayerBase"; }
 
+    virtual void setGeometry(hwc_layer_t* hwcl);
+
+    virtual void setPerFrameData(hwc_layer_t* hwcl);
+
     /**
      * draw - performs some global clipping optimizations
      * and calls onDraw().
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e5e87c6..e6bdfd1 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -53,6 +53,7 @@
 #include "SurfaceFlinger.h"
 
 #include "DisplayHardware/DisplayHardware.h"
+#include "DisplayHardware/HWComposer.h"
 
 /* ideally AID_GRAPHICS would be in a semi-public header
  * or there would be a way to map a user/group name to its id
@@ -78,12 +79,14 @@
         mReadFramebuffer("android.permission.READ_FRAME_BUFFER"),
         mDump("android.permission.DUMP"),
         mVisibleRegionsDirty(false),
+        mHwWorkListDirty(false),
         mDeferReleaseConsole(false),
         mFreezeDisplay(false),
         mFreezeCount(0),
         mFreezeDisplayTime(0),
         mDebugRegion(0),
         mDebugBackground(0),
+        mDebugDisableHWC(0),
         mDebugInSwapBuffers(0),
         mLastSwapBufferTime(0),
         mDebugInTransaction(0),
@@ -165,7 +168,7 @@
 {
     const nsecs_t now = systemTime();
     const nsecs_t duration = now - mBootTime;
-    LOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );  
+    LOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );
     mBootFinished = true;
     property_set("ctl.stop", "bootanim");
 }
@@ -201,10 +204,10 @@
     mServerHeap = new MemoryHeapBase(4096,
             MemoryHeapBase::READ_ONLY, "SurfaceFlinger read-only heap");
     LOGE_IF(mServerHeap==0, "can't create shared memory dealer");
-    
+
     mServerCblk = static_cast<surface_flinger_cblk_t*>(mServerHeap->getBase());
     LOGE_IF(mServerCblk==0, "can't get to shared control block's address");
-    
+
     new(mServerCblk) surface_flinger_cblk_t;
 
     // initialize primary screen
@@ -233,7 +236,7 @@
 
     // Initialize OpenGL|ES
     glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
-    glPixelStorei(GL_PACK_ALIGNMENT, 4); 
+    glPixelStorei(GL_PACK_ALIGNMENT, 4);
     glEnableClientState(GL_VERTEX_ARRAY);
     glEnable(GL_SCISSOR_TEST);
     glShadeModel(GL_FLAT);
@@ -267,7 +270,7 @@
 
     // start boot animation
     property_set("ctl.start", "bootanim");
-    
+
     return NO_ERROR;
 }
 
@@ -370,6 +373,11 @@
     // post surfaces (if needed)
     handlePageFlip();
 
+    if (UNLIKELY(mHwWorkListDirty)) {
+        // build the h/w work list
+        handleWorkList();
+    }
+
     const DisplayHardware& hw(graphicPlane(0).displayHardware());
     if (LIKELY(hw.canDraw() && !isFrozen())) {
         // repaint the framebuffer (if needed)
@@ -384,13 +392,12 @@
         logger.log(GraphicLog::SF_COMPOSITION_COMPLETE, index);
         hw.compositionComplete();
 
-        // release the clients before we flip ('cause flip might block)
-        logger.log(GraphicLog::SF_UNLOCK_CLIENTS, index);
-        unlockClients();
-
         logger.log(GraphicLog::SF_SWAP_BUFFERS, index);
         postFramebuffer();
 
+        logger.log(GraphicLog::SF_UNLOCK_CLIENTS, index);
+        unlockClients();
+
         logger.log(GraphicLog::SF_REPAINT_DONE, index);
     } else {
         // pretend we did the post
@@ -455,6 +462,7 @@
         handleTransactionLocked(transactionFlags, ditchedLayers);
         mLastTransactionTime = systemTime() - now;
         mDebugInTransaction = 0;
+        mHwWorkListDirty = true;
         // here the transaction has been committed
     }
 
@@ -462,6 +470,7 @@
      * Clean-up all layers that went away
      * (do this without the lock held)
      */
+
     const size_t count = ditchedLayers.size();
     for (size_t i=0 ; i<count ; i++) {
         if (ditchedLayers[i] != 0) {
@@ -665,7 +674,7 @@
 
         // Update aboveOpaqueLayers for next (lower) layer
         aboveOpaqueLayers.orSelf(opaqueRegion);
-        
+
         // Store the visible region is screen space
         layer->setVisibleRegion(visibleRegion);
         layer->setCoveredRegion(coveredRegion);
@@ -695,8 +704,8 @@
 void SurfaceFlinger::handlePageFlip()
 {
     bool visibleRegions = mVisibleRegionsDirty;
-    LayerVector& currentLayers = const_cast<LayerVector&>(
-            mDrawingState.layersSortedByZ);
+    LayerVector& currentLayers(
+            const_cast<LayerVector&>(mDrawingState.layersSortedByZ));
     visibleRegions |= lockPageFlip(currentLayers);
 
         const DisplayHardware& hw = graphicPlane(0).displayHardware();
@@ -719,6 +728,7 @@
 
             mWormholeRegion = screenRegion.subtract(opaqueRegion);
             mVisibleRegionsDirty = false;
+            mHwWorkListDirty = true;
         }
 
     unlockPageFlip(currentLayers);
@@ -749,6 +759,24 @@
     }
 }
 
+void SurfaceFlinger::handleWorkList()
+{
+    mHwWorkListDirty = false;
+    HWComposer& hwc(graphicPlane(0).displayHardware().getHwComposer());
+    if (hwc.initCheck() == NO_ERROR) {
+        const Vector< sp<LayerBase> >& currentLayers(mVisibleLayersSortedByZ);
+        const size_t count = currentLayers.size();
+        hwc.createWorkList(count);
+        hwc_layer_t* const cur(hwc.getLayers());
+        for (size_t i=0 ; cur && i<count ; i++) {
+            currentLayers[i]->setGeometry(&cur[i]);
+            if (mDebugDisableHWC) {
+                cur[i].compositionType = HWC_FRAMEBUFFER;
+                cur[i].flags |= HWC_SKIP_LAYER;
+            }
+        }
+    }
+}
 
 void SurfaceFlinger::handleRepaint()
 {
@@ -769,8 +797,8 @@
     glLoadIdentity();
 
     uint32_t flags = hw.getFlags();
-    if ((flags & DisplayHardware::SWAP_RECTANGLE) || 
-        (flags & DisplayHardware::BUFFER_PRESERVED)) 
+    if ((flags & DisplayHardware::SWAP_RECTANGLE) ||
+        (flags & DisplayHardware::BUFFER_PRESERVED))
     {
         // we can redraw only what's dirty, but since SWAP_RECTANGLE only
         // takes a rectangle, we must make sure to update that whole
@@ -813,9 +841,73 @@
         // draw something...
         drawWormhole();
     }
+
+    status_t err = NO_ERROR;
     const Vector< sp<LayerBase> >& layers(mVisibleLayersSortedByZ);
-    const size_t count = layers.size();
-    for (size_t i=0 ; i<count ; ++i) {
+    size_t count = layers.size();
+
+    const DisplayHardware& hw(graphicPlane(0).displayHardware());
+    HWComposer& hwc(hw.getHwComposer());
+    hwc_layer_t* const cur(hwc.getLayers());
+
+    LOGE_IF(cur && hwc.getNumLayers() != count,
+            "HAL number of layers (%d) doesn't match surfaceflinger (%d)",
+            hwc.getNumLayers(), count);
+
+    // just to be extra-safe, use the smallest count
+    if (hwc.initCheck() == NO_ERROR) {
+        count = count < hwc.getNumLayers() ? count : hwc.getNumLayers();
+    }
+
+    /*
+     *  update the per-frame h/w composer data for each layer
+     *  and build the transparent region of the FB
+     */
+    Region transparent;
+    if (cur) {
+        for (size_t i=0 ; i<count ; i++) {
+            const sp<LayerBase>& layer(layers[i]);
+            layer->setPerFrameData(&cur[i]);
+            if (cur[i].hints & HWC_HINT_CLEAR_FB) {
+                if (!(layer->needsBlending())) {
+                    transparent.orSelf(layer->visibleRegionScreen);
+                }
+            }
+        }
+        err = hwc.prepare();
+        LOGE_IF(err, "HWComposer::prepare failed (%s)", strerror(-err));
+    }
+
+    /*
+     *  clear the area of the FB that need to be transparent
+     */
+    transparent.andSelf(dirty);
+    if (!transparent.isEmpty()) {
+        glClearColor(0,0,0,0);
+        Region::const_iterator it = transparent.begin();
+        Region::const_iterator const end = transparent.end();
+        const int32_t height = hw.getHeight();
+        while (it != end) {
+            const Rect& r(*it++);
+            const GLint sy = height - (r.top + r.height());
+            glScissor(r.left, sy, r.width(), r.height());
+            glClear(GL_COLOR_BUFFER_BIT);
+        }
+    }
+
+
+    /*
+     * and then, render the layers targeted at the framebuffer
+     */
+    for (size_t i=0 ; i<count ; i++) {
+        if (cur) {
+            if ((cur[i].compositionType != HWC_FRAMEBUFFER) &&
+                !(cur[i].flags & HWC_SKIP_LAYER)) {
+                // skip layers handled by the HAL
+                continue;
+            }
+        }
+
         const sp<LayerBase>& layer(layers[i]);
         const Region clip(dirty.intersect(layer->visibleRegionScreen));
         if (!clip.isEmpty()) {
@@ -1054,7 +1146,7 @@
     if (android_atomic_dec(&mTransactionCount) == 1) {
         signalEvent();
 
-        // if there is a transaction with a resize, wait for it to 
+        // if there is a transaction with a resize, wait for it to
         // take effect before returning.
         Mutex::Autolock _l(mStateLock);
         while (mResizeTransationPending) {
@@ -1098,7 +1190,7 @@
     return NO_ERROR;
 }
 
-int SurfaceFlinger::setOrientation(DisplayID dpy, 
+int SurfaceFlinger::setOrientation(DisplayID dpy,
         int orientation, uint32_t flags)
 {
     if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
@@ -1131,14 +1223,17 @@
                 int(w), int(h));
         return surfaceHandle;
     }
-    
+
     //LOGD("createSurface for pid %d (%d x %d)", pid, w, h);
     sp<Layer> normalLayer;
     switch (flags & eFXSurfaceMask) {
         case eFXSurfaceNormal:
+#if HAS_PUSH_BUFFERS
             if (UNLIKELY(flags & ePushBuffers)) {
                 layer = createPushBuffersSurface(client, d, w, h, flags);
-            } else {
+            } else
+#endif
+            {
                 normalLayer = createNormalSurface(client, d, w, h, flags, format);
                 layer = normalLayer;
             }
@@ -1157,7 +1252,7 @@
         ssize_t token = addClientLayer(client, layer);
 
         surfaceHandle = layer->getSurface();
-        if (surfaceHandle != 0) { 
+        if (surfaceHandle != 0) {
             params->token = token;
             params->identity = surfaceHandle->getIdentity();
             params->width = w;
@@ -1241,7 +1336,7 @@
     /*
      * called by the window manager, when a surface should be marked for
      * destruction.
-     * 
+     *
      * The surface is removed from the current and drawing lists, but placed
      * in the purgatory queue, so it's not destroyed right-away (we need
      * to wait for all client's references to go away first).
@@ -1262,7 +1357,7 @@
 status_t SurfaceFlinger::destroySurface(const sp<LayerBaseClient>& layer)
 {
     // called by ~ISurface() when all references are gone
-    
+
     class MessageDestroySurface : public MessageBase {
         SurfaceFlinger* flinger;
         sp<LayerBaseClient> layer;
@@ -1275,9 +1370,9 @@
             layer.clear(); // clear it outside of the lock;
             Mutex::Autolock _l(flinger->mStateLock);
             /*
-             * remove the layer from the current list -- chances are that it's 
-             * not in the list anyway, because it should have been removed 
-             * already upon request of the client (eg: window manager). 
+             * remove the layer from the current list -- chances are that it's
+             * not in the list anyway, because it should have been removed
+             * already upon request of the client (eg: window manager).
              * However, a buggy client could have not done that.
              * Since we know we don't have any more clients, we don't need
              * to use the purgatory.
@@ -1392,7 +1487,7 @@
         }
         const bool locked(retry >= 0);
         if (!locked) {
-            snprintf(buffer, SIZE, 
+            snprintf(buffer, SIZE,
                     "SurfaceFlinger appears to be unresponsive, "
                     "dumping anyways (no locks held)\n");
             result.append(buffer);
@@ -1434,6 +1529,13 @@
             result.append(buffer);
         }
 
+        HWComposer& hwc(hw.getHwComposer());
+        snprintf(buffer, SIZE, "  h/w composer %s and %s\n",
+                hwc.initCheck()==NO_ERROR ? "present" : "not present",
+                mDebugDisableHWC ? "disabled" : "enabled");
+        result.append(buffer);
+        hwc.dump(result, buffer, SIZE);
+
         const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
         alloc.dump(result);
 
@@ -1507,6 +1609,11 @@
                 n = data.readInt32();
                 mDebugBackground = n ? 1 : 0;
                 return NO_ERROR;
+            case 1008:  // toggle use of hw composer
+                n = data.readInt32();
+                mDebugDisableHWC = n ? 1 : 0;
+                mHwWorkListDirty = true;
+                // fall-through...
             case 1004:{ // repaint everything
                 Mutex::Autolock _l(mStateLock);
                 const DisplayHardware& hw(graphicPlane(0).displayHardware());
@@ -1851,12 +1958,15 @@
 {
     int32_t name = NAME_NOT_FOUND;
     sp<Layer> layer(mFlinger->getLayer(sur));
-    if (layer == 0) return name;
+    if (layer == 0) {
+        return name;
+    }
 
     // if this layer already has a token, just return it
     name = layer->getToken();
-    if ((name >= 0) && (layer->getClient() == this))
+    if ((name >= 0) && (layer->getClient() == this)) {
         return name;
+    }
 
     name = 0;
     do {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index f0a167b..732e57e 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -303,6 +303,7 @@
             void        handlePageFlip();
             bool        lockPageFlip(const LayerVector& currentLayers);
             void        unlockPageFlip(const LayerVector& currentLayers);
+            void        handleWorkList();
             void        handleRepaint();
             void        postFramebuffer();
             void        composeSurfaces(const Region& dirty);
@@ -383,6 +384,7 @@
                 Region                      mInvalidRegion;
                 Region                      mWormholeRegion;
                 bool                        mVisibleRegionsDirty;
+                bool                        mHwWorkListDirty;
                 bool                        mDeferReleaseConsole;
                 bool                        mFreezeDisplay;
                 int32_t                     mFreezeCount;
@@ -393,6 +395,7 @@
                 // don't use a lock for these, we don't care
                 int                         mDebugRegion;
                 int                         mDebugBackground;
+                int                         mDebugDisableHWC;
                 volatile nsecs_t            mDebugInSwapBuffers;
                 nsecs_t                     mLastSwapBufferTime;
                 volatile nsecs_t            mDebugInTransaction;
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 ddfc520..e37733c 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,132 @@
         }
     }
 
+    /**
+     * 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.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
+            result = util.formatInOriginalFormat(pn, defaultCountryIso);
+        } catch (NumberParseException e) {
+        }
+        return result;
+    }
+
+    /**
+     * Format the phone number only if the given number hasn't been formatted.
+     * <p>
+     * The number which has only dailable character is treated as not being
+     * formatted.
+     *
+     * @param phoneNumber
+     *            the number to be formatted.
+     * @param phoneNumberE164
+     *            the E164 format number whose country code is used if the given
+     *            phoneNumber doesn't have the country code.
+     * @param defaultCountryIso
+     *            the ISO 3166-1 two letters country code whose convention will
+     *            be used if the phoneNumberE164 is null or invalid.
+     * @return the formatted number if the given number has been formatted,
+     *            otherwise, return the given number.
+     *
+     * @hide
+     */
+    public static String formatNumber(
+            String phoneNumber, String phoneNumberE164, String defaultCountryIso) {
+        int len = phoneNumber.length();
+        for (int i = 0; i < len; i++) {
+            if (!isDialable(phoneNumber.charAt(i))) {
+                return phoneNumber;
+            }
+        }
+        PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+        // Get the country code from phoneNumberE164
+        if (phoneNumberE164 != null && phoneNumberE164.length() >= 2
+                && phoneNumberE164.charAt(0) == '+') {
+            try {
+                PhoneNumber pn = util.parse(phoneNumberE164, defaultCountryIso);
+                String regionCode = util.getRegionCodeForNumber(pn);
+                if (!TextUtils.isEmpty(regionCode)) {
+                    defaultCountryIso = regionCode;
+                }
+            } catch (NumberParseException e) {
+            }
+        }
+        String result = formatNumber(phoneNumber, defaultCountryIso);
+        return result != null ? result : phoneNumber;
+    }
+
+    /**
+     * 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 ((i == 0 && c == '+') || 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 +1677,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 +1685,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;
@@ -1741,7 +1872,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) {
@@ -1761,7 +1892,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
@@ -1883,12 +2014,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,
@@ -1900,18 +2031,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
      */
@@ -2046,7 +2177,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..0746562 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;
@@ -54,6 +53,10 @@
     public static final int ENCODING_7BIT = 1;
     public static final int ENCODING_8BIT = 2;
     public static final int ENCODING_16BIT = 3;
+    /**
+     * @hide This value is not defined in global standard. Only in Korea, this is used.
+     */
+    public static final int ENCODING_KSC5601 = 4;
 
     /** The maximum number of payload bytes per message */
     public static final int MAX_USER_DATA_BYTES = 140;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 27e08d4..2370add 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -912,4 +912,25 @@
             return null;
         }
     }
+
+    /**
+     * @return true if the current device is "voice capable".
+     * <p>
+     * "Voice capable" means that this device supports circuit-switched
+     * (i.e. voice) phone calls over the telephony network, and is allowed
+     * to display the in-call UI while a cellular voice call is active.
+     * This will be false on "data only" devices which can't make voice
+     * calls and don't support any in-call UI.
+     * <p>
+     * Note: the meaning of this flag is subtly different from the
+     * PackageManager.FEATURE_TELEPHONY system feature, which is available
+     * on any device with a telephony radio, even if the device is
+     * data-only.
+     *
+     * @hide pending API review
+     */
+    public boolean isVoiceCapable() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_voice_capable);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index cf89848..6e13690 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -70,6 +70,7 @@
      */
     public String name;
     public String phoneNumber;
+    public String nomalizedNumber;
 
     public String cnapName;
     public int numberPresentation;
@@ -150,6 +151,12 @@
                     info.phoneNumber = cursor.getString(columnIndex);
                 }
 
+                // Look for the normalized number
+                columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
+                if (columnIndex != -1) {
+                    info.nomalizedNumber = cursor.getString(columnIndex);
+                }
+
                 // Look for the label/type combo
                 columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
                 if (columnIndex != -1) {
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index 25ca559..a967850 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.database.SQLException;
+import android.location.CountryDetector;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
@@ -232,7 +233,11 @@
 
                     // Use the number entered by the user for display.
                     if (!TextUtils.isEmpty(cw.number)) {
-                        mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number);
+                        CountryDetector detector = (CountryDetector) mQueryContext.getSystemService(
+                                Context.COUNTRY_DETECTOR);
+                        mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number,
+                                mCallerInfo.nomalizedNumber,
+                                detector.detectCountry().getCountryIso());
                     }
                 }
 
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..185d413 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -21,11 +21,20 @@
 import com.android.internal.util.HierarchicalState;
 import com.android.internal.util.HierarchicalStateMachine;
 
+import android.net.LinkAddress;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemProperties;
 import android.util.EventLog;
 
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
 /**
  * {@hide}
  *
@@ -60,7 +69,7 @@
  *        EVENT_GET_LAST_FAIL_DONE,
  *        EVENT_DEACTIVATE_DONE.
  *     }
- *   ++ # mInactiveState 
+ *   ++ # mInactiveState
  *        e(doNotifications)
  *        x(clearNotifications) {
  *            EVENT_RESET { notifiyDisconnectCompleted }.
@@ -255,10 +264,8 @@
     protected int mTag;
     protected PhoneBase phone;
     protected int cid;
-    protected String interfaceName;
-    protected String ipAddress;
-    protected String gatewayAddress;
-    protected String[] dnsServers;
+    protected LinkProperties mLinkProperties = new LinkProperties();
+    protected LinkCapabilities mCapabilities = new LinkCapabilities();
     protected long createTime;
     protected long lastFailTime;
     protected FailCause lastFailCause;
@@ -283,8 +290,6 @@
         if (DBG) log("DataConnection constructor E");
         this.phone = phone;
         this.cid = -1;
-        this.dnsServers = new String[2];
-
         clearSettings();
 
         setDbg(false);
@@ -377,11 +382,7 @@
         this.lastFailTime = -1;
         this.lastFailCause = FailCause.NONE;
 
-        interfaceName = null;
-        ipAddress = null;
-        gatewayAddress = null;
-        dnsServers[0] = null;
-        dnsServers[1] = null;
+        mLinkProperties = new LinkProperties();
     }
 
     /**
@@ -416,35 +417,67 @@
 //            for (int i = 0; i < response.length; i++) {
 //                log("  response[" + i + "]='" + response[i] + "'");
 //            }
+
+            // Start with clean network properties and if we have
+            // a failure we'll clear again at the bottom of this code.
+            LinkProperties linkProperties = new LinkProperties();
             if (response.length >= 2) {
                 cid = Integer.parseInt(response[0]);
-                interfaceName = response[1];
-                if (response.length > 2) {
-                    ipAddress = response[2];
+                String interfaceName = response[1];
+                result = SetupResult.SUCCESS;
+
+                try {
                     String prefix = "net." + interfaceName + ".";
-                    gatewayAddress = SystemProperties.get(prefix + "gw");
+
+                    NetworkInterface networkInterface = NetworkInterface.getByName(interfaceName);
+                    linkProperties.setInterfaceName(interfaceName);
+
+                    // TODO: Get gateway and dns via RIL interface not property?
+                    String gatewayAddress = SystemProperties.get(prefix + "gw");
+                    linkProperties.setGateway(InetAddress.getByName(gatewayAddress));
+
+                    for (InterfaceAddress addr : networkInterface.getInterfaceAddresses()) {
+                        linkProperties.addLinkAddress(new LinkAddress(addr));
+                    }
+                    // TODO: Get gateway and dns via RIL interface not property?
+                    String dnsServers[] = new String[2];
                     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;
+                        linkProperties.addDns(InetAddress.getByName(dnsServers[0]));
+                        linkProperties.addDns(InetAddress.getByName(dnsServers[1]));
                     } else {
                         result = SetupResult.ERR_BadDns;
                     }
-                } else {
-                    result = SetupResult.SUCCESS;
+                } catch (UnknownHostException e1) {
+                    log("onSetupCompleted: UnknowHostException " + e1);
+                    e1.printStackTrace();
+                    result = SetupResult.ERR_Other;
+                } catch (SocketException e2) {
+                    log("onSetupCompleted: SocketException " + e2);
+                    e2.printStackTrace();
+                    result = SetupResult.ERR_Other;
                 }
             } else {
+                log("onSetupCompleted: error; expected number of responses >= 2 was " +
+                        response.length);
                 result = SetupResult.ERR_Other;
             }
+
+            // An error occurred so clear properties
+            if (result != SetupResult.SUCCESS) {
+                log("onSetupCompleted with an error clearing LinkProperties");
+                linkProperties.clear();
+            }
+            mLinkProperties = linkProperties;
         }
 
-        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("LinkProperties: " + mLinkProperties.toString());
+            }
+        }
         return result;
     }
 
@@ -606,7 +639,16 @@
                             break;
                         case ERR_BadDns:
                             // Connection succeeded but DNS info is bad so disconnect
-                            EventLog.writeEvent(EventLogTags.PDP_BAD_DNS_ADDRESS, dnsServers[0]);
+                            StringBuilder dnsAddressesSb = new StringBuilder();
+                            for (InetAddress addr : mLinkProperties.getDnses()) {
+                                if (dnsAddressesSb.length() != 0) dnsAddressesSb.append(" ");
+                                dnsAddressesSb.append(addr.toString());
+                            }
+                            if (dnsAddressesSb.length() == 0) {
+                                dnsAddressesSb.append("no-dns-addresses");
+                            }
+                            EventLog.writeEvent(EventLogTags.PDP_BAD_DNS_ADDRESS,
+                                    dnsAddressesSb.toString());
                             tearDownData(cp);
                             transitionTo(mDisconnectingBadDnsState);
                             break;
@@ -873,31 +915,22 @@
     }
 
     /**
-     * @return the interface name as a string.
+     * Return the LinkProperties for the connection.
+     *
+     * @return a copy of the LinkProperties, is never null.
      */
-    public String getInterface() {
-        return interfaceName;
+    public LinkProperties getLinkProperties() {
+        return new LinkProperties(mLinkProperties);
     }
 
     /**
-     * @return the ip address as a string.
+     * A capability is an Integer/String pair, the capabilities
+     * are defined in the class LinkSocket#Key.
+     *
+     * @return a copy of this connections capabilities, may be empty but never null.
      */
-    public String getIpAddress() {
-        return ipAddress;
-    }
-
-    /**
-     * @return the gateway address as a string.
-     */
-    public String getGatewayAddress() {
-        return gatewayAddress;
-    }
-
-    /**
-     * @return an array of associated DNS addresses.
-     */
-    public String[] getDnsServers() {
-        return dnsServers;
+    public LinkCapabilities getLinkCapabilities() {
+        return new LinkCapabilities(mCapabilities);
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 265bf7e..52839be 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -17,10 +17,11 @@
 package com.android.internal.telephony;
 
 import android.app.PendingIntent;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
-import android.os.RemoteException;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
@@ -28,6 +29,7 @@
 
 import java.util.ArrayList;
 
+
 /**
  * {@hide}
  *
@@ -188,7 +190,16 @@
     /** CID of active data connection */
     protected int cidActive;
 
-   /**
+    /** indication of our availability (preconditions to trysetupData are met) **/
+    protected boolean mAvailability = false;
+
+    /** The link properties (dns, gateway, ip, etc) */
+    protected LinkProperties mLinkProperties = new LinkProperties();
+
+    /** The link capabilities */
+    protected LinkCapabilities mLinkCapabilities = new LinkCapabilities();
+
+    /**
      * Default constructor
      */
     protected DataConnectionTracker(PhoneBase phone) {
@@ -411,17 +422,125 @@
 
     public abstract ArrayList<DataConnection> getAllDataConnections();
 
-    protected abstract String getInterfaceName(String apnType);
-
-    protected abstract String getIpAddress(String apnType);
-
-    protected abstract String getGateway(String apnType);
-
-    protected abstract String[] getDnsServers(String apnType);
-
     protected abstract void setState(State s);
 
-    protected synchronized boolean isEnabled(int id) {
+    protected LinkProperties getLinkProperties(String apnType) {
+        int id = apnTypeToId(apnType);
+        if (isApnIdEnabled(id)) {
+            return new LinkProperties(mLinkProperties);
+        } else {
+            return new LinkProperties();
+        }
+    }
+
+    protected LinkCapabilities getLinkCapabilities(String apnType) {
+        int id = apnTypeToId(apnType);
+        if (isApnIdEnabled(id)) {
+            return new LinkCapabilities(mLinkCapabilities);
+        } else {
+            return new LinkCapabilities();
+        }
+    }
+
+    /**
+     * Return the LinkProperties for the connection.
+     *
+     * @param connection
+     * @return a copy of the LinkProperties, is never null.
+     */
+    protected LinkProperties getLinkProperties(DataConnection connection) {
+        return connection.getLinkProperties();
+    }
+
+    /**
+     * A capability is an Integer/String pair, the capabilities
+     * are defined in the class LinkSocket#Key.
+     *
+     * @param connection
+     * @return a copy of this connections capabilities, may be empty but never null.
+     */
+    protected LinkCapabilities getLinkCapabilities(DataConnection connection) {
+        return connection.getLinkCapabilities();
+    }
+
+    // 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 +563,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 +596,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 +613,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 +626,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 +638,8 @@
             if (!isApnTypeActive(type)) {
                 mRequestedApnType = type;
                 onEnableNewApn();
+            } else {
+                notifyApnIdUpToCurrent(Phone.REASON_APN_SWITCHED, apnId);
             }
         } else {
             // disable
@@ -528,8 +648,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 +703,7 @@
                 onTrySetupData(Phone.REASON_DATA_ENABLED);
             } else {
                 onCleanUpConnection(true, Phone.REASON_DATA_DISABLED);
-           }
+            }
         }
     }
-
-
 }
diff --git a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index 057ba0a..6a163dd 100644
--- a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -92,26 +94,45 @@
         }
     }
 
-    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();
+        LinkProperties linkProperties = null;
+        LinkCapabilities linkCapabilities = null;
+        if (state == Phone.DataState.CONNECTED) {
+            linkProperties = sender.getLinkProperties(apnType);
+            linkCapabilities = sender.getLinkCapabilities(apnType);
+        }
         try {
             mRegistry.notifyDataConnection(
-                    convertDataState(sender.getDataConnectionState()),
+                    convertDataState(state),
                     sender.isDataConnectivityPossible(), reason,
                     sender.getActiveApn(),
-                    sender.getActiveApnTypes(),
-                    sender.getInterfaceName(null),
+                    apnType,
+                    linkProperties,
+                    linkCapabilities,
                     ((telephony!=null) ? telephony.getNetworkType() :
-                    TelephonyManager.NETWORK_TYPE_UNKNOWN),
-                    sender.getGateway(null));
+                    TelephonyManager.NETWORK_TYPE_UNKNOWN));
         } catch (RemoteException ex) {
             // system process is dead
         }
     }
 
-    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 30ee77c..e42827f 100644
--- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
@@ -16,11 +16,14 @@
 
 package com.android.internal.telephony;
 
-import android.telephony.SmsMessage;
+import android.text.TextUtils;
 import android.util.SparseIntArray;
 
 import android.util.Log;
 
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
 /**
  * This class implements the character set mapping between
  * the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1
@@ -320,7 +323,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);
@@ -355,6 +358,32 @@
      */
     public static String
     gsm8BitUnpackedToString(byte[] data, int offset, int length) {
+        return gsm8BitUnpackedToString(data, offset, length, "");
+    }
+
+    /**
+     * Convert a GSM alphabet string that's stored in 8-bit unpacked
+     * format (as it often appears in SIM records) into a String
+     *
+     * Field may be padded with trailing 0xff's. The decode stops
+     * at the first 0xff encountered.
+     *
+     * Additionally, in some country(ex. Korea), there are non-ASCII or MBCS characters.
+     * If a character set is given, characters in data are treat as MBCS.
+     */
+    public static String
+    gsm8BitUnpackedToString(byte[] data, int offset, int length, String characterset) {
+        boolean isMbcs = false;
+        Charset charset = null;
+        ByteBuffer mbcsBuffer = null;
+
+        if (!TextUtils.isEmpty(characterset)
+                && !characterset.equalsIgnoreCase("us-ascii")
+                && Charset.isSupported(characterset)) {
+            isMbcs = true;
+            charset = Charset.forName(characterset);
+            mbcsBuffer = ByteBuffer.allocate(2);
+        }
         boolean prevWasEscape;
         StringBuilder ret = new StringBuilder(length);
 
@@ -380,7 +409,15 @@
                 if (prevWasEscape) {
                     ret.append((char)gsmExtendedToChar.get(c, ' '));
                 } else {
-                    ret.append((char)gsmToChar.get(c, ' '));
+                    if (!isMbcs || c < 0x80 || i + 1 >= offset + length) {
+                        ret.append((char)gsmToChar.get(c, ' '));
+                    } else {
+                        // isMbcs must be true. So both mbcsBuffer and charset are initialized.
+                        mbcsBuffer.clear();
+                        mbcsBuffer.put(data, i++, 2);
+                        mbcsBuffer.flip();
+                        ret.append(charset.decode(mbcsBuffer).toString());
+                    }
                 }
                 prevWasEscape = false;
             }
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 87e4b52..6407a4e 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -17,6 +17,8 @@
 package com.android.internal.telephony;
 
 import android.content.Intent;
+import android.net.LinkProperties;
+import android.net.LinkCapabilities;
 import android.os.Bundle;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -32,8 +34,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,
-            String gateway);
-    void notifyDataConnectionFailed(String reason);
+            String reason, String apn, String apnType, in LinkProperties linkProperties,
+            in LinkCapabilities linkCapabilities, int networkType);
+    void notifyDataConnectionFailed(String reason, String apnType);
     void notifyCellLocation(in Bundle cellLocation);
 }
diff --git a/telephony/java/com/android/internal/telephony/IccUtils.java b/telephony/java/com/android/internal/telephony/IccUtils.java
index 957eddd..df579b0 100644
--- a/telephony/java/com/android/internal/telephony/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/IccUtils.java
@@ -16,13 +16,16 @@
 
 package com.android.internal.telephony;
 
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.util.Log;
 
 import com.android.internal.telephony.GsmAlphabet;
-
 import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
 
 /**
  * Various methods, useful for dealing with SIM data.
@@ -150,6 +153,9 @@
      */
     public static String
     adnStringFieldToString(byte[] data, int offset, int length) {
+        if (length == 0) {
+            return "";
+        }
         if (length >= 1) {
             if (data[offset] == (byte) 0x80) {
                 int ucslen = (length - 1) / 2;
@@ -225,7 +231,15 @@
             return ret.toString();
         }
 
-        return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length);
+        Resources resource = Resources.getSystem();
+        String defaultCharset = "";
+        try {
+            defaultCharset =
+                    resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset);
+        } catch (NotFoundException e) {
+            // Ignore Exception and defaultCharset is set to a empty string.
+        }
+        return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
     }
 
     static int
@@ -267,9 +281,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..9b0aa3c 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,"ko",13));	//Korea (Republic of)
+		table.add(new MccEntry(452,"vn",2));	//Viet Nam (Socialist Republic of)
+		table.add(new MccEntry(454,"hk",2));	//"Hong Kong, China"
+		table.add(new MccEntry(455,"mo",2));	//"Macao, China"
+		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 e426e94..2957c7e 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -17,10 +17,10 @@
 package com.android.internal.telephony;
 
 import android.content.Context;
-import android.content.SharedPreferences;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
 import android.os.Handler;
 import android.os.Message;
-import android.preference.PreferenceManager;
 import android.telephony.CellLocation;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
@@ -28,7 +28,6 @@
 
 import com.android.internal.telephony.DataConnection;
 import com.android.internal.telephony.gsm.NetworkInfo;
-import com.android.internal.telephony.gsm.GsmDataConnection;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 
 import java.util.List;
@@ -99,11 +98,12 @@
     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_LINK_PROPERTIES_KEY = "linkProperties";
+    static final String DATA_LINK_CAPABILITIES_KEY = "linkCapabilities";
 
     static final String DATA_IFACE_NAME_KEY = "iface";
-    static final String DATA_GATEWAY_KEY = "gateway";
     static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
     static final String PHONE_IN_ECM_STATE = "phoneinECMState";
 
@@ -243,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.
@@ -313,6 +321,16 @@
     String getActiveApn();
 
     /**
+     * Return the LinkProperties for the named apn or null if not available
+     */
+    LinkProperties getLinkProperties(String apnType);
+
+    /**
+     * Return the LinkCapabilities
+     */
+    LinkCapabilities getLinkCapabilities(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).
@@ -1373,29 +1391,6 @@
     boolean isDataConnectivityPossible();
 
     /**
-     * Returns the name of the network interface used by the specified APN type.
-     */
-    String getInterfaceName(String apnType);
-
-    /**
-     * Returns the IP address of the network interface used by the specified
-     * APN type.
-     */
-    String getIpAddress(String apnType);
-
-    /**
-     * Returns the gateway for the network interface used by the specified APN
-     * type.
-     */
-    String getGateway(String apnType);
-
-    /**
-     * Returns the DNS servers for the network interface used by the specified
-     * APN type.
-     */
-    public String[] getDnsServers(String apnType);
-
-    /**
      * Retrieves the unique device ID, e.g., IMEI for GSM phones and MEID for CDMA phones.
      */
     String getDeviceId();
@@ -1540,6 +1535,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 1674ad6..74e8c1b 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.SharedPreferences;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
 import android.net.wifi.WifiManager;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -35,10 +37,8 @@
 import android.util.Log;
 
 import com.android.internal.R;
-import com.android.internal.telephony.gsm.GsmDataConnection;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 
-import java.util.List;
 import java.util.Locale;
 
 
@@ -741,8 +741,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();
@@ -828,9 +833,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;
     }
 
@@ -920,30 +935,22 @@
          logUnexpectedCdmaMethodCall("unsetOnEcbModeExitResponse");
      }
 
-    public String getInterfaceName(String apnType) {
-        return mDataConnection.getInterfaceName(apnType);
-    }
-
-    public String getIpAddress(String apnType) {
-        return mDataConnection.getIpAddress(apnType);
-    }
-
     public boolean isDataConnectivityEnabled() {
         return mDataConnection.getDataEnabled();
     }
 
-    public String getGateway(String apnType) {
-        return mDataConnection.getGateway(apnType);
-    }
-
-    public String[] getDnsServers(String apnType) {
-        return mDataConnection.getDnsServers(apnType);
-    }
-
     public String[] getActiveApnTypes() {
         return mDataConnection.getActiveApnTypes();
     }
 
+    public LinkProperties getLinkProperties(String apnType) {
+        return mDataConnection.getLinkProperties(apnType);
+    }
+
+    public LinkCapabilities getLinkCapabilities(String apnType) {
+        return mDataConnection.getLinkCapabilities(apnType);
+    }
+
     public String getActiveApn() {
         return mDataConnection.getActiveApnString();
     }
@@ -956,6 +963,10 @@
         return mDataConnection.disableApnType(type);
     }
 
+    public boolean isDataConnectivityPossible() {
+        return ((mDataConnection != null) && (mDataConnection.isDataPossible()));
+    }
+
     /**
      * simulateDataConnection
      *
@@ -984,7 +995,7 @@
         }
 
         mDataConnection.setState(dcState);
-        notifyDataConnection(null);
+        notifyDataConnection(null, Phone.APN_TYPE_DEFAULT);
     }
 
     /**
@@ -1038,6 +1049,10 @@
                 "called, CDMAPhone inactive.");
     }
 
+    public DataState getDataConnectionState() {
+        return getDataConnectionState(APN_TYPE_DEFAULT);
+    }
+
     /**
      * Common error logger method for unexpected calls to GSM/WCDMA-only methods.
      */
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 77f1e6c..02fdf28 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -20,13 +20,12 @@
 import android.app.ActivityManagerNative;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemProperties;
-import android.preference.PreferenceManager;
 import android.telephony.CellLocation;
-import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.util.Log;
@@ -34,7 +33,6 @@
 import com.android.internal.telephony.cdma.CDMAPhone;
 import com.android.internal.telephony.gsm.GSMPhone;
 import com.android.internal.telephony.gsm.NetworkInfo;
-import com.android.internal.telephony.gsm.GsmDataConnection;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 
 import java.util.List;
@@ -172,7 +170,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 +209,14 @@
         return mActivePhone.getActiveApnTypes();
     }
 
+    public LinkProperties getLinkProperties(String apnType) {
+        return mActivePhone.getLinkProperties(apnType);
+    }
+
+    public LinkCapabilities getLinkCapabilities(String apnType) {
+        return mActivePhone.getLinkCapabilities(apnType);
+    }
+
     public String getActiveApn() {
         return mActivePhone.getActiveApn();
     }
@@ -664,22 +674,6 @@
         return mActivePhone.isDataConnectivityPossible();
     }
 
-    public String getInterfaceName(String apnType) {
-        return mActivePhone.getInterfaceName(apnType);
-    }
-
-    public String getIpAddress(String apnType) {
-        return mActivePhone.getIpAddress(apnType);
-    }
-
-    public String getGateway(String apnType) {
-        return mActivePhone.getGateway(apnType);
-    }
-
-    public String[] getDnsServers(String apnType) {
-        return mActivePhone.getDnsServers(apnType);
-    }
-
     public String getDeviceId() {
         return mActivePhone.getDeviceId();
     }
@@ -768,6 +762,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..8c2280b 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);
     }
@@ -1299,10 +1298,18 @@
     public void
     setupDataCall(String radioTechnology, String profile, String apn,
             String user, String password, String authType, Message result) {
+        setupDataCallWithProtocol(radioTechnology, profile, apn, user, password,
+                              authType, "IP", result);
+    }
+
+    public void
+    setupDataCallWithProtocol(String radioTechnology, String profile,
+            String apn, String user, String password, String authType,
+            String protocolType, Message result) {
         RILRequest rr
                 = RILRequest.obtain(RIL_REQUEST_SETUP_DATA_CALL, result);
 
-        rr.mp.writeInt(6);
+        rr.mp.writeInt(7);
 
         rr.mp.writeString(radioTechnology);
         rr.mp.writeString(profile);
@@ -1310,11 +1317,12 @@
         rr.mp.writeString(user);
         rr.mp.writeString(password);
         rr.mp.writeString(authType);
+        rr.mp.writeString(protocolType);
 
         if (RILJ_LOGD) riljLog(rr.serialString() + "> "
                 + requestToString(rr.mRequest) + " " + radioTechnology + " "
                 + profile + " " + apn + " " + user + " "
-                + password + " " + authType);
+                + password + " " + authType + " " + protocolType);
 
         send(rr);
     }
@@ -2902,7 +2910,11 @@
             dataCall.active = p.readInt();
             dataCall.type = p.readString();
             dataCall.apn = p.readString();
-            dataCall.address = p.readString();
+            String address = p.readString();
+            if (address != null) {
+                address = address.split(" ")[0];
+            }
+            dataCall.address = address;
 
             response.add(dataCall);
         }
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index 917e1d8..3a7ce47 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -255,6 +255,7 @@
         mCm.unregisterForOn(this);
     }
 
+    @Override
     protected void finalize() {
         Log.d(TAG, "SMSDispatcher finalized");
     }
@@ -927,14 +928,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;
@@ -943,7 +944,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 f31bf24..6e53ec5 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -515,14 +515,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.
@@ -613,7 +605,7 @@
         }
     }
 
-    public DataState getDataConnectionState() {
+    public DataState getDataConnectionState(String apnType) {
         DataState ret = DataState.DISCONNECTED;
 
         if (mSST == null) {
@@ -625,6 +617,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:
@@ -878,26 +872,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()) {
@@ -1191,6 +1165,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..e499e95 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -23,14 +23,12 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.TrafficStats;
 import android.net.wifi.WifiManager;
 import android.os.AsyncResult;
 import android.os.Message;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -50,8 +48,6 @@
 import com.android.internal.telephony.EventLogTags;
 import com.android.internal.telephony.gsm.ApnSetting;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.RetryManager;
-import com.android.internal.telephony.ServiceStateTracker;
 
 import java.util.ArrayList;
 
@@ -305,9 +301,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 +344,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 +355,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 +387,7 @@
         }
 
         setState(State.DISCONNECTING);
+        notifyDataAvailability(reason);
 
         boolean notificationDeferred = false;
         for (DataConnection conn : dataConnectionList) {
@@ -440,13 +446,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 +628,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 +694,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 +732,10 @@
         }
 
         if (ar.exception == null) {
+            // TODO: We should clear LinkProperties/Capabilities when torn down or disconnected
+            mLinkProperties = getLinkProperties(mActiveDataConnection);
+            mLinkCapabilities = getLinkCapabilities(mActiveDataConnection);
+
             // everything is setup
             notifyDefaultData(reason);
         } else {
@@ -762,7 +775,7 @@
             onRestartRadio();
         }
 
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         mActiveApn = null;
         if (retryAfterDisconnected(reason)) {
           trySetupData(reason);
@@ -789,7 +802,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 +814,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 +853,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);
@@ -949,34 +964,6 @@
         }
     }
 
-    protected String getInterfaceName(String apnType) {
-        if (mActiveDataConnection != null) {
-            return mActiveDataConnection.getInterface();
-        }
-        return null;
-    }
-
-    protected String getIpAddress(String apnType) {
-        if (mActiveDataConnection != null) {
-            return mActiveDataConnection.getIpAddress();
-        }
-        return null;
-    }
-
-    protected String getGateway(String apnType) {
-        if (mActiveDataConnection != null) {
-            return mActiveDataConnection.getGatewayAddress();
-        }
-        return null;
-    }
-
-    protected String[] getDnsServers(String apnType) {
-        if (mActiveDataConnection != null) {
-            return mActiveDataConnection.getDnsServers();
-        }
-        return null;
-    }
-
     public ArrayList<DataConnection> getAllDataConnections() {
         return dataConnectionList;
     }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index dceff2a..37a33bc 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -70,6 +70,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!");
     }
@@ -92,6 +93,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected int dispatchMessage(SmsMessageBase smsb) {
 
         // If sms is null, means there was a parsing error.
@@ -335,6 +337,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendData(String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -343,6 +346,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendText(String destAddr, String scAddr, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -351,6 +355,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents) {
@@ -359,7 +364,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;
@@ -432,8 +437,9 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendSms(SmsTracker tracker) {
-        HashMap map = tracker.mData;
+        HashMap<String, Object> map = tracker.mData;
 
         // byte smsc[] = (byte[]) map.get("smsc");  // unused for CDMA
         byte pdu[] = (byte[]) map.get("pdu");
@@ -444,11 +450,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
 
@@ -469,16 +477,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 d2a4bd8..7d2013b 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -16,6 +16,16 @@
 
 package com.android.internal.telephony.cdma;
 
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.DataConnectionTracker;
+import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+
 import android.app.AlarmManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -38,19 +48,8 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
-import android.util.Config;
 import android.util.TimeUtils;
 
-import com.android.internal.telephony.CommandException;
-import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.DataConnectionTracker;
-import com.android.internal.telephony.EventLogTags;
-import com.android.internal.telephony.IccCard;
-import com.android.internal.telephony.MccTable;
-import com.android.internal.telephony.ServiceStateTracker;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
-
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -1008,7 +1007,7 @@
         mNeedFixZone = false;
 
         if (zone != null) {
-            if (getAutoTime()) {
+            if (getAutoTimeZone()) {
                 setAndBroadcastNetworkSetTimeZone(zone.getID());
             }
             saveNitzTimeZone(zone.getID());
@@ -1140,7 +1139,7 @@
         }
 
         if (hasCdmaDataConnectionChanged || hasNetworkTypeChanged) {
-            phone.notifyDataConnection(null);
+            phone.notifyDataConnection(null, null);
         }
 
         if (hasRoamingOn) {
@@ -1460,7 +1459,7 @@
             }
 
             if (zone != null) {
-                if (getAutoTime()) {
+                if (getAutoTimeZone()) {
                     setAndBroadcastNetworkSetTimeZone(zone.getID());
                 }
                 saveNitzTimeZone(zone.getID());
@@ -1550,6 +1549,14 @@
         }
     }
 
+    private boolean getAutoTimeZone() {
+        try {
+            return Settings.System.getInt(cr, Settings.System.AUTO_TIME_ZONE) > 0;
+        } catch (SettingNotFoundException snfe) {
+            return true;
+        }
+    }
+
     private void saveNitzTimeZone(String zoneId) {
         mSavedTimeZone = zoneId;
     }
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
index e97549d..d84b6ab 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
@@ -68,8 +68,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 d563dc8..8accfd1 100644
--- 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;
@@ -34,7 +31,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;
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 ab79fe9..cf06dab 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;
@@ -697,7 +694,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 {
@@ -805,6 +802,7 @@
      * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
      *
      * @param bData an instance of BearerData.
+     *
      * @return byte array of raw encoded SMS bearer data.
      */
     public static byte[] encode(BearerData bData) {
@@ -915,7 +913,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 {
@@ -961,7 +959,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;
@@ -1554,7 +1552,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 4f00163..4a60231 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -19,7 +19,7 @@
 
 import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
 
-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 43971ff..30a6324 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -287,7 +287,7 @@
         return mPendingMMIs;
     }
 
-    public DataState getDataConnectionState() {
+    public DataState getDataConnectionState(String apnType) {
         DataState ret = DataState.DISCONNECTED;
 
         if (mSST == null) {
@@ -300,6 +300,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:
@@ -401,8 +403,8 @@
     }
 
     /*package*/ void
-    notifyDataConnectionFailed(String reason) {
-        mNotifier.notifyDataConnectionFailed(this, reason);
+    notifyDataConnectionFailed(String reason, String apnType) {
+        mNotifier.notifyDataConnectionFailed(this, reason, apnType);
     }
 
     /*package*/ void
@@ -1091,27 +1093,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/GsmDataConnection.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
index 09d46dd..1572f09 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
@@ -180,8 +180,8 @@
 
     @Override
     protected boolean isDnsOk(String[] domainNameServers) {
-        if (NULL_IP.equals(dnsServers[0]) && NULL_IP.equals(dnsServers[1])
-                    && !((GSMPhone) phone).isDnsCheckDisabled()) {
+        if (NULL_IP.equals(domainNameServers[0]) && NULL_IP.equals(domainNameServers[1])
+                && !((GSMPhone) phone).isDnsCheckDisabled()) {
             // Work around a race condition where QMI does not fill in DNS:
             // Deactivate PDP and let DataConnectionTracker retry.
             // Do not apply the race condition workaround for MMS APN
@@ -189,6 +189,9 @@
             // Otherwise, the default APN will not be restored anymore.
             if (!apn.types[0].equals(Phone.APN_TYPE_MMS)
                 || !isIpAddress(apn.mmsProxy)) {
+                log(String.format(
+                        "isDnsOk: return false apn.types[0]=%s APN_TYPE_MMS=%s isIpAddress(%s)=%s",
+                        apn.types[0], Phone.APN_TYPE_MMS, apn.mmsProxy, isIpAddress(apn.mmsProxy)));
                 return false;
             }
         }
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index e7d57bc..66da6e8 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -27,15 +27,14 @@
 import android.content.SharedPreferences;
 import android.database.ContentObserver;
 import android.database.Cursor;
-import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.NetworkInfo;
+import android.net.ProxyProperties;
 import android.net.TrafficStats;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.os.AsyncResult;
 import android.os.Message;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -58,6 +57,9 @@
 import com.android.internal.telephony.DataConnection.FailCause;
 
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 
 /**
@@ -388,12 +390,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 +401,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 +417,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 +455,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 +464,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 +482,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,46 +605,10 @@
         pdp.connect(msg, apn);
 
         setState(State.INITING);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         return true;
     }
 
-    protected String getInterfaceName(String apnType) {
-        if (mActivePdp != null &&
-                (apnType == null ||
-                (mActiveApn != null && mActiveApn.canHandleType(apnType)))) {
-            return mActivePdp.getInterface();
-        }
-        return null;
-    }
-
-    protected String getIpAddress(String apnType) {
-        if (mActivePdp != null &&
-                (apnType == null ||
-                (mActiveApn != null && mActiveApn.canHandleType(apnType)))) {
-            return mActivePdp.getIpAddress();
-        }
-        return null;
-    }
-
-    public String getGateway(String apnType) {
-        if (mActivePdp != null &&
-                (apnType == null ||
-                (mActiveApn != null && mActiveApn.canHandleType(apnType)))) {
-            return mActivePdp.getGatewayAddress();
-        }
-        return null;
-    }
-
-    protected String[] getDnsServers(String apnType) {
-        if (mActivePdp != null &&
-                (apnType == null ||
-                (mActiveApn != null && mActiveApn.canHandleType(apnType)))) {
-            return mActivePdp.getDnsServers();
-        }
-        return null;
-    }
-
     private boolean
     pdpStatesHasCID (ArrayList<DataCallState> states, int cid) {
         for (int i = 0, s = states.size() ; i < s ; i++) {
@@ -746,7 +720,7 @@
 
     private void notifyDefaultData(String reason) {
         setState(State.CONNECTED);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         startNetStatPoll();
         // reset reconnect timer
         mRetryMgr.resetRetryCount();
@@ -756,7 +730,7 @@
     private void gotoIdleAndNotifyDataConnection(String reason) {
         if (DBG) log("gotoIdleAndNotifyDataConnection: reason=" + reason);
         setState(State.IDLE);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         mActiveApn = null;
     }
 
@@ -998,7 +972,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 +1065,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 +1099,27 @@
         }
 
         if (ar.exception == null) {
+            // TODO: We should clear LinkProperties/Capabilities when torn down or disconnected
+            mLinkProperties = getLinkProperties(mActivePdp);
+            mLinkCapabilities = getLinkCapabilities(mActivePdp);
+
+            ApnSetting apn = mActivePdp.getApn();
+            if (apn.proxy != null && apn.proxy.length() != 0) {
+                try {
+                    ProxyProperties proxy = new ProxyProperties();
+                    proxy.setSocketAddress(new InetSocketAddress(InetAddress.getByName(apn.proxy),
+                            Integer.parseInt(apn.port)));
+                    mLinkProperties.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");
@@ -1157,7 +1152,7 @@
             // No try for permanent failure
             if (cause.isPermanentFail()) {
                 notifyNoData(cause);
-                phone.notifyDataConnection(Phone.REASON_APN_FAILED);
+                notifyDataConnection(Phone.REASON_APN_FAILED);
                 onEnableApn(apnTypeToId(mRequestedApnType), DISABLED);
                 return;
             }
@@ -1186,7 +1181,7 @@
            reason = (String) ar.userObj;
         }
         setState(State.IDLE);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         mActiveApn = null;
         if (retryAfterDisconnected(reason)) {
             trySetupData(reason);
@@ -1217,7 +1212,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);
         }
     }
 
@@ -1225,7 +1220,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 3079a64..ed7066b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -56,6 +56,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);
@@ -84,6 +85,7 @@
 
 
     /** {@inheritDoc} */
+    @Override
     protected int dispatchMessage(SmsMessageBase smsb) {
 
         // If sms is null, means there was a parsing error.
@@ -151,6 +153,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendData(String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -159,6 +162,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendText(String destAddr, String scAddr, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -167,6 +171,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendMultipartText(String destinationAddress, String scAddress,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents) {
@@ -306,8 +311,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");
@@ -322,12 +328,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");
@@ -342,6 +349,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
         // FIXME unit test leaves cm == null. this should change
         if (mCm != null) {
@@ -350,6 +358,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.");
@@ -357,6 +366,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.");
@@ -364,6 +374,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..83ad552 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -16,6 +16,17 @@
 
 package com.android.internal.telephony.gsm;
 
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.DataConnectionTracker;
+import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+
 import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -47,17 +58,6 @@
 import android.util.Log;
 import android.util.TimeUtils;
 
-import com.android.internal.telephony.CommandException;
-import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.DataConnectionTracker;
-import com.android.internal.telephony.EventLogTags;
-import com.android.internal.telephony.IccCard;
-import com.android.internal.telephony.MccTable;
-import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.ServiceStateTracker;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
-
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -995,7 +995,7 @@
                     mNeedFixZone = false;
 
                     if (zone != null) {
-                        if (getAutoTime()) {
+                        if (getAutoTimeZone()) {
                             setAndBroadcastNetworkSetTimeZone(zone.getID());
                         }
                         saveNitzTimeZone(zone.getID());
@@ -1018,7 +1018,8 @@
         }
 
         if (hasNetworkTypeChanged) {
-            phone.notifyDataConnection(null);
+            // TODO - do we really want this?
+            phone.notifyDataConnection(null, null);
         }
 
         if (hasRoamingOn) {
@@ -1475,7 +1476,7 @@
             }
 
             if (zone != null) {
-                if (getAutoTime()) {
+                if (getAutoTimeZone()) {
                     setAndBroadcastNetworkSetTimeZone(zone.getID());
                 }
                 saveNitzTimeZone(zone.getID());
@@ -1545,6 +1546,15 @@
         }
     }
 
+    private boolean getAutoTimeZone() {
+        try {
+            return Settings.System.getInt(phone.getContext().getContentResolver(),
+                    Settings.System.AUTO_TIME_ZONE) > 0;
+        } catch (SettingNotFoundException snfe) {
+            return true;
+        }
+    }
+
     private void saveNitzTimeZone(String zoneId) {
         mSavedTimeZone = zoneId;
     }
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
index 67ecc77..aab359f 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -87,6 +87,7 @@
     public void dispose() {
     }
 
+    @Override
     protected void finalize() {
         try {
             super.finalize();
@@ -192,6 +193,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 9a3c476..e24613f 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;
@@ -34,6 +33,7 @@
 import static android.telephony.SmsMessage.ENCODING_7BIT;
 import static android.telephony.SmsMessage.ENCODING_8BIT;
 import static android.telephony.SmsMessage.ENCODING_16BIT;
+import static android.telephony.SmsMessage.ENCODING_KSC5601;
 import static android.telephony.SmsMessage.ENCODING_UNKNOWN;
 import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
 import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
@@ -45,7 +45,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 +311,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 +377,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 +482,7 @@
         return bo;
     }
 
-    static class PduParser {
+    private static class PduParser {
         byte pdu[];
         int cur;
         SmsHeader userDataHeader;
@@ -490,10 +490,6 @@
         int mUserDataSeptetPadding;
         int mUserDataSize;
 
-        PduParser(String s) {
-            this(IccUtils.hexStringToBytes(s));
-        }
-
         PduParser(byte[] pdu) {
             this.pdu = pdu;
             cur = 0;
@@ -545,7 +541,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 +569,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 +628,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 +667,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 +690,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++];
@@ -781,6 +777,27 @@
             return ret;
         }
 
+        /**
+         * Interprets the user data payload as KSC-5601 characters, and
+         * decodes them into a String.
+         *
+         * @param byteCount the number of bytes in the user data payload
+         * @return a String with the decoded characters
+         */
+        String getUserDataKSC5601(int byteCount) {
+            String ret;
+
+            try {
+                ret = new String(pdu, cur, byteCount, "KSC5601");
+            } catch (UnsupportedEncodingException ex) {
+                ret = "";
+                Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+            }
+
+            cur += byteCount;
+            return ret;
+        }
+
         boolean moreDataPresent() {
             return (pdu.length > cur);
         }
@@ -827,11 +844,13 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public int getProtocolIdentifier() {
         return protocolIdentifier;
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isReplace() {
         return (protocolIdentifier & 0xc0) == 0x40
                 && (protocolIdentifier & 0x3f) > 0
@@ -839,12 +858,14 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isCphsMwiMessage() {
         return ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear()
                 || ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isMWIClearMessage() {
         if (isMwi && (mwiSense == false)) {
             return true;
@@ -855,6 +876,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isMWISetMessage() {
         if (isMwi && (mwiSense == true)) {
             return true;
@@ -865,6 +887,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isMwiDontStore() {
         if (isMwi && mwiDontStore) {
             return true;
@@ -884,31 +907,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);
@@ -1107,6 +1133,16 @@
                 Log.w(LOG_TAG, "MWI for fax, email, or other "
                         + (dataCodingScheme & 0xff));
             }
+        } else if ((dataCodingScheme & 0xC0) == 0x80) {
+            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+            // 0x80..0xBF == Reserved coding groups
+            if (dataCodingScheme == 0x84) {
+                // This value used for KSC5601 by carriers in Korea.
+                encodingType = ENCODING_KSC5601;
+            } else {
+                Log.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
+                        + (dataCodingScheme & 0xff));
+            }
         } else {
             Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
                     + (dataCodingScheme & 0xff));
@@ -1131,6 +1167,10 @@
         case ENCODING_16BIT:
             messageBody = p.getUserDataUCS2(count);
             break;
+
+        case ENCODING_KSC5601:
+            messageBody = p.getUserDataKSC5601(count);
+            break;
         }
 
         if (Config.LOGV) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'");
@@ -1162,6 +1202,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/sip/SipConnectionBase.java b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
index dc4b27b..c139a64 100644
--- a/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
@@ -19,6 +19,7 @@
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.UUSInfo;
 
 import android.net.sip.SipAudioCall;
 import android.os.SystemClock;
@@ -171,4 +172,10 @@
         // TODO: add PRESENTATION_URL
         return Connection.PRESENTATION_ALLOWED;
     }
+
+    @Override
+    public UUSInfo getUUSInfo() {
+        // FIXME: what's this for SIP?
+        return null;
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index 4fd4cdd..6ed9295 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -38,7 +38,6 @@
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneNotifier;
-import com.android.internal.telephony.UUSInfo;
 
 import java.text.ParseException;
 import java.util.List;
@@ -72,6 +71,14 @@
         mSipManager = SipManager.newInstance(context);
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) return true;
+        if (!(o instanceof SipPhone)) return false;
+        SipPhone that = (SipPhone) o;
+        return mProfile.getUriString().equals(that.mProfile.getUriString());
+    }
+
     public String getPhoneName() {
         return "SIP:" + getUriString(mProfile);
     }
@@ -147,10 +154,6 @@
         }
     }
 
-    public Connection dial(String dialString, UUSInfo uusinfo) throws CallStateException {
-        return dial(dialString);
-    }
-
     public Connection dial(String dialString) throws CallStateException {
         synchronized (SipPhone.class) {
             return dialInternal(dialString);
@@ -832,10 +835,6 @@
             }
         }
 
-        @Override
-        public UUSInfo getUUSInfo() {
-            return null;
-        }
     }
 
     private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
index 5499966..afd4d0c 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.sip;
 
 import android.content.Context;
+import android.net.LinkProperties;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
@@ -42,6 +43,7 @@
 import com.android.internal.telephony.PhoneNotifier;
 import com.android.internal.telephony.PhoneSubInfo;
 import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.UUSInfo;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -62,6 +64,12 @@
 
     public abstract Call getRingingCall();
 
+    public Connection dial(String dialString, UUSInfo uusInfo)
+            throws CallStateException {
+        // ignore UUSInfo
+        return dial(dialString);
+    }
+
     void migrateFrom(SipPhoneBase from) {
         migrate(mRingbackRegistrants, from.mRingbackRegistrants);
         migrate(mPreciseCallStateRegistrants, from.mPreciseCallStateRegistrants);
@@ -419,6 +427,18 @@
         Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP.");
     }
 
+    //@Override
+    public boolean needsOtaServiceProvisioning() {
+        // FIXME: what's this for SIP?
+        return false;
+    }
+
+    //@Override
+    public LinkProperties getLinkProperties(String apnType) {
+        // FIXME: what's this for SIP?
+        return null;
+    }
+
     void updatePhoneState() {
         State oldState = state;
 
diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
index 9c72e5a..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];
@@ -894,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) {
@@ -903,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) {
@@ -912,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) {
@@ -921,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) {
@@ -956,6 +954,7 @@
         unimplemented(response);
     }
 
+    @Deprecated
     public void setupDefaultPDP(String apn, String user, String password, Message result) {
         unimplemented(result);
     }
@@ -967,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) {
@@ -1046,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..a5139bd
--- /dev/null
+++ b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+    }
+
+    /**
+     * Set the radio state of mock ril to the given state
+     * @param state for given radio state
+     * @return true if the state is set successful, false if it fails
+     */
+    public boolean setRadioState(int state) {
+        RilCtrlCmds.CtrlReqRadioState req = new RilCtrlCmds.CtrlReqRadioState();
+        if (state < 0 || state > RilCmds.RADIOSTATE_NV_READY) {
+            Log.v(TAG, "the give radio state is not valid.");
+            return false;
+        }
+        req.setState(state);
+        if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, req)) {
+            Log.v(TAG, "send set radio state request failed.");
+            return false;
+        }
+        Msg response = getCtrlResponse();
+        if (response == null) {
+            Log.v(TAG, "failed to get response for setRadioState");
+            return false;
+        }
+        response.printHeader(TAG);
+        RilCtrlCmds.CtrlRspRadioState resp =
+            response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+        int curstate = resp.getState();
+        return curstate == state;
+    }
+}
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..3ea1cf2
--- /dev/null
+++ b/telephony/tests/telephonymockriltests/src/com/android/telephonymockriltests/functional/SimpleTestUsingMockRil.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.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);
+    }
+
+    /**
+     * Set the current radio state of RIL
+     * and verify the radio state is set correctly
+     */
+    public void testSetRadioState() {
+        for (int state = 0; state <= 9; state++) {
+            Log.v(TAG, "set radio state to be " + state);
+            assertTrue("set radio state: " + state + " failed.",
+                       mMockRilCtrl.setRadioState(state));
+        }
+        assertFalse("use an invalid radio state", mMockRilCtrl.setRadioState(-1));
+        assertFalse("the radio state doesn't exist", mMockRilCtrl.setRadioState(10));
+    }
+}
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..7011aeb 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import com.android.internal.telephony.GsmAlphabet;
+
 import junit.framework.TestCase;
 
 import android.test.suitebuilder.annotation.LargeTest;
@@ -51,7 +53,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'));
 
         //
@@ -307,4 +309,26 @@
         assertEquals("a",
                 GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));
     }
+
+    @SmallTest
+    public void testGsm8BitUpackedWithEuckr() throws Exception {
+        // Some feature phones in Korea store contacts as euc-kr.
+        // Test this situations.
+        byte unpacked[];
+
+        // Test general alphabet strings.
+        unpacked = IccUtils.hexStringToBytes("61626320646566FF");
+        assertEquals("abc def",
+                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+
+        // Test korean strings.
+        unpacked = IccUtils.hexStringToBytes("C5D7BDBAC6AEFF");
+        assertEquals("\uD14C\uC2A4\uD2B8",
+                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+
+        // Test gsm Extented Characters.
+        unpacked = GsmAlphabet.stringToGsm8BitPacked(sGsmExtendedChars);
+        assertEquals(sGsmExtendedChars,
+                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+    }
 }
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..c4a6f53
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -0,0 +1,538 @@
+/*
+ * 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;
+
+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"));
+        assertEquals("+16502910000", PhoneNumberUtils.normalizeNumber("+1 650 2910000"));
+    }
+
+    @SmallTest
+    public void testFormatDailabeNumber() {
+        // Using the phoneNumberE164's country code
+        assertEquals("(650) 291-0000",
+                PhoneNumberUtils.formatNumber("6502910000", "+16502910000", "CN"));
+        // The phoneNumberE164 is null
+        assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("6502910000", null, "US"));
+        // The given number has a country code.
+        assertEquals("+1 650-291-0000", PhoneNumberUtils.formatNumber("+16502910000", null, "CN"));
+        // The given number was formatted.
+        assertEquals("650-291-0000", PhoneNumberUtils.formatNumber("650-291-0000", null, "US"));
+    }
+}
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/SimUtilsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
index db38ede..ef62d85 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
@@ -28,7 +28,7 @@
     public void testBasic() throws Exception {
         byte[] data, data2;
 
-        /* 
+        /*
          * bcdToString()
          */
 
@@ -40,9 +40,13 @@
         assertEquals("0126045001448486", IccUtils.bcdToString(data, 1, data.length - 2));
 
         // Stops on invalid BCD value
-        data = IccUtils.hexStringToBytes("98F062400510444868f2");
+        data = IccUtils.hexStringToBytes("98E062400510444868f2");
         assertEquals("890", IccUtils.bcdToString(data, 0, data.length));
 
+        // skip the high nibble 'F' since some PLMNs have it
+        data = IccUtils.hexStringToBytes("98F062400510444868f2");
+        assertEquals("890260450014484862", IccUtils.bcdToString(data, 0, data.length));
+
         /*
          * gsmBcdByteToInt()
          */
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..f0d5b31
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.internal.telephony.ril_proto.RilCmds;
+
+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");
+    }
+
+    public void testSetRadioState() throws IOException {
+        log("testSetRadioState E");
+
+        RilCtrlCmds.CtrlReqRadioState cmdrs = new RilCtrlCmds.CtrlReqRadioState();
+        assertEquals(0, cmdrs.getState());
+
+        cmdrs.setState(RilCmds.RADIOSTATE_SIM_NOT_READY);
+        assertEquals(2, cmdrs.getState());
+
+        Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, cmdrs);
+
+        Msg resp = Msg.recv(mMockRilChannel);
+
+        RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+
+        int state = rsp.getState();
+        log("get response for testSetRadioState: " + state);
+        assertTrue(RilCmds.RADIOSTATE_SIM_NOT_READY == state);
+    }
+}
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..2811b0d 100644
--- a/test-runner/src/android/test/ProviderTestCase2.java
+++ b/test-runner/src/android/test/ProviderTestCase2.java
@@ -74,7 +74,7 @@
     private IsolatedContext mProviderContext;
     private MockContentResolver mResolver;
 
-       private class MockContext2 extends MockContext {
+    private class MockContext2 extends MockContext {
 
         @Override
         public Resources getResources() {
@@ -87,6 +87,11 @@
             // one created through the regular Context
             return getContext().getDir("mockcontext2_" + name, mode);
         }
+
+        @Override
+        public Context getApplicationContext() {
+            return this;
+        }
     }
     /**
      * Constructor.
@@ -141,6 +146,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/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java
index 3fd71c8..b63ff3d 100644
--- a/test-runner/src/android/test/mock/MockContentProvider.java
+++ b/test-runner/src/android/test/mock/MockContentProvider.java
@@ -127,6 +127,16 @@
             throw new UnsupportedOperationException();
         }
 
+        @SuppressWarnings("unused")
+        public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+            return MockContentProvider.this.getStreamTypes(url, mimeTypeFilter);
+        }
+
+        @SuppressWarnings("unused")
+        public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+                throws RemoteException, FileNotFoundException {
+            return MockContentProvider.this.openTypedAssetFile(url, mimeType, opts);
+        }
     }
     private final InversionIContentProvider mIContentProvider = new InversionIContentProvider();
 
@@ -222,6 +232,14 @@
         throw new UnsupportedOperationException("unimplemented mock method call");
     }
 
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+        throw new UnsupportedOperationException("unimplemented mock method call");
+    }
+
+    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) {
+        throw new UnsupportedOperationException("unimplemented mock method call");
+    }
+
     /**
      * Returns IContentProvider which calls back same methods in this class.
      * By overriding this class, we avoid the mechanism hidden behind ContentProvider
diff --git a/test-runner/src/android/test/mock/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/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java
index 0be5bea..183be41 100644
--- a/test-runner/src/android/test/mock/MockIContentProvider.java
+++ b/test-runner/src/android/test/mock/MockIContentProvider.java
@@ -32,6 +32,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
 
 /**
@@ -102,4 +103,13 @@
     public IBinder asBinder() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
+
+    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+            throws RemoteException, FileNotFoundException {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
 }
diff --git a/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/AndroidManifest.xml b/tests/DumpRenderTree/AndroidManifest.xml
index 03b7e26..c151251 100644
--- a/tests/DumpRenderTree/AndroidManifest.xml
+++ b/tests/DumpRenderTree/AndroidManifest.xml
@@ -18,15 +18,18 @@
     <application android:name="HTMLHostApp">
         <uses-library android:name="android.test.runner" />
         <activity android:name="Menu" android:label="Dump Render Tree"
-          android:screenOrientation="portrait">
+          android:screenOrientation="portrait"
+          android:theme="@android:style/Theme.Light">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.TEST" />
             </intent-filter>
         </activity>
         <activity android:name="TestShellActivity" android:launchMode="singleTop"
-          android:screenOrientation="portrait"/>
-        <activity android:name="ReliabilityTestActivity" android:screenOrientation="portrait"/>
+          android:screenOrientation="portrait"
+          android:theme="@android:style/Theme.Light"/>
+        <activity android:name="ReliabilityTestActivity" android:screenOrientation="portrait"
+          android:theme="@android:style/Theme.Light"/>
     </application>
 
     <instrumentation android:name=".LayoutTestsAutoRunner"
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..740f544 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() {
@@ -492,10 +510,22 @@
         obtainMessage(SET_GEOLOCATION_PERMISSION, allow ? 1 : 0, 0).sendToTarget();
     }
 
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        // Configuration is in WebKit, so stay on WebCore thread, but go via the TestShellActivity
+        // as we need access to the Webview.
+        mLayoutTestController.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
+    }
+
     public void overridePreference(String key, boolean value) {
         Message message = obtainMessage(OVERRIDE_PREFERENCE);
         message.getData().putString("key", key);
         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..dea549a 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
@@ -73,43 +73,86 @@
 
     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
+        "http/tests/xmlhttprequest/simple-cross-origin-progress-events.html", // runs webcore into bad state
+        // http://b/2982500
+        "canvas/philip/tests/2d.drawImage.broken.html", // blocks test
+        // http://b/2982500
     };
 
     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/encoding/char-encoding.html"); // fails in Java HTTP stack, see http://b/issue?id=3047156
+        ignoreResultList.add("fast/encoding/char-decoding.html"); // fails in Java HTTP stack, see http://b/issue?id=3047156
+        ignoreResultList.add("fast/encoding/hanarei-blog32-fc2-com.html"); // fails in Java HTTP stack, see http://b/issue?id=3046986
+        ignoreResultList.add("fast/encoding/mailto-always-utf-8.html"); // Requires waitForPolicyDelegate(), see http://b/issue?id=3043468
+        ignoreResultList.add("fast/encoding/percent-escaping.html"); // fails in Java HTTP stack, see http://b/issue?id=3046984
         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("http/tests/appcache/origin-quota.html"); // needs clearAllApplicationCaches(), see http://b/issue?id=2944196
         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 or tests unsuitable for Android.
+        ignoreResultList.add("fast/encoding/char-decoding-mac.html"); // Mac-specific encodings (also marked Won't Fix in Chromium, bug 7388)
+        ignoreResultList.add("fast/encoding/char-encoding-mac.html"); // Mac-specific encodings (also marked Won't Fix in Chromium, bug 7388)
+        ignoreResultList.add("fast/encoding/idn-security.html"); // Mac-specific IDN checks (also marked Won't Fix in Chromium, bug 21814)
+        ignoreResultList.add("fast/events/touch/basic-multi-touch-events.html"); // Requires multi-touch gestures not supported by Android system
+        ignoreResultList.add("fast/events/touch/touch-coords-in-zoom-and-scroll.html"); // Requires eventSender.zoomPageIn(),zoomPageOut()
+        ignoreResultList.add("fast/events/touch/touch-target.html"); // Requires multi-touch gestures not supported by Android system
         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 +215,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..9be2f1c 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,11 @@
     public void setGeolocationPermission(boolean allow);
 
     public void overridePreference(String key, boolean value);
+
+    // For XSSAuditor tests
+    public void setXSSAuditorEnabled(boolean flag);
+
+    // For DeviceOrientation tests
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma);
 }
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..19815fd 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;
@@ -55,6 +57,7 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Vector;
 
@@ -99,6 +102,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 +122,7 @@
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+        requestWindowFeature(Window.FEATURE_PROGRESS);
 
         LinearLayout contentView = new LinearLayout(this);
         contentView.setOrientation(LinearLayout.VERTICAL);
@@ -139,12 +145,19 @@
         // WebView::setJsFlags is noop in JSC build.
         mWebView.setJsFlags("--expose_gc");
 
+        // Always send multitouch events to Webkit since the layout test
+        // is only for the Webkit not the browser's UI.
+        mWebView.setDeferMultiTouch(true);
+
         mHandler = new AsyncHandler();
 
         Intent intent = getIntent();
         if (intent != null) {
             executeIntent(intent);
         }
+
+        // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
+        mWebView.useMockDeviceOrientation();
     }
 
     @Override
@@ -159,6 +172,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 +188,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 +258,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 +351,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 +385,9 @@
         Log.v(LOGTAG, "notifyDone called: " + url);
         if (mWaitUntilDone) {
             mWaitUntilDone = false;
-            mChromeClient.onProgressChanged(mWebView, 101);
+            if (!mRequestedWebKitData && !mTimedOut && !finished()) {
+                requestWebKitData();
+            }
         }
     }
 
@@ -459,8 +498,25 @@
      * Sets the Geolocation permission state to be used for all future requests.
      */
     public void setGeolocationPermission(boolean allow) {
-        mGeolocationPermissionSet = true;
+        mIsGeolocationPermissionSet = true;
         mGeolocationPermission = allow;
+
+        if (mPendingGeolocationPermissionCallbacks != null) {
+            Iterator iter = mPendingGeolocationPermissionCallbacks.keySet().iterator();
+            while (iter.hasNext()) {
+                GeolocationPermissions.Callback callback =
+                        (GeolocationPermissions.Callback) iter.next();
+                String origin = (String) mPendingGeolocationPermissionCallbacks.get(callback);
+                callback.invoke(origin, mGeolocationPermission, false);
+            }
+            mPendingGeolocationPermissionCallbacks = null;
+        }
+    }
+
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        mWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
     }
 
     public void overridePreference(String key, boolean value) {
@@ -473,6 +529,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 +550,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 +615,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);
@@ -670,21 +713,40 @@
         @Override
         public void onGeolocationPermissionsShowPrompt(String origin,
                 GeolocationPermissions.Callback callback) {
-            if (mGeolocationPermissionSet) {
+            if (mIsGeolocationPermissionSet) {
                 callback.invoke(origin, mGeolocationPermission, false);
+                return;
             }
+            if (mPendingGeolocationPermissionCallbacks == null) {
+                mPendingGeolocationPermissionCallbacks =
+                        new HashMap<GeolocationPermissions.Callback, String>();
+            }
+            mPendingGeolocationPermissionCallbacks.put(callback, origin);
         }
 
         @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
@@ -723,13 +785,15 @@
 
     private static class NewWindowWebView extends WebView {
         public NewWindowWebView(Context context, Map<String, Object> jsIfaces) {
-            super(context, null, 0, jsIfaces);
+            super(context, null, 0, jsIfaces, false);
         }
     }
 
     private void resetTestStatus() {
         mWaitUntilDone = false;
         mDumpDataType = mDefaultDumpDataType;
+        mDumpTopFrameAsText = false;
+        mDumpChildFramesAsText = false;
         mTimedOut = false;
         mDumpTitleChanges = false;
         mRequestedWebKitData = false;
@@ -739,10 +803,12 @@
         mEventSender.clearTouchPoints();
         mEventSender.clearTouchMetaState();
         mPageFinished = false;
-        mOneHundredPercentComplete = false;
         mDumpWebKitData = false;
         mGetDrawtime = false;
         mSaveImagePath = null;
+        setDefaultWebSettings(mWebView);
+        mIsGeolocationPermissionSet = false;
+        mPendingGeolocationPermissionCallbacks = null;
     }
 
     private long[] getDrawWebViewTime(WebView view, int count) {
@@ -779,7 +845,7 @@
     }
 
     private boolean canMoveToNextTest() {
-        return (mDumpWebKitData && mOneHundredPercentComplete && mPageFinished && !mWaitUntilDone) || mTimedOut;
+        return (mDumpWebKitData && mPageFinished && !mWaitUntilDone) || mTimedOut;
     }
 
     private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) {
@@ -787,6 +853,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 +878,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 +895,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 +907,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 +922,6 @@
 
     private boolean mPageFinished = false;
     private boolean mDumpWebKitData = false;
-    private boolean mOneHundredPercentComplete = false;
 
     static final String TIMEOUT_STR = "**Test timeout";
 
@@ -861,11 +936,15 @@
     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() +
         "/android/page_draw_time.txt";
 
-    private boolean mGeolocationPermissionSet;
+    private boolean mIsGeolocationPermissionSet;
     private boolean mGeolocationPermission;
+    private Map mPendingGeolocationPermissionCallbacks;
 }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java b/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java
index 716086b..383d782 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java
@@ -31,6 +31,7 @@
 	
     WebViewEventSender(WebView webView) {
         mWebView = webView;
+        mWebView.getSettings().setBuiltInZoomControls(true);
         mTouchPoints = new Vector<TouchPoint>();
     }
 	
@@ -170,70 +171,128 @@
 	}
 
     public void touchStart() {
-        // We only support single touch so examine the first touch point only.
-        // If multi touch is enabled in the future, we need to re-examine this to send
-        // all the touch points with the event.
-        TouchPoint tp = mTouchPoints.get(0);
-
-        if (tp == null) {
+        final int numPoints = mTouchPoints.size();
+        if (numPoints == 0) {
             return;
         }
 
-        tp.setDownTime(SystemClock.uptimeMillis());
-        MotionEvent event = MotionEvent.obtain(tp.downTime(), tp.downTime(),
-                MotionEvent.ACTION_DOWN, tp.getX(), tp.getY(), mTouchMetaState);
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+        long downTime = SystemClock.uptimeMillis();
+
+        for (int i = 0; i < numPoints; ++i) {
+            pointerIds[i] = mTouchPoints.get(i).getId();
+            pointerCoords[i] = new MotionEvent.PointerCoords();
+            pointerCoords[i].x = mTouchPoints.get(i).getX();
+            pointerCoords[i].y = mTouchPoints.get(i).getY();
+            mTouchPoints.get(i).setDownTime(downTime);
+        }
+
+        MotionEvent event = MotionEvent.obtain(downTime, downTime,
+            MotionEvent.ACTION_DOWN, numPoints, pointerIds, pointerCoords,
+            mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
+
         mWebView.onTouchEvent(event);
     }
 
     public void touchMove() {
-        TouchPoint tp = mTouchPoints.get(0);
-
-        if (tp == null) {
+        final int numPoints = mTouchPoints.size();
+        if (numPoints == 0) {
             return;
         }
 
-        if (!tp.hasMoved()) {
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+        int numMovedPoints = 0;
+        for (int i = 0; i < numPoints; ++i) {
+            TouchPoint tp = mTouchPoints.get(i);
+            if (tp.hasMoved()) {
+                pointerIds[numMovedPoints] = mTouchPoints.get(i).getId();
+                pointerCoords[i] = new MotionEvent.PointerCoords();
+                pointerCoords[numMovedPoints].x = mTouchPoints.get(i).getX();
+                pointerCoords[numMovedPoints].y = mTouchPoints.get(i).getY();
+                ++numMovedPoints;
+                tp.setMoved(false);
+            }
+        }
+
+        if (numMovedPoints == 0) {
             return;
         }
 
-        MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
-                MotionEvent.ACTION_MOVE, tp.getX(), tp.getY(), mTouchMetaState);
+        MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(),
+                SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE,
+                numMovedPoints, pointerIds, pointerCoords,
+                mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
         mWebView.onTouchEvent(event);
-
-        tp.setMoved(false);
     }
 
     public void touchEnd() {
-        TouchPoint tp = mTouchPoints.get(0);
-
-        if (tp == null) {
+        final int numPoints = mTouchPoints.size();
+        if (numPoints == 0) {
             return;
         }
 
-        MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
-                MotionEvent.ACTION_UP, tp.getX(), tp.getY(), mTouchMetaState);
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+
+        for (int i = 0; i < numPoints; ++i) {
+            pointerIds[i] = mTouchPoints.get(i).getId();
+            pointerCoords[i] = new MotionEvent.PointerCoords();
+            pointerCoords[i].x = mTouchPoints.get(i).getX();
+            pointerCoords[i].y = mTouchPoints.get(i).getY();
+        }
+
+        MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(),
+                SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,
+                numPoints, pointerIds, pointerCoords,
+                mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
         mWebView.onTouchEvent(event);
 
-        if (tp.isReleased()) {
-            mTouchPoints.remove(0);
+        for (int i = numPoints - 1; i >= 0; --i) {  // remove released points.
+            TouchPoint tp = mTouchPoints.get(i);
+            if (tp.isReleased()) {
+              mTouchPoints.remove(i);
+            }
         }
     }
 
     public void touchCancel() {
-        TouchPoint tp = mTouchPoints.get(0);
-        if (tp == null) {
+        final int numPoints = mTouchPoints.size();
+        if (numPoints == 0) {
             return;
         }
 
-        if (tp.cancelled()) {
-            MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
-                    MotionEvent.ACTION_CANCEL, tp.getX(), tp.getY(), mTouchMetaState);
-            mWebView.onTouchEvent(event);
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+        long cancelTime = SystemClock.uptimeMillis();
+        int numCanceledPoints = 0;
+
+        for (int i = 0; i < numPoints; ++i) {
+            TouchPoint tp = mTouchPoints.get(i);
+            if (tp.cancelled()) {
+                pointerIds[numCanceledPoints] = mTouchPoints.get(i).getId();
+                pointerCoords[numCanceledPoints] = new MotionEvent.PointerCoords();
+                pointerCoords[numCanceledPoints].x = mTouchPoints.get(i).getX();
+                pointerCoords[numCanceledPoints].y = mTouchPoints.get(i).getY();
+                ++numCanceledPoints;
+            }
         }
+
+        if (numCanceledPoints == 0) {
+            return;
+        }
+
+        MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(),
+            SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL,
+            numCanceledPoints, pointerIds, pointerCoords,
+            mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
+
+        mWebView.onTouchEvent(event);
     }
 
     public void cancelTouchPoint(int id) {
-        TouchPoint tp = mTouchPoints.get(0);
+        TouchPoint tp = mTouchPoints.get(id);
         if (tp == null) {
             return;
         }
@@ -242,14 +301,19 @@
     }
 
     public void addTouchPoint(int x, int y) {
-        mTouchPoints.add(new TouchPoint(contentsToWindowX(x), contentsToWindowY(y)));
-        if (mTouchPoints.size() > 1) {
-            Log.w(LOGTAG, "Adding more than one touch point, but multi touch is not supported!");
+        final int numPoints = mTouchPoints.size();
+        int id;
+        if (numPoints == 0) {
+          id = 0;
+        } else {
+          id = mTouchPoints.get(numPoints - 1).getId() + 1;
         }
+
+        mTouchPoints.add(new TouchPoint(id, contentsToWindowX(x), contentsToWindowY(y)));
     }
 
-    public void updateTouchPoint(int id, int x, int y) {
-        TouchPoint tp = mTouchPoints.get(0);
+    public void updateTouchPoint(int i, int x, int y) {
+        TouchPoint tp = mTouchPoints.get(i);
         if (tp == null) {
             return;
         }
@@ -276,7 +340,7 @@
     }
 
     public void releaseTouchPoint(int id) {
-        TouchPoint tp = mTouchPoints.get(0);
+        TouchPoint tp = mTouchPoints.get(id);
         if (tp == null) {
             return;
         }
@@ -305,6 +369,7 @@
     private int mouseY;
 
     private class TouchPoint {
+        private int mId;
         private int mX;
         private int mY;
         private long mDownTime;
@@ -312,7 +377,8 @@
         private boolean mMoved;
         private boolean mCancelled;
 
-        public TouchPoint(int x, int y) {
+        public TouchPoint(int id, int x, int y) {
+            mId = id;
             mX = x;
             mY = y;
             mReleased = false;
@@ -332,6 +398,7 @@
         public void setMoved(boolean moved) { mMoved = moved; }
         public boolean hasMoved() { return mMoved; }
 
+        public int getId() { return mId; }
         public int getX() { return mX; }
         public int getY() { return mY; }
 
diff --git a/tests/DumpRenderTree2/Android.mk b/tests/DumpRenderTree2/Android.mk
new file mode 100644
index 0000000..81fc633
--- /dev/null
+++ b/tests/DumpRenderTree2/Android.mk
@@ -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.
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+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..ea6571e
--- /dev/null
+++ b/tests/DumpRenderTree2/AndroidManifest.xml
@@ -0,0 +1,60 @@
+<?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>
+        <uses-library android:name="android.test.runner" />
+
+        <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.TEST" />
+            </intent-filter>
+        </activity>
+
+        <!-- android:launchMode="singleTask" is there so we only have a one instance
+             of this activity. However, it doesn't seem to work exactly like described in the
+             documentation, because the behaviour of the application suggest
+             there is only a single task for all 3 activities. We don't understand
+             how exactly it all works, but at the moment it works just fine.
+             It can lead to some weird behaviour in the future. -->
+        <activity android:name=".TestsListActivity"
+                  android:label="Tests' list activity"
+                  android:launchMode="singleTask"
+                  android:configChanges="orientation">
+        </activity>
+
+        <activity android:name=".LayoutTestsExecutor"
+                  android:theme="@style/WhiteBackground"
+                  android:label="Layout tests' executor"
+                  android:process=":executor">
+        </activity>
+
+        <service android:name="ManagerService">
+        </service>
+    </application>
+
+    <instrumentation android:name="com.android.dumprendertree2.scriptsupport.ScriptTestRunner"
+                     android:targetPackage="com.android.dumprendertree2"
+                     android:label="Layout tests script runner" />
+
+    <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>
diff --git a/tests/DumpRenderTree2/assets/run_apache2.py b/tests/DumpRenderTree2/assets/run_apache2.py
new file mode 100755
index 0000000..5edead1
--- /dev/null
+++ b/tests/DumpRenderTree2/assets/run_apache2.py
@@ -0,0 +1,150 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Start, stop, or restart apache2 server.
+
+  Apache2 must be installed with mod_php!
+
+  Usage:
+    run_apache2.py start|stop|restart
+"""
+
+import sys
+import os
+import subprocess
+import logging
+import optparse
+import time
+
+def main(options, args):
+  if len(args) < 1:
+    run_cmd = ""
+  else:
+    run_cmd = args[0]
+
+  # Setup logging class
+  logging.basicConfig(level=logging.INFO, format='%(message)s')
+
+  if not run_cmd in ("start", "stop", "restart"):
+    logging.info("illegal argument: " + run_cmd)
+    logging.info("Usage: python run_apache2.py start|stop|restart")
+    return
+
+  # Create /tmp/WebKit if it doesn't exist. This is needed for various files used by apache2
+  tmp_WebKit = os.path.join("/tmp", "WebKit")
+  if not os.path.exists(tmp_WebKit):
+    os.mkdir(tmp_WebKit)
+
+  # Get the path to android tree root based on the script location.
+  # Basically we go 5 levels up
+  parent = os.pardir
+  script_location = os.path.abspath(os.path.dirname(sys.argv[0]))
+  android_tree_root = os.path.join(script_location, parent, parent, parent, parent, parent)
+  android_tree_root = os.path.normpath(android_tree_root)
+
+  # If any of these is relative, then it's relative to ServerRoot (in our case android_tree_root)
+  webkit_path = os.path.join("external", "webkit")
+  if (options.tests_root_directory != None):
+    # if options.tests_root_directory is absolute, os.getcwd() is discarded!
+    layout_tests_path = os.path.normpath(os.path.join(os.getcwd(), options.tests_root_directory))
+  else:
+    layout_tests_path = os.path.join(webkit_path, "LayoutTests")
+  http_conf_path = os.path.join(layout_tests_path, "http", "conf")
+
+  # Prepare the command to set ${APACHE_RUN_USER} and ${APACHE_RUN_GROUP}
+  envvars_path = os.path.join("/etc", "apache2", "envvars")
+  export_envvars_cmd = "source " + envvars_path
+
+  error_log_path = os.path.join(tmp_WebKit, "apache2-error.log")
+  custom_log_path = os.path.join(tmp_WebKit, "apache2-access.log")
+
+  # Prepare the command to (re)start/stop the server with specified settings
+  apache2_restart_template = "apache2 -k %s"
+  directives  = " -c \"ServerRoot " + android_tree_root + "\""
+
+  # The default config in apache2-debian-httpd.conf listens on ports 8080 and
+  # 8443. We also need to listen on port 8000 for HTTP tests.
+  directives += " -c \"Listen 8000\""
+
+  # We use http/tests as the document root as the HTTP tests use hardcoded
+  # resources at the server root. We then use aliases to make available the
+  # complete set of tests and the required scripts.
+  directives += " -c \"DocumentRoot " + os.path.join(layout_tests_path, "http", "tests/") + "\""
+  directives += " -c \"Alias /LayoutTests " + layout_tests_path + "\""
+  directives += " -c \"Alias /WebKitTools/DumpRenderTree/android " + \
+    os.path.join(webkit_path, "WebKitTools", "DumpRenderTree", "android") + "\""
+  directives += " -c \"Alias /ThirdPartyProject.prop " + \
+    os.path.join(webkit_path, "ThirdPartyProject.prop") + "\""
+
+  # This directive is commented out in apache2-debian-httpd.conf for some reason
+  # However, it is useful to browse through tests in the browser, so it's added here.
+  # One thing to note is that because of problems with mod_dir and port numbers, mod_dir
+  # is turned off. That means that there _must_ be a trailing slash at the end of URL
+  # for auto indexes to work correctly.
+  directives += " -c \"LoadModule autoindex_module /usr/lib/apache2/modules/mod_autoindex.so\""
+
+  directives += " -c \"ErrorLog " + error_log_path +"\""
+  directives += " -c \"CustomLog " + custom_log_path + " combined\""
+  directives += " -c \"SSLCertificateFile " + os.path.join(http_conf_path, "webkit-httpd.pem") + \
+    "\""
+  directives += " -c \"User ${APACHE_RUN_USER}\""
+  directives += " -c \"Group ${APACHE_RUN_GROUP}\""
+  directives += " -C \"TypesConfig " + \
+    os.path.join(android_tree_root, http_conf_path, "mime.types") + "\""
+  conf_file_cmd = " -f " + \
+    os.path.join(android_tree_root, http_conf_path, "apache2-debian-httpd.conf")
+
+  # Try to execute the commands
+  logging.info("Will " + run_cmd + " apache2 server.")
+
+  # It is worth noting here that if the configuration file with which we restart the server points
+  # to a different PidFile it will not work and will result in a second apache2 instance.
+  if (run_cmd == 'restart'):
+    logging.info("First will stop...")
+    execute_cmd(export_envvars_cmd + " && " + (apache2_restart_template % ('stop')) + directives + conf_file_cmd)
+    logging.info("Stopped. Will start now...")
+    # We need to sleep breifly to avoid errors with apache being stopped and started too quickly
+    time.sleep(0.5)
+
+  execute_cmd(export_envvars_cmd + " && " + (apache2_restart_template % (run_cmd)) + directives + conf_file_cmd)
+
+def execute_cmd(cmd):
+  p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  (out, err) = p.communicate()
+
+  # Output the stdout from the command to console
+  logging.info(out)
+
+  # Report any errors
+  if p.returncode != 0:
+    logging.info("!! ERRORS:")
+
+    if err.find(envvars_path) != -1:
+      logging.info(err)
+    elif err.find('command not found') != -1:
+      logging.info("apache2 is probably not installed")
+    else:
+      logging.info(err)
+      logging.info("Try looking in " + error_log_path + " for details")
+  else:
+    logging.info("OK")
+
+if __name__ == "__main__":
+  option_parser = optparse.OptionParser(usage="Usage: %prog [options] start|stop|restart")
+  option_parser.add_option("", "--tests-root-directory",
+                           help="The directory from which to take the tests, default is external/webkit/LayoutTests in this checkout of the Android tree")
+  options, args = option_parser.parse_args();
+  main(options, args);
diff --git a/tests/DumpRenderTree2/assets/run_layout_tests.py b/tests/DumpRenderTree2/assets/run_layout_tests.py
new file mode 100755
index 0000000..303a054
--- /dev/null
+++ b/tests/DumpRenderTree2/assets/run_layout_tests.py
@@ -0,0 +1,94 @@
+#!/usr/bin/python
+
+"""Run layout tests on the device.
+
+  It runs the specified tests on the device, downloads the summaries to the temporary directory
+  and optionally shows the detailed results the host's default browser.
+
+  Usage:
+    run_layout_tests.py --show-results-in-browser test-relative-path
+"""
+
+import logging
+import optparse
+import os
+import re
+import sys
+import subprocess
+import tempfile
+import webbrowser
+
+#TODO: These should not be hardcoded
+RESULTS_ABSOLUTE_PATH = "/sdcard/layout-test-results/"
+DETAILS_HTML = "details.html"
+SUMMARY_TXT = "summary.txt"
+
+def main(options, args):
+  if args:
+    path = " ".join(args);
+  else:
+    path = "";
+
+  logging.basicConfig(level=logging.INFO, format='%(message)s')
+
+  tmpdir = tempfile.gettempdir()
+
+  if options.tests_root_directory != None:
+    # if options.tests_root_directory is absolute, os.getcwd() is discarded!
+    tests_root_directory = os.path.normpath(os.path.join(os.getcwd(), options.tests_root_directory))
+    server_options = " --tests-root-directory=" + tests_root_directory
+  else:
+    server_options = "";
+
+  # Restart the server
+  cmd = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), "run_apache2.py") + server_options + " restart"
+  os.system(cmd);
+
+  # Run the tests in path
+  adb_cmd = "adb"
+  if options.serial:
+    adb_cmd += " -s " + options.serial
+  cmd = adb_cmd + " shell am instrument "
+  cmd += "-e class com.android.dumprendertree2.scriptsupport.Starter#startLayoutTests "
+  cmd += "-e path \"" + path + "\" "
+  cmd += "-w com.android.dumprendertree2/com.android.dumprendertree2.scriptsupport.ScriptTestRunner"
+
+  logging.info("Running the tests...")
+  (stdoutdata, stderrdata) = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+  if re.search("^INSTRUMENTATION_STATUS_CODE: -1", stdoutdata, re.MULTILINE) != None:
+    logging.info("Failed to run the tests. Is DumpRenderTree2 installed on the device?")
+    return
+
+  logging.info("Downloading the summaries...")
+
+  # Download the txt summary to tmp folder
+  summary_txt_tmp_path = os.path.join(tmpdir, SUMMARY_TXT)
+  cmd = "rm -f " + summary_txt_tmp_path + ";"
+  cmd += adb_cmd + " pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path
+  subprocess.Popen(cmd, shell=True).wait()
+
+  # Download the html summary to tmp folder
+  details_html_tmp_path = os.path.join(tmpdir, DETAILS_HTML)
+  cmd = "rm -f " + details_html_tmp_path + ";"
+  cmd += adb_cmd + " pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path
+  subprocess.Popen(cmd, shell=True).wait()
+
+  # Print summary to console
+  logging.info("All done.\n")
+  cmd = "cat " + summary_txt_tmp_path
+  os.system(cmd)
+  logging.info("")
+
+  # Open the browser with summary
+  if options.show_results_in_browser != "false":
+    webbrowser.open(details_html_tmp_path)
+
+if __name__ == "__main__":
+  option_parser = optparse.OptionParser(usage="Usage: %prog [options] test-relative-path")
+  option_parser.add_option("", "--show-results-in-browser", default="true",
+                           help="Show the results the host's default web browser, default=true")
+  option_parser.add_option("", "--tests-root-directory",
+                           help="The directory from which to take the tests, default is external/webkit/LayoutTests in this checkout of the Android tree")
+  option_parser.add_option("-s", "--serial", default=None, help="Specify the serial number of device to run test on")
+  options, args = option_parser.parse_args();
+  main(options, args);
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/menu/gui_menu.xml b/tests/DumpRenderTree2/res/menu/gui_menu.xml
new file mode 100644
index 0000000..a5b2b65
--- /dev/null
+++ b/tests/DumpRenderTree2/res/menu/gui_menu.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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/run_all"
+          android:title="@string/run_all_tests" />
+</menu>
\ 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..0496404
--- /dev/null
+++ b/tests/DumpRenderTree2/res/values/strings.xml
@@ -0,0 +1,30 @@
+<?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>
+
+    <string name="run_all_tests">Run all tests in the current directory</string>
+</resources>
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/res/values/style.xml b/tests/DumpRenderTree2/res/values/style.xml
new file mode 100644
index 0000000..35f3419
--- /dev/null
+++ b/tests/DumpRenderTree2/res/values/style.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.
+-->
+<resources>
+    <style name="WhiteBackground">
+        <item name="android:background">@android:color/white</item>
+    </style>
+</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..614b03c
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.util.Log;
+import android.webkit.WebView;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+/**
+ * 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>, Serializable {
+
+    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);
+    }
+
+    /**
+     * A code representing the result of comparing actual and expected results.
+     */
+    public enum ResultCode implements Serializable {
+        RESULTS_MATCH("Results match"),
+        RESULTS_DIFFER("Results differ"),
+        NO_EXPECTED_RESULT("No expected result"),
+        NO_ACTUAL_RESULT("No actual result");
+
+        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;
+    }
+
+    public byte[] getBytes() {
+        ByteArrayOutputStream baos = null;
+        ObjectOutputStream oos = null;
+        try {
+            try {
+                baos = new ByteArrayOutputStream();
+                oos = new ObjectOutputStream(baos);
+                oos.writeObject(this);
+            } finally {
+                if (baos != null) {
+                    baos.close();
+                }
+                if (oos != null) {
+                    oos.close();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Unable to serialize result: " + getRelativePath(), e);
+        }
+
+        return baos == null ? null : baos.toByteArray();
+    }
+
+    public static AbstractResult create(byte[] bytes) {
+        ByteArrayInputStream bais = null;
+        ObjectInputStream ois = null;
+        AbstractResult result = null;
+        try {
+            try {
+                bais = new ByteArrayInputStream(bytes);
+                ois = new ObjectInputStream(bais);
+                result = (AbstractResult)ois.readObject();
+            } finally {
+                if (bais != null) {
+                    bais.close();
+                }
+                if (ois != null) {
+                    ois.close();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Unable to deserialize result!", e);
+        } catch (ClassNotFoundException e) {
+            Log.e(LOG_TAG, "Unable to deserialize result!", e);
+        }
+        return result;
+    }
+
+    public void clearResults() {
+        mAdditionalTextOutputString = null;
+    }
+
+    /**
+     * 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 setExpectedImageResultPath(String relativePath);
+
+    public abstract String getExpectedImageResultPath();
+
+    public abstract void setExpectedTextResult(String expectedResult);
+
+    public abstract void setExpectedTextResultPath(String relativePath);
+
+    public abstract String getExpectedTextResultPath();
+
+    /**
+     * 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 status code representing the result of comparing actual and expected results.
+     *
+     * @return
+     *      the status code from comparing actual and expected results
+     */
+    public abstract ResultCode getResultCode();
+
+    /**
+     * Returns whether this test crashed.
+     *
+     * @return
+     *      whether this test crashed
+     */
+    public abstract boolean didCrash();
+
+    /**
+     * Returns whether this test timed out.
+     *
+     * @return
+     *      whether this test timed out
+     */
+    public abstract boolean didTimeOut();
+
+    /**
+     * Sets that this test timed out.
+     */
+    public abstract void setDidTimeOut();
+
+    /**
+     * Returns whether the test passed.
+     *
+     * @return
+     *      whether the test passed
+     */
+    public boolean didPass() {
+        // Tests that crash can't have timed out or have an actual result.
+        assert !(didCrash() && didTimeOut());
+        assert !(didCrash() && getResultCode() != ResultCode.NO_ACTUAL_RESULT);
+        return !didCrash() && !didTimeOut() && getResultCode() == ResultCode.RESULTS_MATCH;
+    }
+
+    /**
+     * 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();
+}
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..bb9a916
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/AdditionalTextOutput.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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, "urlString=" + urlString + " databaseIdentifier=" + databaseIdentifier,
+                    e);
+        }
+
+        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/CrashedDummyResult.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/CrashedDummyResult.java
new file mode 100644
index 0000000..4831168
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/CrashedDummyResult.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 dummy class representing test that crashed.
+ *
+ * TODO: All the methods regarding expected results need implementing.
+ */
+public class CrashedDummyResult extends AbstractResult {
+    String mRelativePath;
+
+    public CrashedDummyResult(String relativePath) {
+        mRelativePath = relativePath;
+    }
+
+    @Override
+    public byte[] getActualImageResult() {
+        return null;
+    }
+
+    @Override
+    public String getActualTextResult() {
+        return null;
+    }
+
+    @Override
+    public Bundle getBundle() {
+        /** TODO:  */
+        return null;
+    }
+
+    @Override
+    public String getDiffAsHtml() {
+        /** TODO: Probably show at least expected results */
+        return "Ooops, I crashed...";
+    }
+
+    @Override
+    public String getRelativePath() {
+        return mRelativePath;
+    }
+
+    @Override
+    public ResultCode getResultCode() {
+        return ResultCode.NO_ACTUAL_RESULT;
+    }
+
+    @Override
+    public boolean didCrash() {
+        return true;
+    }
+
+    @Override
+    public boolean didTimeOut() {
+        return false;
+    }
+
+    @Override
+    public void setDidTimeOut() {
+        /** This method is not applicable for this type of result */
+        assert false;
+    }
+
+    @Override
+    public TestType getType() {
+        return null;
+    }
+
+    @Override
+    public void obtainActualResults(WebView webview, Message resultObtainedMsg) {
+        /** This method is not applicable for this type of result */
+        assert false;
+    }
+
+    @Override
+    public void setExpectedImageResult(byte[] expectedResult) {
+        /** TODO */
+    }
+
+    @Override
+    public void setExpectedTextResult(String expectedResult) {
+        /** TODO */
+    }
+
+    @Override
+    public String getExpectedImageResultPath() {
+        /** TODO */
+        return null;
+    }
+
+    @Override
+    public String getExpectedTextResultPath() {
+        /** TODO */
+        return null;
+    }
+
+    @Override
+    public void setExpectedImageResultPath(String relativePath) {
+        /** TODO */
+    }
+
+    @Override
+    public void setExpectedTextResultPath(String relativePath) {
+        /** TODO */
+    }
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java
new file mode 100644
index 0000000..5b7cbc4
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.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.android.dumprendertree2;
+
+import android.webkit.WebView;
+
+/**
+ * A class that acts as a JS interface for webview to mock various touch events,
+ * mouse actions and key presses.
+ *
+ * The methods here just call corresponding methods on EventSenderImpl
+ * that contains the logic of how to execute the methods.
+ */
+public class EventSender {
+    EventSenderImpl mEventSenderImpl = new EventSenderImpl();
+
+    public void reset(WebView webView) {
+        mEventSenderImpl.reset(webView);
+    }
+
+    public void enableDOMUIEventLogging(int domNode) {
+        mEventSenderImpl.enableDOMUIEventLogging(domNode);
+    }
+
+    public void fireKeyboardEventsToElement(int domNode) {
+        mEventSenderImpl.fireKeyboardEventsToElement(domNode);
+    }
+
+    public void keyDown(String character, String[] withModifiers) {
+        mEventSenderImpl.keyDown(character, withModifiers);
+    }
+
+    public void keyDown(String character) {
+        keyDown(character, null);
+    }
+
+    public void leapForward(int milliseconds) {
+        mEventSenderImpl.leapForward(milliseconds);
+    }
+
+    public void mouseClick() {
+        mEventSenderImpl.mouseClick();
+    }
+
+    public void mouseDown() {
+        mEventSenderImpl.mouseDown();
+    }
+
+    public void mouseMoveTo(int x, int y) {
+        mEventSenderImpl.mouseMoveTo(x, y);
+    }
+
+    public void mouseUp() {
+        mEventSenderImpl.mouseUp();
+    }
+
+    public void touchStart() {
+        mEventSenderImpl.touchStart();
+    }
+
+    public void addTouchPoint(int x, int y) {
+        mEventSenderImpl.addTouchPoint(x, y);
+    }
+
+    public void updateTouchPoint(int id, int x, int y) {
+        mEventSenderImpl.updateTouchPoint(id, x, y);
+    }
+
+    public void setTouchModifier(String modifier, boolean enabled) {
+        mEventSenderImpl.setTouchModifier(modifier, enabled);
+    }
+
+    public void touchMove() {
+        mEventSenderImpl.touchMove();
+    }
+
+    public void releaseTouchPoint(int id) {
+        mEventSenderImpl.releaseTouchPoint(id);
+    }
+
+    public void touchEnd() {
+        mEventSenderImpl.touchEnd();
+    }
+
+    public void touchCancel() {
+        mEventSenderImpl.touchCancel();
+    }
+
+    public void clearTouchPoints() {
+        mEventSenderImpl.clearTouchPoints();
+    }
+
+    public void cancelTouchPoint(int id) {
+        mEventSenderImpl.cancelTouchPoint(id);
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java
new file mode 100644
index 0000000..68bcf11
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.webkit.WebView;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * An implementation of EventSender
+ */
+public class EventSenderImpl {
+    private static final String LOG_TAG = "EventSenderImpl";
+
+    private static final int MSG_ENABLE_DOM_UI_EVENT_LOGGING = 0;
+    private static final int MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT = 1;
+    private static final int MSG_LEAP_FORWARD = 2;
+
+    private static final int MSG_KEY_DOWN = 3;
+
+    private static final int MSG_MOUSE_DOWN = 4;
+    private static final int MSG_MOUSE_UP = 5;
+    private static final int MSG_MOUSE_CLICK = 6;
+    private static final int MSG_MOUSE_MOVE_TO = 7;
+
+    private static final int MSG_ADD_TOUCH_POINT = 8;
+    private static final int MSG_TOUCH_START = 9;
+    private static final int MSG_UPDATE_TOUCH_POINT = 10;
+    private static final int MSG_TOUCH_MOVE = 11;
+    private static final int MSG_CLEAR_TOUCH_POINTS = 12;
+    private static final int MSG_TOUCH_CANCEL = 13;
+    private static final int MSG_RELEASE_TOUCH_POINT = 14;
+    private static final int MSG_TOUCH_END = 15;
+    private static final int MSG_SET_TOUCH_MODIFIER = 16;
+    private static final int MSG_CANCEL_TOUCH_POINT = 17;
+
+    public static class TouchPoint {
+        WebView mWebView;
+        private int mId;
+        private int mX;
+        private int mY;
+        private long mDownTime;
+        private boolean mReleased = false;
+        private boolean mMoved = false;
+        private boolean mCancelled = false;
+
+        public TouchPoint(WebView webView, int id, int x, int y) {
+            mWebView = webView;
+            mId = id;
+            mX = scaleX(x);
+            mY = scaleY(y);
+        }
+
+        public int getId() {
+          return mId;
+        }
+
+        public int getX() {
+            return mX;
+        }
+
+        public int getY() {
+            return mY;
+        }
+
+        public boolean hasMoved() {
+            return mMoved;
+        }
+
+        public void move(int newX, int newY) {
+            mX = scaleX(newX);
+            mY = scaleY(newY);
+            mMoved = true;
+        }
+
+        public void resetHasMoved() {
+            mMoved = false;
+        }
+
+        public long getDownTime() {
+            return mDownTime;
+        }
+
+        public void setDownTime(long downTime) {
+            mDownTime = downTime;
+        }
+
+        public boolean isReleased() {
+            return mReleased;
+        }
+
+        public void release() {
+            mReleased = true;
+        }
+
+        public boolean isCancelled() {
+            return mCancelled;
+        }
+
+        public void cancel() {
+            mCancelled = true;
+        }
+
+        private int scaleX(int x) {
+            return (int)(x * mWebView.getScale()) - mWebView.getScrollX();
+        }
+
+        private int scaleY(int y) {
+            return (int)(y * mWebView.getScale()) - mWebView.getScrollY();
+        }
+    }
+
+    private List<TouchPoint> mTouchPoints;
+    private int mTouchMetaState;
+    private int mMouseX;
+    private int mMouseY;
+
+    private WebView mWebView;
+
+    private Handler mEventSenderHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            Bundle bundle;
+            MotionEvent event;
+            long ts;
+
+            switch (msg.what) {
+                case MSG_ENABLE_DOM_UI_EVENT_LOGGING:
+                    /** TODO: implement */
+                    break;
+
+                case MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT:
+                    /** TODO: implement */
+                    break;
+
+                case MSG_LEAP_FORWARD:
+                    /** TODO: implement */
+                    break;
+
+                case MSG_KEY_DOWN:
+                    bundle = (Bundle)msg.obj;
+                    String character = bundle.getString("character");
+                    String[] withModifiers = bundle.getStringArray("withModifiers");
+
+                    if (withModifiers != null && withModifiers.length > 0) {
+                        for (int i = 0; i < withModifiers.length; i++) {
+                            executeKeyEvent(KeyEvent.ACTION_DOWN,
+                                    modifierToKeyCode(withModifiers[i]));
+                        }
+                    }
+                    executeKeyEvent(KeyEvent.ACTION_DOWN,
+                            charToKeyCode(character.toLowerCase().toCharArray()[0]));
+                    break;
+
+                /** MOUSE */
+
+                case MSG_MOUSE_DOWN:
+                    ts = SystemClock.uptimeMillis();
+                    event = MotionEvent.obtain(ts, ts, MotionEvent.ACTION_DOWN, mMouseX, mMouseY, 0);
+                    mWebView.onTouchEvent(event);
+                    break;
+
+                case MSG_MOUSE_UP:
+                    ts = SystemClock.uptimeMillis();
+                    event = MotionEvent.obtain(ts, ts, MotionEvent.ACTION_UP, mMouseX, mMouseY, 0);
+                    mWebView.onTouchEvent(event);
+                    break;
+
+                case MSG_MOUSE_CLICK:
+                    mouseDown();
+                    mouseUp();
+                    break;
+
+                case MSG_MOUSE_MOVE_TO:
+                    mMouseX = msg.arg1;
+                    mMouseY = msg.arg2;
+                    break;
+
+                /** TOUCH */
+
+                case MSG_ADD_TOUCH_POINT:
+                    int numPoints = getTouchPoints().size();
+                    int id;
+                    if (numPoints == 0) {
+                        id = 0;
+                    } else {
+                        id = getTouchPoints().get(numPoints - 1).getId() + 1;
+                    }
+                    getTouchPoints().add(new TouchPoint(mWebView, id,
+                            msg.arg1, msg.arg2));
+                    break;
+
+                case MSG_TOUCH_START:
+                    if (getTouchPoints().isEmpty()) {
+                        return;
+                    }
+                    for (int i = 0; i < getTouchPoints().size(); ++i) {
+                        getTouchPoints().get(i).setDownTime(SystemClock.uptimeMillis());
+                    }
+                    executeTouchEvent(MotionEvent.ACTION_DOWN);
+                    break;
+
+                case MSG_UPDATE_TOUCH_POINT:
+                    bundle = (Bundle)msg.obj;
+
+                    int index = bundle.getInt("id");
+                    if (index >= getTouchPoints().size()) {
+                        Log.w(LOG_TAG + "::MSG_UPDATE_TOUCH_POINT", "TouchPoint out of bounds: "
+                                + index);
+                        break;
+                    }
+
+                    getTouchPoints().get(index).move(bundle.getInt("x"), bundle.getInt("y"));
+                    break;
+
+                case MSG_TOUCH_MOVE:
+                    /**
+                     * FIXME: At the moment we don't support multi-touch. Hence, we only examine
+                     * the first touch point. In future this method will need rewriting.
+                     */
+                    if (getTouchPoints().isEmpty()) {
+                        return;
+                    }
+                    executeTouchEvent(MotionEvent.ACTION_MOVE);
+                    for (int i = 0; i < getTouchPoints().size(); ++i) {
+                        getTouchPoints().get(i).resetHasMoved();
+                    }
+                    break;
+
+                case MSG_CANCEL_TOUCH_POINT:
+                    if (msg.arg1 >= getTouchPoints().size()) {
+                        Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: "
+                                + msg.arg1);
+                        break;
+                    }
+
+                    getTouchPoints().get(msg.arg1).cancel();
+                    break;
+
+                case MSG_TOUCH_CANCEL:
+                    /**
+                     * FIXME: At the moment we don't support multi-touch. Hence, we only examine
+                     * the first touch point. In future this method will need rewriting.
+                     */
+                    if (getTouchPoints().isEmpty()) {
+                        return;
+                    }
+                    executeTouchEvent(MotionEvent.ACTION_CANCEL);
+                    break;
+
+                case MSG_RELEASE_TOUCH_POINT:
+                    if (msg.arg1 >= getTouchPoints().size()) {
+                        Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: "
+                                + msg.arg1);
+                        break;
+                    }
+
+                    getTouchPoints().get(msg.arg1).release();
+                    break;
+
+                case MSG_TOUCH_END:
+                    /**
+                     * FIXME: At the moment we don't support multi-touch. Hence, we only examine
+                     * the first touch point. In future this method will need rewriting.
+                     */
+                    if (getTouchPoints().isEmpty()) {
+                        return;
+                    }
+                    executeTouchEvent(MotionEvent.ACTION_UP);
+                    // remove released points.
+                    for (int i = getTouchPoints().size() - 1; i >= 0; --i) {
+                        if (getTouchPoints().get(i).isReleased()) {
+                            getTouchPoints().remove(i);
+                        }
+                    }
+                    break;
+
+                case MSG_SET_TOUCH_MODIFIER:
+                    bundle = (Bundle)msg.obj;
+                    String modifier = bundle.getString("modifier");
+                    boolean enabled = bundle.getBoolean("enabled");
+
+                    int mask = 0;
+                    if ("alt".equals(modifier.toLowerCase())) {
+                        mask = KeyEvent.META_ALT_ON;
+                    } else if ("shift".equals(modifier.toLowerCase())) {
+                        mask = KeyEvent.META_SHIFT_ON;
+                    } else if ("ctrl".equals(modifier.toLowerCase())) {
+                        mask = KeyEvent.META_SYM_ON;
+                    }
+
+                    if (enabled) {
+                        mTouchMetaState |= mask;
+                    } else {
+                        mTouchMetaState &= ~mask;
+                    }
+
+                    break;
+
+                case MSG_CLEAR_TOUCH_POINTS:
+                    getTouchPoints().clear();
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    };
+
+    public void reset(WebView webView) {
+        mWebView = webView;
+        mTouchPoints = null;
+        mTouchMetaState = 0;
+        mMouseX = 0;
+        mMouseY = 0;
+    }
+
+    public void enableDOMUIEventLogging(int domNode) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_ENABLE_DOM_UI_EVENT_LOGGING);
+        msg.arg1 = domNode;
+        msg.sendToTarget();
+    }
+
+    public void fireKeyboardEventsToElement(int domNode) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT);
+        msg.arg1 = domNode;
+        msg.sendToTarget();
+    }
+
+    public void leapForward(int milliseconds) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_LEAP_FORWARD);
+        msg.arg1 = milliseconds;
+        msg.sendToTarget();
+    }
+
+    public void keyDown(String character, String[] withModifiers) {
+        Bundle bundle = new Bundle();
+        bundle.putString("character", character);
+        bundle.putStringArray("withModifiers", withModifiers);
+        mEventSenderHandler.obtainMessage(MSG_KEY_DOWN, bundle).sendToTarget();
+    }
+
+    /** MOUSE */
+
+    public void mouseDown() {
+        mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_DOWN);
+    }
+
+    public void mouseUp() {
+        mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_UP);
+    }
+
+    public void mouseClick() {
+        mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_CLICK);
+    }
+
+    public void mouseMoveTo(int x, int y) {
+        mEventSenderHandler.obtainMessage(MSG_MOUSE_MOVE_TO, x, y).sendToTarget();
+    }
+
+    /** TOUCH */
+
+    public void addTouchPoint(int x, int y) {
+        mEventSenderHandler.obtainMessage(MSG_ADD_TOUCH_POINT, x, y).sendToTarget();
+    }
+
+    public void touchStart() {
+        mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_START);
+    }
+
+    public void updateTouchPoint(int id, int x, int y) {
+        Bundle bundle = new Bundle();
+        bundle.putInt("id", id);
+        bundle.putInt("x", x);
+        bundle.putInt("y", y);
+        mEventSenderHandler.obtainMessage(MSG_UPDATE_TOUCH_POINT, bundle).sendToTarget();
+    }
+
+    public void touchMove() {
+        mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_MOVE);
+    }
+
+    public void cancelTouchPoint(int id) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_CANCEL_TOUCH_POINT);
+        msg.arg1 = id;
+        msg.sendToTarget();
+    }
+
+    public void touchCancel() {
+        mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_CANCEL);
+    }
+
+    public void releaseTouchPoint(int id) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_RELEASE_TOUCH_POINT);
+        msg.arg1 = id;
+        msg.sendToTarget();
+    }
+
+    public void touchEnd() {
+        mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_END);
+    }
+
+    public void setTouchModifier(String modifier, boolean enabled) {
+        Bundle bundle = new Bundle();
+        bundle.putString("modifier", modifier);
+        bundle.putBoolean("enabled", enabled);
+        mEventSenderHandler.obtainMessage(MSG_SET_TOUCH_MODIFIER, bundle).sendToTarget();
+    }
+
+    public void clearTouchPoints() {
+        mEventSenderHandler.sendEmptyMessage(MSG_CLEAR_TOUCH_POINTS);
+    }
+
+    private List<TouchPoint> getTouchPoints() {
+        if (mTouchPoints == null) {
+            mTouchPoints = new LinkedList<TouchPoint>();
+        }
+
+        return mTouchPoints;
+    }
+
+    private void executeTouchEvent(int action) {
+        int numPoints = getTouchPoints().size();
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+
+        for (int i = 0; i < numPoints; ++i) {
+            boolean isNeeded = false;
+            switch(action) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_UP:
+                isNeeded = true;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                isNeeded = getTouchPoints().get(i).hasMoved();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                isNeeded = getTouchPoints().get(i).isCancelled();
+                break;
+            default:
+                Log.w(LOG_TAG + "::executeTouchEvent(),", "action not supported:" + action);
+                break;
+            }
+
+            numPoints = 0;
+            if (isNeeded) {
+                pointerIds[numPoints] = getTouchPoints().get(i).getId();
+                pointerCoords[numPoints] = new MotionEvent.PointerCoords();
+                pointerCoords[numPoints].x = getTouchPoints().get(i).getX();
+                pointerCoords[numPoints].y = getTouchPoints().get(i).getY();
+                ++numPoints;
+            }
+        }
+
+        if (numPoints == 0) {
+            return;
+        }
+
+        MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).getDownTime(),
+                SystemClock.uptimeMillis(), action,
+                numPoints, pointerIds, pointerCoords,
+                mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
+
+        mWebView.onTouchEvent(event);
+    }
+
+    private void executeKeyEvent(int action, int keyCode) {
+        KeyEvent event = new KeyEvent(action, keyCode);
+        mWebView.onKeyDown(event.getKeyCode(), event);
+    }
+
+    /**
+     * Assumes lowercase chars, case needs to be handled by calling function.
+     */
+    private static int charToKeyCode(char c) {
+        // handle numbers
+        if (c >= '0' && c <= '9') {
+            int offset = c - '0';
+            return KeyEvent.KEYCODE_0 + offset;
+        }
+
+        // handle characters
+        if (c >= 'a' && c <= 'z') {
+            int offset = c - 'a';
+            return KeyEvent.KEYCODE_A + offset;
+        }
+
+        // handle all others
+        switch (c) {
+            case '*':
+                return KeyEvent.KEYCODE_STAR;
+
+            case '#':
+                return KeyEvent.KEYCODE_POUND;
+
+            case ',':
+                return KeyEvent.KEYCODE_COMMA;
+
+            case '.':
+                return KeyEvent.KEYCODE_PERIOD;
+
+            case '\t':
+                return KeyEvent.KEYCODE_TAB;
+
+            case ' ':
+                return KeyEvent.KEYCODE_SPACE;
+
+            case '\n':
+                return KeyEvent.KEYCODE_ENTER;
+
+            case '\b':
+            case 0x7F:
+                return KeyEvent.KEYCODE_DEL;
+
+            case '~':
+                return KeyEvent.KEYCODE_GRAVE;
+
+            case '-':
+                return KeyEvent.KEYCODE_MINUS;
+
+            case '=':
+                return KeyEvent.KEYCODE_EQUALS;
+
+            case '(':
+                return KeyEvent.KEYCODE_LEFT_BRACKET;
+
+            case ')':
+                return KeyEvent.KEYCODE_RIGHT_BRACKET;
+
+            case '\\':
+                return KeyEvent.KEYCODE_BACKSLASH;
+
+            case ';':
+                return KeyEvent.KEYCODE_SEMICOLON;
+
+            case '\'':
+                return KeyEvent.KEYCODE_APOSTROPHE;
+
+            case '/':
+                return KeyEvent.KEYCODE_SLASH;
+
+            default:
+                return c;
+        }
+    }
+
+    private static int modifierToKeyCode(String modifier) {
+        if (modifier.equals("ctrlKey")) {
+            return KeyEvent.KEYCODE_ALT_LEFT;
+        } else if (modifier.equals("shiftKey")) {
+            return KeyEvent.KEYCODE_SHIFT_LEFT;
+        } else if (modifier.equals("altKey")) {
+            return KeyEvent.KEYCODE_SYM;
+        }
+
+        return KeyEvent.KEYCODE_UNKNOWN;
+    }
+}
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..9bbf64a
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 com.android.dumprendertree2.forwarder.ForwarderManager;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+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 HTTP_TESTS_PATH = "http/tests/";
+    private static final String SSL_PATH = "ssl/";
+
+    private static final String TOKEN_CRASH = "CRASH";
+    private static final String TOKEN_FAIL = "FAIL";
+    private static final String TOKEN_SLOW = "SLOW";
+
+    private final Set<String> mCrashList = new HashSet<String>();
+    private final Set<String> mFailList = new HashSet<String>();
+    private final Set<String> mSlowList = new HashSet<String>();
+
+    public FileFilter() {
+        loadTestExpectations();
+    }
+
+    private static final String trimTrailingSlashIfPresent(String path) {
+        File file = new File(path);
+        return file.getPath();
+    }
+
+    public void loadTestExpectations() {
+        URL url = null;
+        try {
+            url = new URL(ForwarderManager.getHostSchemePort(false) + "LayoutTests/" +
+                    TEST_EXPECTATIONS_TXT_PATH);
+        } catch (MalformedURLException e) {
+            assert false;
+        }
+
+        try {
+            InputStream inputStream = null;
+            BufferedReader bufferedReader = null;
+            try {
+                bufferedReader = new BufferedReader(new StringReader(new String(
+                        FsUtils.readDataFromUrl(url))));
+
+                String line;
+                String entry;
+                String[] parts;
+                String path;
+                Set<String> tokens;
+                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 */
+                    if (tokens.contains(TOKEN_CRASH)) {
+                        mCrashList.add(path);
+
+                        /** If test is on skip list we ignore any further options */
+                        continue;
+                    }
+
+                    if (tokens.contains(TOKEN_FAIL)) {
+                        mFailList.add(path);
+                    }
+                    if (tokens.contains(TOKEN_SLOW)) {
+                        mSlowList.add(path);
+                    }
+                }
+            } finally {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (bufferedReader != null) {
+                    bufferedReader.close();
+                }
+            }
+        } catch (FileNotFoundException e) {
+            Log.w(LOG_TAG, "reloadConfiguration(): File not found: " + e.getMessage());
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "url=" + url, e);
+        }
+    }
+
+    /**
+     * Checks if test is expected to crash.
+     *
+     * <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 isCrash(String testPath) {
+        for (String prefix : getPrefixes(testPath)) {
+            if (mCrashList.contains(prefix)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks if test result is supposed to be "failed".
+     *
+     * <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 "failed"
+     */
+    public boolean isFail(String testPath) {
+        for (String prefix : getPrefixes(testPath)) {
+            if (mFailList.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 a URL of the test on the server.
+     *
+     * @param relativePath
+     * @return a URL of the test on the server
+     */
+    public static URL getUrl(String relativePath) {
+        String urlBase = ForwarderManager.getHostSchemePort(false);
+
+        /**
+         * URL is formed differently for HTTP vs non-HTTP tests, because HTTP tests
+         * expect different document root. See run_apache2.py and .conf file for details
+         */
+        if (relativePath.startsWith(HTTP_TESTS_PATH)) {
+            relativePath = relativePath.substring(HTTP_TESTS_PATH.length());
+            if (relativePath.startsWith(SSL_PATH)) {
+                urlBase = ForwarderManager.getHostSchemePort(true);
+            }
+        } else {
+            relativePath = "LayoutTests/" + relativePath;
+        }
+
+        try {
+            return new URL(urlBase + relativePath);
+        } catch (MalformedURLException e) {
+            Log.e(LOG_TAG, "Malformed URL!", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * 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;
+    }
+}
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..4f9a737
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.util.Log;
+
+import com.android.dumprendertree2.forwarder.ForwarderManager;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.util.EntityUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+public class FsUtils {
+    public static final String LOG_TAG = "FsUtils";
+
+    private static final String SCRIPT_URL = ForwarderManager.getHostSchemePort(false) +
+            "WebKitTools/DumpRenderTree/android/get_layout_tests_dir_contents.php";
+
+    private static final int HTTP_TIMEOUT_MS = 5000;
+
+    private static HttpClient sHttpClient;
+
+    private static HttpClient getHttpClient() {
+        if (sHttpClient == null) {
+            HttpParams params = new BasicHttpParams();
+
+            SchemeRegistry schemeRegistry = new SchemeRegistry();
+            schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(),
+                    ForwarderManager.HTTP_PORT));
+            schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(),
+                    ForwarderManager.HTTPS_PORT));
+
+            ClientConnectionManager connectionManager = new ThreadSafeClientConnManager(params,
+                    schemeRegistry);
+            sHttpClient = new DefaultHttpClient(connectionManager, params);
+            HttpConnectionParams.setSoTimeout(sHttpClient.getParams(), HTTP_TIMEOUT_MS);
+            HttpConnectionParams.setConnectionTimeout(sHttpClient.getParams(), HTTP_TIMEOUT_MS);
+        }
+        return sHttpClient;
+    }
+
+    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: " + file.getAbsolutePath());
+                outputStream = new FileOutputStream(file, append);
+                outputStream.write(bytes);
+            } finally {
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "file.getAbsolutePath=" + file.getAbsolutePath() + " append=" + append,
+                    e);
+        }
+    }
+
+    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, "file.getAbsolutePath=" + file.getAbsolutePath(), e);
+        }
+
+        return bytes;
+    }
+
+    public static byte[] readDataFromUrl(URL url) {
+        if (url == null) {
+            Log.w(LOG_TAG, "readDataFromUrl(): url is null!");
+            return null;
+        }
+
+        HttpGet httpRequest = new HttpGet(url.toString());
+        ResponseHandler<byte[]> handler = new ResponseHandler<byte[]>() {
+            @Override
+            public byte[] handleResponse(HttpResponse response) throws IOException {
+                if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+                    return null;
+                }
+                HttpEntity entity = response.getEntity();
+                return (entity == null ? null : EntityUtils.toByteArray(entity));
+            }
+        };
+
+        byte[] bytes = null;
+        try {
+            /**
+             * TODO: Not exactly sure why some requests hang indefinitely, but adding this
+             * timeout (in static getter for http client) in loop helps.
+             */
+            boolean timedOut;
+            do {
+                timedOut = false;
+                try {
+                    bytes = getHttpClient().execute(httpRequest, handler);
+                } catch (SocketTimeoutException e) {
+                    timedOut = true;
+                    Log.w(LOG_TAG, "Expected SocketTimeoutException: " + url, e);
+                }
+            } while (timedOut);
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "url=" + url, e);
+        }
+
+        return bytes;
+    }
+
+    public static List<String> getLayoutTestsDirContents(String dirRelativePath, boolean recurse,
+            boolean mode) {
+        String modeString = (mode ? "folders" : "files");
+
+        URL url = null;
+        try {
+            url = new URL(SCRIPT_URL +
+                    "?path=" + dirRelativePath +
+                    "&recurse=" + recurse +
+                    "&mode=" + modeString);
+        } catch (MalformedURLException e) {
+            Log.e(LOG_TAG, "path=" + dirRelativePath + " recurse=" + recurse + " mode=" +
+                    modeString, e);
+            return new LinkedList<String>();
+        }
+
+        HttpGet httpRequest = new HttpGet(url.toString());
+        ResponseHandler<LinkedList<String>> handler = new ResponseHandler<LinkedList<String>>() {
+            @Override
+            public LinkedList<String> handleResponse(HttpResponse response)
+                    throws IOException {
+                LinkedList<String> lines = new LinkedList<String>();
+
+                if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+                    return lines;
+                }
+                HttpEntity entity = response.getEntity();
+                if (entity == null) {
+                    return lines;
+                }
+
+                BufferedReader reader =
+                        new BufferedReader(new InputStreamReader(entity.getContent()));
+                String line;
+                try {
+                    while ((line = reader.readLine()) != null) {
+                        lines.add(line);
+                    }
+                } finally {
+                    if (reader != null) {
+                        reader.close();
+                    }
+                }
+
+                return lines;
+            }
+        };
+
+        try {
+            return getHttpClient().execute(httpRequest, handler);
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "getLayoutTestsDirContents(): HTTP GET failed for URL " + url);
+            return null;
+        }
+    }
+
+    public static void closeInputStream(InputStream inputStream) {
+        try {
+            if (inputStream != null) {
+                inputStream.close();
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Couldn't close stream!", e);
+        }
+    }
+
+    public static void closeOutputStream(OutputStream outputStream) {
+        try {
+            if (outputStream != null) {
+                outputStream.close();
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Couldn't close stream!", e);
+        }
+    }
+}
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..e608e2d
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 clearAllDatabases() {
+        Log.i(LOG_TAG, "clearAllDatabases() called");
+        WebStorage.getInstance().deleteAllData();
+    }
+
+    public void dumpAsText() {
+        dumpAsText(false);
+    }
+
+    public void dumpAsText(boolean enablePixelTest) {
+        mLayoutTestsExecutor.dumpAsText(enablePixelTest);
+    }
+
+    public void dumpChildFramesAsText() {
+        mLayoutTestsExecutor.dumpChildFramesAsText();
+    }
+
+    public void dumpDatabaseCallbacks() {
+        mLayoutTestsExecutor.dumpDatabaseCallbacks();
+    }
+
+    public void notifyDone() {
+        mLayoutTestsExecutor.notifyDone();
+    }
+
+    public void overridePreference(String key, boolean value) {
+        mLayoutTestsExecutor.overridePreference(key, value);
+    }
+
+    public void setAppCacheMaximumSize(long size) {
+        Log.i(LOG_TAG, "setAppCacheMaximumSize() called with: " + size);
+        WebStorage.getInstance().setAppCacheMaximumSize(size);
+    }
+
+    public void setCanOpenWindows() {
+        mLayoutTestsExecutor.setCanOpenWindows();
+    }
+
+    public void setDatabaseQuota(long quota) {
+        /** TODO: Reset this before every test! */
+        Log.i(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 setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        // Configuration is in WebKit, so stay on WebCore thread, but go via LayoutTestsExecutor
+        // as we need access to the Webview.
+        Log.i(LOG_TAG, "setMockDeviceOrientation(" + canProvideAlpha +
+                ", " + alpha + ", " + canProvideBeta + ", " + beta + ", " + canProvideGamma +
+                ", " + gamma + ")");
+        mLayoutTestsExecutor.setMockDeviceOrientation(
+                canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma);
+    }
+
+    public void setMockGeolocationError(int code, String message) {
+        Log.i(LOG_TAG, "setMockGeolocationError(): " + "code=" + code + " message=" + message);
+        MockGeolocation.getInstance().setError(code, message);
+    }
+
+    public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
+        Log.i(LOG_TAG, "setMockGeolocationPosition(): " + "latitude=" + latitude +
+                " longitude=" + longitude + " accuracy=" + accuracy);
+        MockGeolocation.getInstance().setPosition(latitude, longitude, accuracy);
+    }
+
+    public void setXSSAuditorEnabled(boolean flag) {
+        mLayoutTestsExecutor.setXSSAuditorEnabled(flag);
+    }
+
+    public void waitUntilDone() {
+        mLayoutTestsExecutor.waitUntilDone();
+    }
+}
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..97d7cca
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.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.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.net.http.SslError;
+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.Process;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Window;
+import android.webkit.ConsoleMessage;
+import android.webkit.GeolocationPermissions;
+import android.webkit.HttpAuthHandler;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebStorage;
+import android.webkit.WebStorage.QuotaUpdater;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import java.io.File;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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;
+        }
+    }
+
+    private static final String LOG_TAG = "LayoutTestsExecutor";
+
+    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;
+
+    /** A list of tests that remain to run since last crash */
+    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;
+
+    /** The total number of tests to run, doesn't reset after crash */
+    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 mIsGeolocationPermissionSet;
+    private boolean mGeolocationPermission;
+    private Map<GeolocationPermissions.Callback, String> mPendingGeolocationPermissionCallbacks;
+
+    private EventSender mEventSender = new EventSender();
+
+    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);
+            startTests();
+        }
+
+        @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();
+            }
+        }
+
+         @Override
+         public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
+                 String host, String realm) {
+             if (handler.useHttpAuthUsernamePassword() && view != null) {
+                 String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
+                 if (credentials != null && credentials.length == 2) {
+                     handler.proceed(credentials[0], credentials[1]);
+                     return;
+                 }
+             }
+             handler.cancel();
+         }
+
+         @Override
+         public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+             // We ignore SSL errors. In particular, the certificate used by the LayoutTests server
+             // produces an error as it lacks a CN field.
+             handler.proceed();
+         }
+    };
+
+    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 = createWebViewWithJavascriptInterfaces();
+                setupWebView(newWindowWebView);
+            }
+
+            transport.setWebView(newWindowWebView);
+            resultMsg.sendToTarget();
+            return true;
+        }
+
+        @Override
+        public void onGeolocationPermissionsShowPrompt(String origin,
+                GeolocationPermissions.Callback callback) {
+            if (mIsGeolocationPermissionSet) {
+                callback.invoke(origin, mGeolocationPermission, false);
+                return;
+            }
+            if (mPendingGeolocationPermissionCallbacks == null) {
+                mPendingGeolocationPermissionCallbacks =
+                        new HashMap<GeolocationPermissions.Callback, String>();
+            }
+            mPendingGeolocationPermissionCallbacks.put(callback, origin);
+        }
+    };
+
+    /** IMPLEMENTATION */
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        /**
+         * It detects the crash by catching all the uncaught exceptions. However, we
+         * still have to kill the process, because after catching the exception the
+         * activity remains in a strange state, where intents don't revive it.
+         * However, we send the message to the service to speed up the rebooting
+         * (we don't have to wait for time-out to kick in).
+         */
+        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+            @Override
+            public void uncaughtException(Thread thread, Throwable e) {
+                Log.w(LOG_TAG,
+                        "onTestCrashed(): " + mCurrentTestRelativePath + " thread=" + thread, e);
+
+                try {
+                    Message serviceMsg =
+                            Message.obtain(null, ManagerService.MSG_CURRENT_TEST_CRASHED);
+
+                    mManagerServiceMessenger.send(serviceMsg);
+                } catch (RemoteException e2) {
+                    Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e2);
+                }
+
+                Process.killProcess(Process.myPid());
+            }
+        });
+
+        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 = createWebViewWithJavascriptInterfaces();
+        // When we create the first WebView, we need to pause to wait for the WebView thread to spin
+        // and up and for it to register its message handlers.
+        if (previousWebView == null) {
+            try {
+                Thread.currentThread().sleep(1000);
+            } catch (Exception e) {}
+        }
+        setupWebView(mCurrentWebView);
+
+        mEventSender.reset(mCurrentWebView);
+
+        setContentView(mCurrentWebView);
+        if (previousWebView != null) {
+            Log.d(LOG_TAG + "::reset", "previousWebView != null");
+            previousWebView.destroy();
+        }
+    }
+
+    private static class WebViewWithJavascriptInterfaces extends WebView {
+        public WebViewWithJavascriptInterfaces(
+                Context context, Map<String, Object> javascriptInterfaces) {
+            super(context,
+                  null, // attribute set
+                  0, // default style resource ID
+                  javascriptInterfaces,
+                  false); // is private browsing
+        }
+    }
+    private WebView createWebViewWithJavascriptInterfaces() {
+        Map<String, Object> javascriptInterfaces = new HashMap<String, Object>();
+        javascriptInterfaces.put("layoutTestController", mLayoutTestController);
+        javascriptInterfaces.put("eventSender", mEventSender);
+        return new WebViewWithJavascriptInterfaces(this, javascriptInterfaces);
+    }
+
+    private void setupWebView(WebView webView) {
+        webView.setWebViewClient(mWebViewClient);
+        webView.setWebChromeClient(mWebChromeClient);
+
+        /**
+         * 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);
+
+        webView.clearCache(true);
+        webView.setDeferMultiTouch(true);
+
+        WebSettings webViewSettings = webView.getSettings();
+        webViewSettings.setAppCacheEnabled(true);
+        webViewSettings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
+        // Use of larger values causes unexplained AppCache database corruption.
+        // TODO: Investigate what's really going on here.
+        webViewSettings.setAppCacheMaxSize(100 * 1024 * 1024);
+        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);
+
+        // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
+        mCurrentWebView.useMockDeviceOrientation();
+
+        // Must do this after setting the AppCache path.
+        WebStorage.getInstance().deleteAllData();
+    }
+
+    private void startTests() {
+        try {
+            Message serviceMsg =
+                    Message.obtain(null, ManagerService.MSG_FIRST_TEST);
+
+            Bundle bundle = new Bundle();
+            if (!mTestsList.isEmpty()) {
+                bundle.putString("firstTest", mTestsList.get(0));
+                bundle.putInt("index", mCurrentTestIndex);
+            }
+
+            serviceMsg.setData(bundle);
+            mManagerServiceMessenger.send(serviceMsg);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e);
+        }
+
+        runNextTest();
+    }
+
+    private void runNextTest() {
+        assert mCurrentState == CurrentState.IDLE : "mCurrentState = " + mCurrentState.name();
+
+        if (mTestsList.isEmpty()) {
+            onAllTestsFinished();
+            return;
+        }
+
+        mCurrentTestRelativePath = mTestsList.remove(0);
+
+        Log.i(LOG_TAG, "runNextTest(): Start: " + mCurrentTestRelativePath +
+                " (" + mCurrentTestIndex + ")");
+
+        mCurrentTestUri = FileFilter.getUrl(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();
+
+        Log.w(LOG_TAG, "onTestTimedOut(): " + mCurrentTestRelativePath);
+        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();
+
+        Log.i(LOG_TAG, "onTestFinished(): " + mCurrentTestRelativePath);
+        mResultHandler.removeMessages(MSG_TEST_TIMED_OUT);
+        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;
+
+        if (mCurrentTestTimedOut) {
+            mCurrentResult.setDidTimeOut();
+        }
+        mCurrentResult.obtainActualResults(mCurrentWebView,
+                mResultHandler.obtainMessage(MSG_ACTUAL_RESULT_OBTAINED));
+    }
+
+    private void onActualResultsObtained() {
+        assert mCurrentState == CurrentState.OBTAINING_RESULT
+                : "mCurrentState = " + mCurrentState.name();
+
+        Log.i(LOG_TAG, "onActualResultsObtained(): " + mCurrentTestRelativePath);
+        mCurrentState = CurrentState.IDLE;
+
+        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 (!mTestsList.isEmpty()) {
+                bundle.putString("nextTest", mTestsList.get(0));
+            }
+
+            serviceMsg.setData(bundle);
+            mManagerServiceMessenger.send(serviceMsg);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e);
+        }
+    }
+
+    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, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e);
+        }
+
+        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_PERMISSION = 6;
+    private static final int MSG_OVERRIDE_PREFERENCE = 7;
+    private static final int MSG_SET_XSS_AUDITOR_ENABLED = 8;
+
+    /** String constants for use with layoutTestController.overridePreference() */
+    private final String WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED =
+            "WebKitOfflineWebApplicationCacheEnabled";
+
+    Handler mLayoutTestControllerHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
+
+            switch (msg.what) {
+                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_DUMP_DATABASE_CALLBACKS:
+                    mDumpDatabaseCallbacks = true;
+                    break;
+
+                case MSG_NOTIFY_DONE:
+                    if (mCurrentState == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST) {
+                        onTestFinished();
+                    }
+                    break;
+
+                case MSG_OVERRIDE_PREFERENCE:
+                    /**
+                     * TODO: We should look up the correct WebView for the frame which
+                     * called the layoutTestController method. Currently, we just use the
+                     * WebView for the main frame. EventSender suffers from the same
+                     * problem.
+                     */
+                    if (msg.getData().getString("key").equals(
+                            WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED)) {
+                        mCurrentWebView.getSettings().setAppCacheEnabled(msg.getData().getBoolean(
+                                "value"));
+                    } else {
+                        Log.w(LOG_TAG, "MSG_OVERRIDE_PREFERENCE: unsupported preference!");
+                    }
+                    break;
+
+                case MSG_SET_CAN_OPEN_WINDOWS:
+                    mCanOpenWindows = true;
+                    break;
+
+                case MSG_SET_GEOLOCATION_PERMISSION:
+                    mIsGeolocationPermissionSet = true;
+                    mGeolocationPermission = msg.arg1 == 1;
+
+                    if (mPendingGeolocationPermissionCallbacks != null) {
+                        Iterator<GeolocationPermissions.Callback> iter =
+                                mPendingGeolocationPermissionCallbacks.keySet().iterator();
+                        while (iter.hasNext()) {
+                            GeolocationPermissions.Callback callback = iter.next();
+                            String origin = mPendingGeolocationPermissionCallbacks.get(callback);
+                            callback.invoke(origin, mGeolocationPermission, false);
+                        }
+                        mPendingGeolocationPermissionCallbacks = null;
+                    }
+                    break;
+
+                case MSG_SET_XSS_AUDITOR_ENABLED:
+                    mCurrentWebView.getSettings().setXSSAuditorEnabled(msg.arg1 == 1);
+                    break;
+
+                case MSG_WAIT_UNTIL_DONE:
+                    mCurrentState = CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
+                    break;
+
+                default:
+                    assert false : "msg.what=" + msg.what;
+                    break;
+            }
+        }
+    };
+
+    private void resetLayoutTestController() {
+        mCanOpenWindows = false;
+        mDumpDatabaseCallbacks = false;
+        mIsGeolocationPermissionSet = false;
+        mPendingGeolocationPermissionCallbacks = null;
+    }
+
+    public void dumpAsText(boolean enablePixelTest) {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpAsText(" + enablePixelTest + ") called");
+        /** TODO: Implement */
+        if (enablePixelTest) {
+            Log.w(LOG_TAG, "enablePixelTest not implemented, switching to false");
+        }
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_AS_TEXT);
+    }
+
+    public void dumpChildFramesAsText() {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpChildFramesAsText() called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_CHILD_FRAMES_AS_TEXT);
+    }
+
+    public void dumpDatabaseCallbacks() {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpDatabaseCallbacks() called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_DATABASE_CALLBACKS);
+    }
+
+    public void notifyDone() {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": notifyDone() called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_NOTIFY_DONE);
+    }
+
+    public void overridePreference(String key, boolean value) {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": overridePreference(" + key + ", " + value +
+        ") called");
+        Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_OVERRIDE_PREFERENCE);
+        msg.getData().putString("key", key);
+        msg.getData().putBoolean("value", value);
+        msg.sendToTarget();
+    }
+
+    public void setCanOpenWindows() {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setCanOpenWindows() called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_SET_CAN_OPEN_WINDOWS);
+    }
+
+    public void setGeolocationPermission(boolean allow) {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setGeolocationPermission(" + allow +
+                ") called");
+        Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_SET_GEOLOCATION_PERMISSION);
+        msg.arg1 = allow ? 1 : 0;
+        msg.sendToTarget();
+    }
+
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setMockDeviceOrientation(" + canProvideAlpha +
+                ", " + alpha + ", " + canProvideBeta + ", " + beta + ", " + canProvideGamma +
+                ", " + gamma + ")");
+        mCurrentWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
+    }
+
+    public void setXSSAuditorEnabled(boolean flag) {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setXSSAuditorEnabled(" + flag + ") called");
+        Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_SET_XSS_AUDITOR_ENABLED);
+        msg.arg1 = flag ? 1 : 0;
+        msg.sendToTarget();
+    }
+
+    public void waitUntilDone() {
+        Log.i(LOG_TAG, mCurrentTestRelativePath + ": waitUntilDone() called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_WAIT_UNTIL_DONE);
+    }
+}
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..f42dc86
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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";
+
+    private static final int MSG_CRASH_TIMEOUT_EXPIRED = 0;
+    private static final int MSG_SUMMARIZER_DONE = 1;
+
+    private static final int CRASH_TIMEOUT_MS = 20 * 1000;
+
+    /** TODO: make it a setting */
+    static final String RESULTS_ROOT_DIR_PATH =
+            Environment.getExternalStorageDirectory() + File.separator + "layout-test-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;
+    static final int MSG_FIRST_TEST = 2;
+    static final int MSG_CURRENT_TEST_CRASHED = 3;
+
+    /**
+     * This handler is purely for IPC. It is used to create mMessenger
+     * that generates a binder returned in onBind method.
+     */
+    private Handler mIncomingHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_FIRST_TEST:
+                    mSummarizer.reset();
+                    Bundle bundle = msg.getData();
+                    ensureNextTestSetup(bundle.getString("firstTest"), bundle.getInt("index"));
+                    break;
+
+                case MSG_PROCESS_ACTUAL_RESULTS:
+                    Log.d(LOG_TAG,"mIncomingHandler: " + msg.getData().getString("relativePath"));
+                    onActualResultsObtained(msg.getData());
+                    break;
+
+                case MSG_CURRENT_TEST_CRASHED:
+                    mInternalMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
+                    onTestCrashed();
+                    break;
+
+                case MSG_ALL_TESTS_FINISHED:
+                    /** We run it in a separate thread to avoid ANR */
+                    new Thread() {
+                        @Override
+                        public void run() {
+                            mSummarizer.setTestsRelativePath(mAllTestsRelativePath);
+                            Message msg = Message.obtain(mInternalMessagesHandler,
+                                    MSG_SUMMARIZER_DONE);
+                            mSummarizer.summarize(msg);
+                        }
+                    }.start();
+            }
+        }
+    };
+
+    private Messenger mMessenger = new Messenger(mIncomingHandler);
+
+    private Handler mInternalMessagesHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_CRASH_TIMEOUT_EXPIRED:
+                    onTestCrashed();
+                    break;
+
+                case MSG_SUMMARIZER_DONE:
+                    Intent intent = new Intent(ManagerService.this, TestsListActivity.class);
+                    intent.setAction(Intent.ACTION_SHUTDOWN);
+                    /** This flag is needed because we send the intent from the service */
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    startActivity(intent);
+                    break;
+            }
+        }
+    };
+
+    private FileFilter mFileFilter;
+    private Summarizer mSummarizer;
+
+    private String mCurrentlyRunningTest;
+    private int mCurrentlyRunningTestIndex;
+
+    /**
+     * These are implementation details of getExpectedResultPath() used to reduce the number
+     * of requests required to the host server.
+     */
+    private String mLastExpectedResultPathRequested;
+    private String mLastExpectedResultPathFetched;
+
+    private String mAllTestsRelativePath;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        mFileFilter = new FileFilter();
+        mSummarizer = new Summarizer(mFileFilter, RESULTS_ROOT_DIR_PATH, getApplicationContext());
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        mAllTestsRelativePath = intent.getStringExtra("path");
+        assert mAllTestsRelativePath != null;
+        return START_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    private void onActualResultsObtained(Bundle bundle) {
+        mInternalMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
+        ensureNextTestSetup(bundle.getString("nextTest"), bundle.getInt("testIndex") + 1);
+
+        AbstractResult results =
+                AbstractResult.TestType.valueOf(bundle.getString("type")).createResult(bundle);
+
+        Log.i(LOG_TAG, "onActualResultObtained: " + results.getRelativePath());
+        handleResults(results);
+    }
+
+    private void ensureNextTestSetup(String nextTest, int index) {
+        if (nextTest == null) {
+            Log.w(LOG_TAG, "ensureNextTestSetup(): nextTest=null");
+            return;
+        }
+
+        mCurrentlyRunningTest = nextTest;
+        mCurrentlyRunningTestIndex = index;
+        mInternalMessagesHandler.sendEmptyMessageDelayed(MSG_CRASH_TIMEOUT_EXPIRED, CRASH_TIMEOUT_MS);
+    }
+
+    /**
+     * This sends an intent to TestsListActivity to restart LayoutTestsExecutor.
+     * The more detailed description of the flow is in the comment of onNewIntent
+     * method in TestsListActivity.
+     */
+    private void onTestCrashed() {
+        handleResults(new CrashedDummyResult(mCurrentlyRunningTest));
+
+        Log.w(LOG_TAG, "onTestCrashed(): " + mCurrentlyRunningTest +
+                " (" + mCurrentlyRunningTestIndex + ")");
+
+        Intent intent = new Intent(this, TestsListActivity.class);
+        intent.setAction(Intent.ACTION_REBOOT);
+        /** This flag is needed because we send the intent from the service */
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("crashedTestIndex", mCurrentlyRunningTestIndex);
+        startActivity(intent);
+    }
+
+    private void handleResults(AbstractResult results) {
+        String relativePath = results.getRelativePath();
+        results.setExpectedTextResult(getExpectedTextResult(relativePath));
+        results.setExpectedTextResultPath(getExpectedTextResultPath(relativePath));
+        results.setExpectedImageResult(getExpectedImageResult(relativePath));
+        results.setExpectedImageResultPath(getExpectedImageResultPath(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 String getExpectedTextResult(String relativePath) {
+        byte[] result = getExpectedResult(relativePath, TEXT_RESULT_EXTENSION);
+        if (result != null) {
+            return new String(result);
+        }
+        return null;
+    }
+
+    public byte[] getExpectedImageResult(String relativePath) {
+        return getExpectedResult(relativePath, IMAGE_RESULT_EXTENSION);
+    }
+
+    private byte[] getExpectedResult(String relativePath, String extension) {
+        String originalRelativePath =
+                FileFilter.setPathEnding(relativePath, "-expected." + extension);
+        mLastExpectedResultPathRequested = originalRelativePath;
+
+        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.readDataFromUrl(FileFilter.getUrl(relativePath));
+        }
+
+        mLastExpectedResultPathFetched = bytes == null ? null : relativePath;
+        return bytes;
+    }
+
+    private String getExpectedTextResultPath(String relativePath) {
+        return getExpectedResultPath(relativePath, TEXT_RESULT_EXTENSION);
+    }
+
+    private String getExpectedImageResultPath(String relativePath) {
+        return getExpectedResultPath(relativePath, IMAGE_RESULT_EXTENSION);
+    }
+
+    private String getExpectedResultPath(String relativePath, String extension) {
+        String originalRelativePath =
+            FileFilter.setPathEnding(relativePath, "-expected." + extension);
+        if (!originalRelativePath.equals(mLastExpectedResultPathRequested)) {
+            getExpectedResult(relativePath, extension);
+        }
+
+        return mLastExpectedResultPathFetched;
+    }
+}
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..8d01a53
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.os.Build;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import com.android.dumprendertree2.forwarder.ForwarderManager;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 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-top: 4px;" +
+            "       margin-bottom: 2px;}" +
+            "h3 a span.path {" +
+            "       text-decoration: underline;}" +
+            "h3 span.tri {" +
+            "       text-decoration: none;" +
+            "       float: left;" +
+            "       width: 20px;}" +
+            "h3 span.sqr {" +
+            "       text-decoration: none;" +
+            "       float: left;" +
+            "       width: 20px;}" +
+            "h3 span.sqr_pass {" +
+            "       color: #8ee100;}" +
+            "h3 span.sqr_fail {" +
+            "       color: #c30000;}" +
+            "span.source {" +
+            "       display: block;" +
+            "       font-size: 10px;" +
+            "       color: #888;" +
+            "       margin-left: 20px;" +
+            "       margin-bottom: 1px;}" +
+            "span.source a {" +
+            "       font-size: 10px;" +
+            "       color: #888;}" +
+            "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; }" +
+            "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.RESULTS_DIFFER.name() + "{" +
+            "       background-color: #ccc;" +
+            "       color: black;}" +
+            "span." + AbstractResult.ResultCode.NO_EXPECTED_RESULT.name() + "{" +
+            "       background-color: #a700e4;" +
+            "       color: #fff;}" +
+            "span.timed_out {" +
+            "       background-color: #f3cb00;" +
+            "       color: black;}" +
+            "span.crashed {" +
+            "       background-color: #c30000;" +
+            "       color: #fff;}" +
+            "span.noLtc {" +
+            "       background-color: #944000;" +
+            "       color: #fff;}" +
+            "span.noEventSender {" +
+            "       background-color: #815600;" +
+            "       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_DETAILS_RELATIVE_PATH = "details.html";
+    private static final String TXT_SUMMARY_RELATIVE_PATH = "summary.txt";
+
+    private static final int RESULTS_PER_DUMP = 500;
+    private static final int RESULTS_PER_DB_ACCESS = 50;
+
+    private int mCrashedTestsCount = 0;
+    private List<AbstractResult> mUnexpectedFailures = new ArrayList<AbstractResult>();
+    private List<AbstractResult> mExpectedFailures = new ArrayList<AbstractResult>();
+    private List<AbstractResult> mExpectedPasses = new ArrayList<AbstractResult>();
+    private List<AbstractResult> mUnexpectedPasses = new ArrayList<AbstractResult>();
+
+    private Cursor mUnexpectedFailuresCursor;
+    private Cursor mExpectedFailuresCursor;
+    private Cursor mUnexpectedPassesCursor;
+    private Cursor mExpectedPassesCursor;
+
+    private FileFilter mFileFilter;
+    private String mResultsRootDirPath;
+    private String mTestsRelativePath;
+    private Date mDate;
+
+    private int mResultsSinceLastHtmlDump = 0;
+    private int mResultsSinceLastDbAccess = 0;
+
+    private SummarizerDBHelper mDbHelper;
+
+    public Summarizer(FileFilter fileFilter, String resultsRootDirPath, Context context) {
+        mFileFilter = fileFilter;
+        mResultsRootDirPath = resultsRootDirPath;
+
+        /**
+         * We don't run the database I/O in a separate thread to avoid consumer/producer problem
+         * and to simplify code.
+         */
+        mDbHelper = new SummarizerDBHelper(context);
+        mDbHelper.open();
+    }
+
+    public static URI getDetailsUri() {
+        return new File(ManagerService.RESULTS_ROOT_DIR_PATH + File.separator +
+                HTML_DETAILS_RELATIVE_PATH).toURI();
+    }
+
+    public void appendTest(AbstractResult result) {
+        String relativePath = result.getRelativePath();
+
+        if (result.didCrash()) {
+            mCrashedTestsCount++;
+        }
+
+        if (result.didPass()) {
+            result.clearResults();
+            if (mFileFilter.isFail(relativePath)) {
+                mUnexpectedPasses.add(result);
+            } else {
+                mExpectedPasses.add(result);
+            }
+        } else {
+            if (mFileFilter.isFail(relativePath)) {
+                mExpectedFailures.add(result);
+            } else {
+                mUnexpectedFailures.add(result);
+            }
+        }
+
+        if (++mResultsSinceLastDbAccess == RESULTS_PER_DB_ACCESS) {
+            persistLists();
+            clearLists();
+        }
+    }
+
+    private void clearLists() {
+        mUnexpectedFailures.clear();
+        mExpectedFailures.clear();
+        mUnexpectedPasses.clear();
+        mExpectedPasses.clear();
+    }
+
+    private void persistLists() {
+        persistListToTable(mUnexpectedFailures, SummarizerDBHelper.UNEXPECTED_FAILURES_TABLE);
+        persistListToTable(mExpectedFailures, SummarizerDBHelper.EXPECTED_FAILURES_TABLE);
+        persistListToTable(mUnexpectedPasses, SummarizerDBHelper.UNEXPECTED_PASSES_TABLE);
+        persistListToTable(mExpectedPasses, SummarizerDBHelper.EXPECTED_PASSES_TABLE);
+        mResultsSinceLastDbAccess = 0;
+    }
+
+    private void persistListToTable(List<AbstractResult> results, String table) {
+        for (AbstractResult abstractResult : results) {
+            mDbHelper.insertAbstractResult(abstractResult, table);
+        }
+    }
+
+    public void setTestsRelativePath(String testsRelativePath) {
+        mTestsRelativePath = testsRelativePath;
+    }
+
+    public void summarize(Message onFinishMessage) {
+        persistLists();
+        clearLists();
+
+        mUnexpectedFailuresCursor =
+            mDbHelper.getAbstractResults(SummarizerDBHelper.UNEXPECTED_FAILURES_TABLE);
+        mUnexpectedPassesCursor =
+            mDbHelper.getAbstractResults(SummarizerDBHelper.UNEXPECTED_PASSES_TABLE);
+        mExpectedFailuresCursor =
+            mDbHelper.getAbstractResults(SummarizerDBHelper.EXPECTED_FAILURES_TABLE);
+        mExpectedPassesCursor =
+            mDbHelper.getAbstractResults(SummarizerDBHelper.EXPECTED_PASSES_TABLE);
+
+        String webKitRevision = getWebKitRevision();
+        createHtmlDetails(webKitRevision);
+        createTxtSummary(webKitRevision);
+
+        clearLists();
+        mUnexpectedFailuresCursor.close();
+        mUnexpectedPassesCursor.close();
+        mExpectedFailuresCursor.close();
+        mExpectedPassesCursor.close();
+
+        onFinishMessage.sendToTarget();
+    }
+
+    public void reset() {
+        mCrashedTestsCount = 0;
+        clearLists();
+        mDbHelper.reset();
+        mDate = new Date();
+    }
+
+    private void dumpHtmlToFile(StringBuilder html, boolean append) {
+        FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_DETAILS_RELATIVE_PATH),
+                html.toString().getBytes(), append);
+        html.setLength(0);
+        mResultsSinceLastHtmlDump = 0;
+    }
+
+    private void createTxtSummary(String webKitRevision) {
+        StringBuilder txt = new StringBuilder();
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+        txt.append("Path: " + mTestsRelativePath + "\n");
+        txt.append("Date: " + dateFormat.format(mDate) + "\n");
+        txt.append("Build fingerprint: " + Build.FINGERPRINT + "\n");
+        txt.append("WebKit version: " + getWebKitVersionFromUserAgentString() + "\n");
+        txt.append("WebKit revision: " + webKitRevision + "\n");
+
+        txt.append("TOTAL:                     " + getTotalTestCount() + "\n");
+        txt.append("CRASHED (among all tests): " + mCrashedTestsCount + "\n");
+        txt.append("UNEXPECTED FAILURES:       " + mUnexpectedFailuresCursor.getCount() + "\n");
+        txt.append("UNEXPECTED PASSES:         " + mUnexpectedPassesCursor.getCount() + "\n");
+        txt.append("EXPECTED FAILURES:         " + mExpectedFailuresCursor.getCount() + "\n");
+        txt.append("EXPECTED PASSES:           " + mExpectedPassesCursor.getCount() + "\n");
+
+        FsUtils.writeDataToStorage(new File(mResultsRootDirPath, TXT_SUMMARY_RELATIVE_PATH),
+                txt.toString().getBytes(), false);
+    }
+
+    private void createHtmlDetails(String webKitRevision) {
+        StringBuilder html = new StringBuilder();
+
+        html.append("<html><head>");
+        html.append(CSS);
+        html.append(SCRIPT);
+        html.append("</head><body>");
+
+        createTopSummaryTable(webKitRevision, html);
+        dumpHtmlToFile(html, false);
+
+        createResultsList(html, "Unexpected failures", mUnexpectedFailuresCursor);
+        createResultsList(html, "Unexpected passes", mUnexpectedPassesCursor);
+        createResultsList(html, "Expected failures", mExpectedFailuresCursor);
+        createResultsList(html, "Expected passes", mExpectedPassesCursor);
+
+        html.append("</body></html>");
+        dumpHtmlToFile(html, true);
+    }
+
+    private int getTotalTestCount() {
+        return mUnexpectedFailuresCursor.getCount() +
+                mUnexpectedPassesCursor.getCount() +
+                mExpectedPassesCursor.getCount() +
+                mExpectedFailuresCursor.getCount();
+    }
+
+    private String getWebKitVersionFromUserAgentString() {
+        Resources resources = new Resources(new AssetManager(), new DisplayMetrics(),
+                new Configuration());
+        String userAgent =
+                resources.getString(com.android.internal.R.string.web_user_agent);
+
+        Matcher matcher = Pattern.compile("AppleWebKit/([0-9]+?\\.[0-9])").matcher(userAgent);
+        if (matcher.find()) {
+            return matcher.group(1);
+        }
+        return "unknown";
+    }
+
+    private String getWebKitRevision() {
+        URL url = null;
+        try {
+            url = new URL(ForwarderManager.getHostSchemePort(false) + "ThirdPartyProject.prop");
+        } catch (MalformedURLException e) {
+            assert false;
+        }
+
+        String thirdPartyProjectContents = new String(FsUtils.readDataFromUrl(url));
+        Matcher matcher = Pattern.compile("^version=([0-9]+)", Pattern.MULTILINE).matcher(
+                thirdPartyProjectContents);
+        if (matcher.find()) {
+            return matcher.group(1);
+        }
+        return "unknown";
+    }
+
+    private void createTopSummaryTable(String webKitRevision, StringBuilder html) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+        html.append("<h1>" + "Layout tests' results for: " +
+                (mTestsRelativePath.equals("") ? "all tests" : mTestsRelativePath) + "</h1>");
+        html.append("<h3>" + "Date: " + dateFormat.format(new Date()) + "</h3>");
+        html.append("<h3>" + "Build fingerprint: " + Build.FINGERPRINT + "</h3>");
+        html.append("<h3>" + "WebKit version: " + getWebKitVersionFromUserAgentString() + "</h3>");
+
+        html.append("<h3>" + "WebKit revision: ");
+        html.append("<a href=\"http://trac.webkit.org/browser/trunk?rev=" + webKitRevision +
+                "\" target=\"_blank\"><span class=\"path\">" + webKitRevision + "</span></a>");
+        html.append("</h3>");
+
+        html.append("<table class=\"summary\">");
+        createSummaryTableRow(html, "TOTAL", getTotalTestCount());
+        createSummaryTableRow(html, "CRASHED (among all tests)", mCrashedTestsCount);
+        createSummaryTableRow(html, "UNEXPECTED FAILURES", mUnexpectedFailuresCursor.getCount());
+        createSummaryTableRow(html, "UNEXPECTED PASSES", mUnexpectedPassesCursor.getCount());
+        createSummaryTableRow(html, "EXPECTED FAILURES", mExpectedFailuresCursor.getCount());
+        createSummaryTableRow(html, "EXPECTED PASSES", mExpectedPassesCursor.getCount());
+        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 createResultsList(
+            StringBuilder html, String title, Cursor cursor) {
+        String relativePath;
+        String id = "";
+        AbstractResult.ResultCode resultCode;
+
+        html.append("<h2>" + title + " [" + cursor.getCount() + "]</h2>");
+
+        if (!cursor.moveToFirst()) {
+            return;
+        }
+
+        AbstractResult result;
+        do {
+            result = SummarizerDBHelper.getAbstractResult(cursor);
+
+            relativePath = result.getRelativePath();
+            resultCode = result.getResultCode();
+
+            html.append("<h3>");
+
+            /**
+             * 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, ":");
+
+            /** Write the test name */
+            if (resultCode == AbstractResult.ResultCode.RESULTS_DIFFER) {
+                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>");
+            } else {
+                html.append("<a href=\"" + getViewSourceUrl(result.getRelativePath()).toString() + "\"");
+                html.append(" target=\"_blank\">");
+                html.append("<span class=\"sqr sqr_" + (result.didPass() ? "pass" : "fail"));
+                html.append("\">&#x25a0; </span>");
+                html.append("<span class=\"path\">" + result.getRelativePath() + "</span>");
+                html.append("</a>");
+            }
+
+            if (!result.didPass()) {
+                appendTags(html, result);
+            }
+
+            html.append("</h3>");
+            appendExpectedResultsSources(result, html);
+
+            if (resultCode == AbstractResult.ResultCode.RESULTS_DIFFER) {
+                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(" | ");
+                html.append("<a href=\"" + getViewSourceUrl(relativePath).toString() + "\"");
+                html.append(" target=\"_blank\">Show source</a>");
+                html.append("</div>");
+            }
+
+            html.append("<div class=\"space\"></div>");
+
+            if (++mResultsSinceLastHtmlDump == RESULTS_PER_DUMP) {
+                dumpHtmlToFile(html, true);
+            }
+
+            cursor.moveToNext();
+        } while (!cursor.isAfterLast());
+    }
+
+    private void appendTags(StringBuilder html, AbstractResult result) {
+        /** Tag tests which crash, time out or where results don't match */
+        if (result.didCrash()) {
+            html.append(" <span class=\"listItem crashed\">Crashed</span>");
+        } else {
+            if (result.didTimeOut()) {
+                html.append(" <span class=\"listItem timed_out\">Timed out</span>");
+            }
+            AbstractResult.ResultCode resultCode = result.getResultCode();
+            if (resultCode != AbstractResult.ResultCode.RESULTS_MATCH) {
+                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("has no method")) {
+            if (additionalTextOutputString.contains("LayoutTestController")) {
+                html.append(" <span class=\"listItem noLtc\">LTC function missing</span>");
+            }
+            if (additionalTextOutputString.contains("EventSender")) {
+                html.append(" <span class=\"listItem noEventSender\">");
+                html.append("ES function missing</span>");
+            }
+        }
+    }
+
+    private static final void appendExpectedResultsSources(AbstractResult result,
+            StringBuilder html) {
+        String textSource = result.getExpectedTextResultPath();
+        String imageSource = result.getExpectedImageResultPath();
+
+        if (textSource == null) {
+            // Show if a text result is missing. We may want to revisit this decision when we add
+            // support for image results.
+            html.append("<span class=\"source\">Expected textual result missing</span>");
+        } else {
+            html.append("<span class=\"source\">Expected textual result from: ");
+            html.append("<a href=\"" + ForwarderManager.getHostSchemePort(false) + "LayoutTests/" +
+                    textSource + "\"");
+            html.append(" target=\"_blank\">");
+            html.append(textSource + "</a></span>");
+        }
+        if (imageSource != null) {
+            html.append("<span class=\"source\">Expected image result from: ");
+            html.append("<a href=\"" + ForwarderManager.getHostSchemePort(false) + "LayoutTests/" +
+                    imageSource + "\"");
+            html.append(" target=\"_blank\">");
+            html.append(imageSource + "</a></span>");
+        }
+    }
+
+    private static final URL getViewSourceUrl(String relativePath) {
+        URL url = null;
+        try {
+            url = new URL("http", "localhost", ForwarderManager.HTTP_PORT,
+                    "/WebKitTools/DumpRenderTree/android/view_source.php?src=" +
+                    relativePath);
+        } catch (MalformedURLException e) {
+            assert false : "relativePath=" + relativePath;
+        }
+        return url;
+    }
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/SummarizerDBHelper.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/SummarizerDBHelper.java
new file mode 100644
index 0000000..23e13ec
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/SummarizerDBHelper.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.dumprendertree2;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A basic class that wraps database accesses inside itself and provides functionality to
+ * store and retrieve AbstractResults.
+ */
+public class SummarizerDBHelper {
+    private static final String KEY_ID = "id";
+    private static final String KEY_PATH = "path";
+    private static final String KEY_BYTES = "bytes";
+
+    private static final String DATABASE_NAME = "SummarizerDB";
+    private static final int DATABASE_VERSION = 1;
+
+    static final String EXPECTED_FAILURES_TABLE = "expectedFailures";
+    static final String UNEXPECTED_FAILURES_TABLE = "unexpectedFailures";
+    static final String EXPECTED_PASSES_TABLE = "expextedPasses";
+    static final String UNEXPECTED_PASSES_TABLE = "unexpextedPasses";
+    private static final Set<String> TABLES_NAMES = new HashSet<String>();
+    {
+        TABLES_NAMES.add(EXPECTED_FAILURES_TABLE);
+        TABLES_NAMES.add(EXPECTED_PASSES_TABLE);
+        TABLES_NAMES.add(UNEXPECTED_FAILURES_TABLE);
+        TABLES_NAMES.add(UNEXPECTED_PASSES_TABLE);
+    }
+
+    private static final void createTables(SQLiteDatabase db) {
+        String cmd;
+        for (String tableName : TABLES_NAMES) {
+            cmd = "create table " + tableName + " ("
+                    + KEY_ID + " integer primary key autoincrement, "
+                    + KEY_PATH + " text not null, "
+                    + KEY_BYTES + " blob not null);";
+            db.execSQL(cmd);
+        }
+    }
+
+    private static final void dropTables(SQLiteDatabase db) {
+        for (String tableName : TABLES_NAMES) {
+            db.execSQL("DROP TABLE IF EXISTS " + tableName);
+        }
+    }
+
+    private static class DatabaseHelper extends SQLiteOpenHelper {
+        DatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            dropTables(db);
+            createTables(db);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            /** NOOP for now, because we will never upgrade the db */
+        }
+
+        public void reset(SQLiteDatabase db) {
+            dropTables(db);
+            createTables(db);
+        }
+    }
+
+    private DatabaseHelper mDbHelper;
+    private SQLiteDatabase mDb;
+
+    private final Context mContext;
+
+    public SummarizerDBHelper(Context ctx) {
+        mContext = ctx;
+        mDbHelper = new DatabaseHelper(mContext);
+    }
+
+    public void reset() {
+        mDbHelper.reset(this.mDb);
+    }
+
+    public void open() throws SQLException {
+        mDb = mDbHelper.getWritableDatabase();
+    }
+
+    public void close() {
+        mDbHelper.close();
+    }
+
+    public void insertAbstractResult(AbstractResult result, String table) {
+        ContentValues cv = new ContentValues();
+        cv.put(KEY_PATH, result.getRelativePath());
+        cv.put(KEY_BYTES, result.getBytes());
+        mDb.insert(table, null, cv);
+    }
+
+    public Cursor getAbstractResults(String table) throws SQLException {
+        return mDb.query(false, table, new String[] {KEY_BYTES}, null, null, null, null,
+                KEY_PATH + " ASC", null);
+    }
+
+    public static AbstractResult getAbstractResult(Cursor cursor) {
+        return AbstractResult.create(cursor.getBlob(cursor.getColumnIndex(KEY_BYTES)));
+    }
+}
\ 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..9db4d2b
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.Gravity;
+import android.view.Window;
+import android.webkit.WebView;
+import android.widget.Toast;
+
+import com.android.dumprendertree2.scriptsupport.OnEverythingFinishedCallback;
+
+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.
+ */
+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;
+
+    private OnEverythingFinishedCallback mOnEverythingFinishedCallback;
+    private boolean mEverythingFinished;
+
+    @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);
+
+        Intent serviceIntent = new Intent(this, ManagerService.class);
+        serviceIntent.putExtra("path", path);
+        startService(serviceIntent);
+
+        new TestsListPreloaderThread(path, doneMsg).start();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        if (intent.getAction().equals(Intent.ACTION_REBOOT)) {
+            onCrashIntent(intent);
+        } else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
+            onEverythingFinishedIntent(intent);
+        }
+    }
+
+    /**
+     * This method handles an intent that comes from ManageService when crash is detected.
+     * The intent contains an index in mTestsList of the test that crashed. TestsListActivity
+     * restarts the LayoutTestsExecutor from the following test in mTestsList, by sending
+     * an intent to it. This new intent contains a list of remaining tests to run,
+     * total count of all tests, and the index of the first test to run after restarting.
+     * LayoutTestExecutor runs then as usual, sending reports to ManagerService. If it
+     * detects the crash it sends a new intent and the flow repeats.
+     */
+    private void onCrashIntent(Intent intent) {
+        int nextTestToRun = intent.getIntExtra("crashedTestIndex", -1) + 1;
+        if (nextTestToRun > 0 && nextTestToRun <= mTotalTestCount) {
+            restartExecutor(nextTestToRun);
+        }
+    }
+
+    public void registerOnEverythingFinishedCallback(OnEverythingFinishedCallback callback) {
+        mOnEverythingFinishedCallback = callback;
+        if (mEverythingFinished) {
+            mOnEverythingFinishedCallback.onFinished();
+        }
+    }
+
+    private void onEverythingFinishedIntent(Intent intent) {
+        Toast toast = Toast.makeText(this,
+                "All tests finished.\nPress back key to return to the tests' list.",
+                Toast.LENGTH_LONG);
+        toast.setGravity(Gravity.CENTER, -40, 0);
+        toast.show();
+
+        /** Show the details to the user */
+        WebView webView = new WebView(this);
+        webView.getSettings().setJavaScriptEnabled(true);
+        webView.getSettings().setBuiltInZoomControls(true);
+        webView.getSettings().setEnableSmoothTransition(true);
+        /** This enables double-tap to zoom */
+        webView.getSettings().setUseWideViewPort(true);
+
+        setContentView(webView);
+        webView.loadUrl(Summarizer.getDetailsUri().toString());
+
+        mEverythingFinished = true;
+        if (mOnEverythingFinishedCallback != null) {
+            mOnEverythingFinishedCallback.onFinished();
+        }
+    }
+
+    /**
+     * This, together with android:configChanges="orientation" in manifest file, prevents
+     * the activity from restarting on orientation change.
+     */
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        outState.putStringArrayList("testsList", mTestsList);
+        outState.putInt("totalCount", mTotalTestCount);
+
+        super.onSaveInstanceState(outState);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+
+        mTestsList = savedInstanceState.getStringArrayList("testsList");
+        mTotalTestCount = savedInstanceState.getInt("totalCount");
+    }
+
+    /**
+     * (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);
+
+        if (startFrom < mTotalTestCount) {
+            intent.putStringArrayListExtra(LayoutTestsExecutor.EXTRA_TESTS_LIST,
+                    new ArrayList<String>(mTestsList.subList(startFrom, mTotalTestCount)));
+            intent.putExtra(LayoutTestsExecutor.EXTRA_TEST_INDEX, startFrom);
+        } else {
+            intent.putStringArrayListExtra(LayoutTestsExecutor.EXTRA_TESTS_LIST,
+                    new ArrayList<String>());
+        }
+
+        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..0e7d47a
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * 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";
+
+    /** A list containing relative paths of tests to run */
+    private ArrayList<String> mTestsList = new ArrayList<String>();
+
+    private FileFilter mFileFilter;
+
+    /**
+     * A relative path to the directory 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();
+        mRelativePath = path;
+        mDoneMsg = doneMsg;
+    }
+
+    @Override
+    public void run() {
+        if (FileFilter.isTestFile(mRelativePath)) {
+            mTestsList.add(mRelativePath);
+        } else {
+            loadTestsFromUrl(mRelativePath);
+        }
+
+        mDoneMsg.obj = mTestsList;
+        mDoneMsg.sendToTarget();
+    }
+
+    /**
+     * Loads all the tests from the given directories and all the subdirectories
+     * into mTestsList.
+     *
+     * @param dirRelativePath
+     */
+    private void loadTestsFromUrl(String rootRelativePath) {
+        LinkedList<String> directoriesList = new LinkedList<String>();
+        directoriesList.add(rootRelativePath);
+
+        String relativePath;
+        String itemName;
+        while (!directoriesList.isEmpty()) {
+            relativePath = directoriesList.removeFirst();
+
+            List<String> dirRelativePaths = FsUtils.getLayoutTestsDirContents(relativePath, false, true);
+            if (dirRelativePaths != null) {
+                for (String dirRelativePath : dirRelativePaths) {
+                    itemName = new File(dirRelativePath).getName();
+                    if (FileFilter.isTestDir(itemName)) {
+                        directoriesList.add(dirRelativePath);
+                    }
+                }
+            }
+
+            List<String> testRelativePaths = FsUtils.getLayoutTestsDirContents(relativePath, false, false);
+            if (testRelativePaths != null) {
+                for (String testRelativePath : testRelativePaths) {
+                    itemName = new File(testRelativePath).getName();
+                    if (FileFilter.isTestFile(itemName)) {
+                        /** We choose to skip all the tests that are expected to crash. */
+                        if (!mFileFilter.isCrash(testRelativePath)) {
+                            mTestsList.add(testRelativePath);
+                        } else {
+                            /**
+                             * TODO: Summarizer is now in service - figure out how to send the info.
+                             * Previously: mSummarizer.addSkippedTest(relativePath);
+                             */
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
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..3d2b98b
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.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.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 mExpectedResultPath;
+    private String mActualResult;
+    private String mRelativePath;
+    private boolean mDidTimeOut;
+    private ResultCode mResultCode;
+    transient private Message mResultObtainedMsg;
+
+    private boolean mDumpChildFramesAsText;
+
+    transient 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");
+        mExpectedResultPath = bundle.getString("expectedTextualResultPath");
+        mActualResult = bundle.getString("actualTextualResult");
+        setAdditionalTextOutputString(bundle.getString("additionalTextOutputString"));
+        mRelativePath = bundle.getString("relativePath");
+        mDidTimeOut = bundle.getBoolean("didTimeOut");
+    }
+
+    @Override
+    public void clearResults() {
+        super.clearResults();
+        mExpectedResult = null;
+        mActualResult = null;
+    }
+
+    @Override
+    public ResultCode getResultCode() {
+        if (mResultCode == null) {
+            mResultCode = resultsMatch() ? AbstractResult.ResultCode.RESULTS_MATCH
+                    : AbstractResult.ResultCode.RESULTS_DIFFER;
+        }
+        return mResultCode;
+    }
+
+    private boolean resultsMatch() {
+        assert mExpectedResult != null;
+        assert mActualResult != null;
+        // Trim leading and trailing empty lines, as other WebKit platforms do.
+        String leadingEmptyLines = "^\\n+";
+        String trailingEmptyLines = "\\n+$";
+        String trimmedExpectedResult = mExpectedResult.replaceFirst(leadingEmptyLines, "")
+                .replaceFirst(trailingEmptyLines, "");
+        String trimmedActualResult = mActualResult.replaceFirst(leadingEmptyLines, "")
+                .replaceFirst(trailingEmptyLines, "");
+        return trimmedExpectedResult.equals(trimmedActualResult);
+    }
+
+    @Override
+    public boolean didCrash() {
+        return false;
+    }
+
+    @Override
+    public boolean didTimeOut() {
+        return mDidTimeOut;
+    }
+
+    @Override
+    public void setDidTimeOut() {
+        mDidTimeOut = true;
+    }
+
+    @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 setExpectedImageResultPath(String relativePath) {
+        /** This method is not applicable to this type of result */
+    }
+
+    @Override
+    public String getExpectedImageResultPath() {
+        /** This method is not applicable to this type of result */
+        return null;
+    }
+
+    @Override
+    public void setExpectedTextResultPath(String relativePath) {
+        mExpectedResultPath = relativePath;
+    }
+
+    @Override
+    public String getExpectedTextResultPath() {
+        return mExpectedResultPath;
+    }
+
+    @Override
+    public void setExpectedTextResult(String expectedResult) {
+        // For text results, we use an empty string for the expected result when none is
+        // present, as other WebKit platforms do.
+        mExpectedResult = expectedResult == null ? "" : expectedResult;
+    }
+
+    @Override
+    public String getDiffAsHtml() {
+        assert mExpectedResult != null;
+        assert mActualResult != null;
+
+        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>");
+
+        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);
+        // TODO: We should use a map for each line number and lines pair.
+        assert expectedLines.size() == expectedLineNums.size();
+        assert actualLines.size() == actualLineNums.size();
+        assert expectedLines.size() == actualLines.size();
+
+        html.append(VisualDiffUtils.getHtml(expectedLineNums, expectedLines,
+                actualLineNums, actualLines));
+    }
+
+    @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("expectedTextualResultPath", mExpectedResultPath);
+        bundle.putString("actualTextualResult", getActualTextResult());
+        bundle.putString("additionalTextOutputString", getAdditionalTextOutputString());
+        bundle.putString("relativePath", mRelativePath);
+        bundle.putBoolean("didTimeOut", mDidTimeOut);
+        bundle.putString("type", getType().name());
+        return bundle;
+    }
+
+    @Override
+    public String getRelativePath() {
+        return mRelativePath;
+    }
+}
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..d7f7313
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+        diff_match_patch.Diff diff;
+        int size = diffs.size();
+        boolean isLastDiff;
+        for (int j = 0; j < size; j++) {
+            diff = diffs.get(j);
+            isLastDiff = j == size - 1;
+            switch (diff.operation) {
+                case DELETE:
+                    line = processDiff(diff, lineNums, lines, line, i, delSpan, isLastDiff);
+                    if (line.equals("")) {
+                        i++;
+                    }
+                    break;
+
+                case INSERT:
+                    // If the line is currently empty and this insertion is the entire line, the
+                    // expected line is absent, so it has no line number.
+                    if (diff.text.endsWith("\n") || isLastDiff) {
+                        lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++);
+                        lines.add(line);
+                        line = "";
+                    }
+                    break;
+
+                case EQUAL:
+                    line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff);
+                    if (line.equals("")) {
+                        i++;
+                    }
+                    break;
+            }
+        }
+    }
+
+    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;
+        diff_match_patch.Diff diff;
+        int size = diffs.size();
+        boolean isLastDiff;
+        for (int j = 0; j < size; j++) {
+            diff = diffs.get(j);
+            isLastDiff = j == size - 1;
+            switch (diff.operation) {
+                case INSERT:
+                    line = processDiff(diff, lineNums, lines, line, i, insSpan, isLastDiff);
+                    if (line.equals("")) {
+                        i++;
+                    }
+                    break;
+
+                case DELETE:
+                    // If the line is currently empty and deletion is the entire line, the
+                    // actual line is absent, so it has no line number.
+                    if (diff.text.endsWith("\n") || isLastDiff) {
+                        lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++);
+                        lines.add(line);
+                        line = "";
+                    }
+                    break;
+
+                case EQUAL:
+                    line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff);
+                    if (line.equals("")) {
+                        i++;
+                    }
+                    break;
+            }
+        }
+    }
+
+    /**
+     * 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
+     * @param forceOutputLine Force the current line to be output
+     * @return
+     */
+    public static String processDiff(diff_match_patch.Diff diff, LinkedList<Integer> lineNums,
+            LinkedList<String> lines, String line, int i, String begSpan, boolean forceOutputLine) {
+        String endSpan = "</span>";
+        String br = "&nbsp;";
+
+        if (diff.text.endsWith("\n") || forceOutputLine) {
+            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/forwarder/AdbUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java
new file mode 100644
index 0000000..224509d
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2.forwarder;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+/**
+ * The utility class that can setup a socket allowing the device to communicate with remote
+ * machines through the machine that the device is connected to via adb.
+ */
+public class AdbUtils {
+    private static final String LOG_TAG = "AdbUtils";
+
+    private static final String ADB_OK = "OKAY";
+    private static final int ADB_PORT = 5037;
+    private static final String ADB_HOST = "127.0.0.1";
+    private static final int ADB_RESPONSE_SIZE = 4;
+
+    /**
+     * Creates a new socket that can be configured to serve as a transparent proxy to a
+     * remote machine. This can be achieved by calling configureSocket()
+     *
+     * @return a socket that can be configured to link to remote machine
+     * @throws IOException
+     */
+    public static Socket createSocket() throws IOException{
+        return new Socket(ADB_HOST, ADB_PORT);
+    }
+
+    /**
+     * Configures the connection to serve as a transparent proxy to a remote machine.
+     * The given streams must belong to a socket created by createSocket().
+     *
+     * @param inputStream inputStream of the socket we want to configure
+     * @param outputStream outputStream of the socket we want to configure
+     * @param remoteAddress address of the remote machine (as you would type in a browser
+     *      in a machine that the device is connected to via adb)
+     * @param remotePort port on which to connect
+     * @return if the configuration suceeded
+     * @throws IOException
+     */
+    public static boolean configureConnection(InputStream inputStream, OutputStream outputStream,
+            String remoteAddress, int remotePort) throws IOException {
+        String cmd = "tcp:" + remotePort + ":" + remoteAddress;
+        cmd = String.format("%04X", cmd.length()) + cmd;
+
+        byte[] buf = new byte[ADB_RESPONSE_SIZE];
+        outputStream.write(cmd.getBytes());
+        int read = inputStream.read(buf);
+        if (read != ADB_RESPONSE_SIZE || !ADB_OK.equals(new String(buf))) {
+            Log.w(LOG_TAG, "adb cmd failed.");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java
new file mode 100644
index 0000000..f19cd41
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.forwarder;
+
+import android.util.Log;
+
+import com.android.dumprendertree2.FsUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+/**
+ * Worker class for {@link Forwarder}. A ConnectionHandler will be created once the Forwarder
+ * accepts an incoming connection, and it will then forward the incoming/outgoing streams to a
+ * connection already proxied by adb networking (see also {@link AdbUtils}).
+ */
+public class ConnectionHandler {
+
+    private static final String LOG_TAG = "ConnectionHandler";
+
+    public static interface OnFinishedCallback {
+        public void onFinished();
+    }
+
+    private class SocketPipeThread extends Thread {
+
+        private InputStream mInputStream;
+        private OutputStream mOutputStream;
+
+        public SocketPipeThread(InputStream inputStream, OutputStream outputStream) {
+            mInputStream = inputStream;
+            mOutputStream = outputStream;
+            setName("SocketPipeThread: " + getName());
+        }
+
+        @Override
+        public void run() {
+            byte[] buffer = new byte[4096];
+            int length;
+            while (true) {
+                try {
+                    if ((length = mInputStream.read(buffer)) < 0) {
+                        break;
+                    }
+                    mOutputStream.write(buffer, 0, length);
+                } catch (IOException e) {
+                    /** This exception means one of the streams is closed */
+                    Log.v(LOG_TAG, this.toString(), e);
+                    break;
+                }
+            }
+
+            synchronized (mThreadsRunning) {
+                mThreadsRunning--;
+                if (mThreadsRunning == 0) {
+                    ConnectionHandler.this.stop();
+                    mOnFinishedCallback.onFinished();
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            return getName();
+        }
+    }
+
+    private Integer mThreadsRunning;
+
+    private Socket mFromSocket, mToSocket;
+    private SocketPipeThread mFromToPipe, mToFromPipe;
+    private InputStream mFromSocketInputStream, mToSocketInputStream;
+    private OutputStream mFromSocketOutputStream, mToSocketOutputStream;
+
+    private int mPort;
+    private String mRemoteMachineIpAddress;
+
+    private OnFinishedCallback mOnFinishedCallback;
+
+    public ConnectionHandler(String remoteMachineIp, int port, Socket fromSocket, Socket toSocket)
+            throws IOException {
+        mRemoteMachineIpAddress = remoteMachineIp;
+        mPort = port;
+
+        mFromSocket = fromSocket;
+        mToSocket = toSocket;
+
+        try {
+            mFromSocketInputStream = mFromSocket.getInputStream();
+            mToSocketInputStream = mToSocket.getInputStream();
+            mFromSocketOutputStream = mFromSocket.getOutputStream();
+            mToSocketOutputStream = mToSocket.getOutputStream();
+            AdbUtils.configureConnection(mToSocketInputStream, mToSocketOutputStream,
+                    mRemoteMachineIpAddress, mPort);
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Unable to start ConnectionHandler", e);
+            closeStreams();
+            throw e;
+        }
+
+        mFromToPipe = new SocketPipeThread(mFromSocketInputStream, mToSocketOutputStream);
+        mToFromPipe = new SocketPipeThread(mToSocketInputStream, mFromSocketOutputStream);
+    }
+
+    public void registerOnConnectionHandlerFinishedCallback(OnFinishedCallback callback) {
+        mOnFinishedCallback = callback;
+    }
+
+    private void closeStreams() {
+        FsUtils.closeInputStream(mFromSocketInputStream);
+        FsUtils.closeInputStream(mToSocketInputStream);
+        FsUtils.closeOutputStream(mFromSocketOutputStream);
+        FsUtils.closeOutputStream(mToSocketOutputStream);
+    }
+
+    public void start() {
+        /** We have 2 threads running, one for each pipe, that we start here. */
+        mThreadsRunning = 2;
+        mFromToPipe.start();
+        mToFromPipe.start();
+    }
+
+    public void stop() {
+        shutdown(mFromSocket);
+        shutdown(mToSocket);
+    }
+
+    private void shutdown(Socket socket) {
+        synchronized (mFromToPipe) {
+            synchronized (mToFromPipe) {
+                /** This will stop the while loop in the run method */
+                try {
+                    if (!socket.isInputShutdown()) {
+                        socket.shutdownInput();
+                    }
+                } catch (IOException e) {
+                    Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e);
+                }
+                try {
+                    if (!socket.isOutputShutdown()) {
+                        socket.shutdownOutput();
+                    }
+                } catch (IOException e) {
+                    Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e);
+                }
+                try {
+                    if (!socket.isClosed()) {
+                        socket.close();
+                    }
+                } catch (IOException e) {
+                    Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java
new file mode 100644
index 0000000..ce22fa0
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.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.android.dumprendertree2.forwarder;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A port forwarding server. Listens on localhost on specified port and forwards the tcp
+ * communications to external socket via adb networking proxy.
+ */
+public class Forwarder extends Thread {
+    private static final String LOG_TAG = "Forwarder";
+
+    private int mPort;
+    private String mRemoteMachineIpAddress;
+
+    private ServerSocket mServerSocket;
+
+    private Set<ConnectionHandler> mConnectionHandlers = new HashSet<ConnectionHandler>();
+
+    public Forwarder(int port, String remoteMachineIpAddress) {
+        mPort = port;
+        mRemoteMachineIpAddress = remoteMachineIpAddress;
+    }
+
+    @Override
+    public void start() {
+        Log.i(LOG_TAG, "start(): Starting fowarder on port: " + mPort);
+
+        try {
+            mServerSocket = new ServerSocket(mPort);
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "mPort=" + mPort, e);
+            return;
+        }
+
+        super.start();
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            Socket localSocket;
+            try {
+                localSocket = mServerSocket.accept();
+            } catch (IOException e) {
+                /** This most likely means that mServerSocket is already closed */
+                Log.w(LOG_TAG, "mPort=" + mPort, e);
+                break;
+            }
+
+            Socket remoteSocket = null;
+            final ConnectionHandler connectionHandler;
+            try {
+                remoteSocket = AdbUtils.createSocket();
+                connectionHandler = new ConnectionHandler(
+                        mRemoteMachineIpAddress, mPort, localSocket, remoteSocket);
+            } catch (IOException exception) {
+                try {
+                    localSocket.close();
+                } catch (IOException e) {
+                    Log.e(LOG_TAG, "mPort=" + mPort, e);
+                }
+                if (remoteSocket != null) {
+                    try {
+                        remoteSocket.close();
+                    } catch (IOException e) {
+                        Log.e(LOG_TAG, "mPort=" + mPort, e);
+                    }
+                }
+                continue;
+            }
+
+            /**
+             * We have to close the sockets after the ConnectionHandler finishes, so we
+             * don't get "Too may open files" exception. We also remove the ConnectionHandler
+             * from the collection to avoid memory issues.
+             * */
+            ConnectionHandler.OnFinishedCallback callback =
+                    new ConnectionHandler.OnFinishedCallback() {
+                @Override
+                public void onFinished() {
+                    synchronized (this) {
+                        if (!mConnectionHandlers.remove(connectionHandler)) {
+                            assert false : "removeConnectionHandler(): not in the collection";
+                        }
+                    }
+                }
+            };
+            connectionHandler.registerOnConnectionHandlerFinishedCallback(callback);
+
+            synchronized (this) {
+                mConnectionHandlers.add(connectionHandler);
+            }
+            connectionHandler.start();
+        }
+
+        synchronized (this) {
+            for (ConnectionHandler connectionHandler : mConnectionHandlers) {
+                connectionHandler.stop();
+            }
+        }
+    }
+
+    public void finish() {
+        try {
+            mServerSocket.close();
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "mPort=" + mPort, e);
+        }
+    }
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java
new file mode 100644
index 0000000..cff436ff
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.forwarder;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A simple class to start and stop Forwarders running on some ports.
+ *
+ * It uses a singleton pattern and is thread safe.
+ */
+public class ForwarderManager {
+    private static final String LOG_TAG = "ForwarderManager";
+
+    /**
+     * The IP address of the server serving the tests.
+     */
+    private static final String HOST_IP = "127.0.0.1";
+
+    /**
+     * We use these ports because other webkit platforms do. They are set up in
+     * external/webkit/LayoutTests/http/conf/apache2-debian-httpd.conf
+     */
+    public static final int HTTP_PORT = 8000;
+    public static final int HTTPS_PORT = 8443;
+
+    private static ForwarderManager forwarderManager;
+
+    private Set<Forwarder> mForwarders;
+    private boolean mIsStarted;
+
+    private ForwarderManager() {
+        mForwarders = new HashSet<Forwarder>(2);
+        mForwarders.add(new Forwarder(HTTP_PORT, HOST_IP));
+        mForwarders.add(new Forwarder(HTTPS_PORT, HOST_IP));
+    }
+
+    /**
+     * Returns the main part of the URL with the trailing slash
+     *
+     * @param isHttps
+     * @return
+     */
+    public static final String getHostSchemePort(boolean isHttps) {
+        int port;
+        String protocol;
+        if (isHttps) {
+            protocol = "https";
+            port = HTTPS_PORT;
+        } else {
+            protocol = "http";
+            port = HTTP_PORT;
+        }
+
+        URL url = null;
+        try {
+            url = new URL(protocol, HOST_IP, port, "/");
+        } catch (MalformedURLException e) {
+            assert false : "isHttps=" + isHttps;
+        }
+
+        return url.toString();
+    }
+
+    public static synchronized ForwarderManager getForwarderManager() {
+        if (forwarderManager == null) {
+            forwarderManager = new ForwarderManager();
+        }
+        return forwarderManager;
+    }
+
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+    public synchronized void start() {
+        if (mIsStarted) {
+            Log.w(LOG_TAG, "start(): ForwarderManager already running! NOOP.");
+            return;
+        }
+
+        for (Forwarder forwarder : mForwarders) {
+            forwarder.start();
+        }
+
+        mIsStarted = true;
+        Log.i(LOG_TAG, "ForwarderManager started.");
+    }
+
+    public synchronized void stop() {
+        if (!mIsStarted) {
+            Log.w(LOG_TAG, "stop(): ForwarderManager already stopped! NOOP.");
+            return;
+        }
+
+        for (Forwarder forwarder : mForwarders) {
+            forwarder.finish();
+        }
+
+        mIsStarted = false;
+        Log.i(LOG_TAG, "ForwarderManager stopped.");
+    }
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java
new file mode 100644
index 0000000..e1d4364
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.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 com.android.dumprendertree2.scriptsupport;
+
+/**
+ * Callback used to inform scriptsupport.Starter that everything is finished and
+ * we can exit
+ */
+public interface OnEverythingFinishedCallback {
+    public void onFinished();
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java
new file mode 100644
index 0000000..78f58d5
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.scriptsupport;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+
+/**
+ * Extends InstrumentationTestRunner to allow the script to pass arguments to the application
+ */
+public class ScriptTestRunner extends InstrumentationTestRunner {
+    String mTestsRelativePath;
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        mTestsRelativePath = arguments.getString("path");
+        super.onCreate(arguments);
+    }
+
+    public String getTestsRelativePath() {
+        return mTestsRelativePath;
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java
new file mode 100644
index 0000000..6f41a0f
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.scriptsupport;
+
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import com.android.dumprendertree2.TestsListActivity;
+import com.android.dumprendertree2.forwarder.ForwarderManager;
+
+/**
+ * A class which provides methods that can be invoked by a script running on the host machine to
+ * run the tests.
+ *
+ * It starts a TestsListActivity and does not return until all the tests finish executing.
+ */
+public class Starter extends ActivityInstrumentationTestCase2<TestsListActivity> {
+    private static final String LOG_TAG = "Starter";
+    private boolean mEverythingFinished;
+
+    public Starter() {
+        super(TestsListActivity.class);
+    }
+
+    /**
+     * This method is called from adb to start executing the tests. It doesn't return
+     * until everything is finished so that the script can wait for the end if it needs
+     * to.
+     */
+    public void startLayoutTests() {
+        ScriptTestRunner runner = (ScriptTestRunner)getInstrumentation();
+        String relativePath = runner.getTestsRelativePath();
+
+        ForwarderManager.getForwarderManager().start();
+
+        Intent intent = new Intent();
+        intent.setClassName("com.android.dumprendertree2", "TestsListActivity");
+        intent.setAction(Intent.ACTION_RUN);
+        intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath);
+        setActivityIntent(intent);
+        getActivity().registerOnEverythingFinishedCallback(new OnEverythingFinishedCallback() {
+            /** This method is safe to call on any thread */
+            @Override
+            public void onFinished() {
+                synchronized (Starter.this) {
+                    mEverythingFinished = true;
+                    Starter.this.notifyAll();
+                }
+            }
+        });
+
+        synchronized (this) {
+            while (!mEverythingFinished) {
+                try {
+                    this.wait();
+                } catch (InterruptedException e) {
+                    Log.e(LOG_TAG, "startLayoutTests()", e);
+                }
+            }
+        }
+
+        ForwarderManager.getForwarderManager().stop();
+    }
+}
\ No newline at end of file
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..5de69a7
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.FsUtils;
+import com.android.dumprendertree2.TestsListActivity;
+import com.android.dumprendertree2.R;
+import com.android.dumprendertree2.forwarder.ForwarderManager;
+
+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.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+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 android.widget.Toast;
+
+import java.io.File;
+import java.util.ArrayList;
+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";
+
+    /** 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;
+
+    private static final CharSequence NO_RESPONSE_MESSAGE =
+            "No response from host when getting directory contents. Is the host server running?";
+
+    /** Initialized lazily before first sProgressDialog.show() */
+    private static ProgressDialog sProgressDialog;
+
+    private ListView mListView;
+
+    /** This is a relative path! */
+    private String mCurrentDirPath;
+
+    /**
+     * 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);
+
+        ForwarderManager.getForwarderManager().start();
+
+        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 */
+                    runAllTestsUnder(item.getRelativePath());
+                }
+            }
+        });
+
+        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("");
+    }
+
+    private void runAllTestsUnder(String relativePath) {
+        Intent intent = new Intent();
+        intent.setClass(DirListActivity.this, TestsListActivity.class);
+        intent.setAction(Intent.ACTION_RUN);
+        intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath);
+        startActivity(intent);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.gui_menu, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.run_all:
+                runAllTestsUnder(mCurrentDirPath);
+                return true;
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    @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);
+                        runAllTestsUnder(args.getString("relativePath"));
+                    }
+                });
+
+                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) {
+                    setTitle(shortenTitle(mCurrentDirPath));
+                    delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG);
+                    if (sProgressDialog != null) {
+                        sProgressDialog.dismiss();
+                    }
+                    if (msg.obj == null) {
+                        Toast.makeText(DirListActivity.this, NO_RESPONSE_MESSAGE,
+                                Toast.LENGTH_LONG).show();
+                    } else {
+                        setListAdapter(new DirListAdapter(DirListActivity.this,
+                                (ListItem[])msg.obj));
+                    }
+                }
+            }
+        }).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) {
+        List<ListItem> subDirs = new ArrayList<ListItem>();
+        List<ListItem> subFiles = new ArrayList<ListItem>();
+
+        List<String> dirRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, true);
+        if (dirRelativePaths == null) {
+            return null;
+        }
+        for (String dirRelativePath : dirRelativePaths) {
+            if (FileFilter.isTestDir(new File(dirRelativePath).getName())) {
+                subDirs.add(new ListItem(dirRelativePath, true));
+            }
+        }
+
+        List<String> testRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, false);
+        if (testRelativePaths == null) {
+            return null;
+        }
+        for (String testRelativePath : testRelativePaths) {
+            if (FileFilter.isTestFile(new File(testRelativePath).getName())) {
+                subFiles.add(new ListItem(testRelativePath, false));
+            }
+        }
+
+        /** Concatenate the two lists */
+        subDirs.addAll(subFiles);
+
+        return subDirs.toArray(new ListItem[subDirs.size()]);
+    }
+}
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..df3c3ed
--- /dev/null
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -0,0 +1,307 @@
+<?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.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="AdvancedGradientsActivity"
+                android:label="_Advanced Gradients">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="LabelsActivity"
+                android:label="_Labels">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="ResizeActivity"
+                android:label="_Resize"
+                android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="TextGammaActivity"
+                android:label="_Gamma"
+                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="TextGammaActivity$SubGammaActivity"
+                android:label="_Sub Gamma"
+                android:theme="@android:style/Theme.Translucent.NoTitleBar"
+                android:hardwareAccelerated="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </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="NewLayersActivity"
+                android:label="_NewLayers">
+            <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="BitmapsAlphaActivity"
+                android:label="_BitmapsAlpha"
+                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="ThinPatchesActivity"
+                android:label="_9patchThin"
+                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="GradientsActivity"
+                android:label="_Gradients">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="ShadersActivity"
+                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="TransparentListActivity"
+                android:label="_TransparentList">
+            <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="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="PathsActivity"
+                android:label="_Paths">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="Transform3dActivity"
+                android:label="_3d">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="SimplePathsActivity"
+                android:label="_SimplePaths">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="AdvancedBlendActivity"
+                android:label="_Advanced Blend">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="FramebufferBlendActivity"
+                android:label="_FramebufferBlend">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+
+        <activity
+                android:name="StackActivity"
+                android:label="_Stacks">
+            <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-hdpi/sunset3.png b/tests/HwAccelerationTest/res/drawable-hdpi/sunset3.png
new file mode 100644
index 0000000..6508b27
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable-hdpi/sunset3.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable/btn_toggle_off.9.png b/tests/HwAccelerationTest/res/drawable/btn_toggle_off.9.png
new file mode 100644
index 0000000..26ee1c2
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable/btn_toggle_off.9.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable/btn_toggle_on.9.png b/tests/HwAccelerationTest/res/drawable/btn_toggle_on.9.png
new file mode 100644
index 0000000..53e95af
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable/btn_toggle_on.9.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable/default_wallpaper.jpg b/tests/HwAccelerationTest/res/drawable/default_wallpaper.jpg
new file mode 100644
index 0000000..5acad94
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable/default_wallpaper.jpg
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/patch.9.png b/tests/HwAccelerationTest/res/drawable/patch.9.png
new file mode 100644
index 0000000..e3b3639
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable/patch.9.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/drawable/sunset3.png b/tests/HwAccelerationTest/res/drawable/sunset3.png
new file mode 100644
index 0000000..6508b27
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable/sunset3.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/layout/form.xml b/tests/HwAccelerationTest/res/layout/form.xml
new file mode 100644
index 0000000..0b17db1
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/form.xml
@@ -0,0 +1,66 @@
+<?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">
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="First name" />
+        
+        <EditText
+            android:layout_width="0dip"
+            android:layout_weight="1.0"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="3dip"
+            android:hint="Enter your first name" />
+        
+    </LinearLayout>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Last name" />
+        
+        <EditText
+            android:layout_width="0dip"
+            android:layout_weight="1.0"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="3dip"
+            android:hint="Enter your last name" />
+        
+    </LinearLayout>
+    
+    <EditText
+        android:layout_width="match_parent"
+        android:layout_weight="1.0"
+        android:layout_height="0dip"
+        android:background="#ff7f0000" />
+    
+</LinearLayout>
diff --git a/tests/HwAccelerationTest/res/layout/labels.xml b/tests/HwAccelerationTest/res/layout/labels.xml
new file mode 100644
index 0000000..695a2cc
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/labels.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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="200dip"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:text="This is a long text view for ellipsizing" />
+
+    <TextView
+        android:layout_width="200dip"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:gravity="center_horizontal"
+        android:text="This is a very long text view for ellipsizing" />
+
+    <TextView
+        android:layout_width="200dip"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:gravity="right"
+        android:text="This is a very long text view for ellipsizing" />
+   
+</LinearLayout>
diff --git a/tests/HwAccelerationTest/res/layout/list_activity.xml b/tests/HwAccelerationTest/res/layout/list_activity.xml
new file mode 100644
index 0000000..6bba370
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/list_activity.xml
@@ -0,0 +1,78 @@
+<?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">
+
+    <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>
+    
+    <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/res/layout/stack.xml b/tests/HwAccelerationTest/res/layout/stack.xml
new file mode 100644
index 0000000..b4d2d73a
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/stack.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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:paddingTop="0dp"
+    android:paddingBottom="0dp"
+    android:paddingLeft="12dp"
+    android:paddingRight="12dp"
+    android:focusable="true">
+    <StackView
+        android:id="@+id/stack_view" 
+        android:layout_width="348px" 
+        android:layout_height="374px"
+        android:layout_gravity="center"
+        android:background="#00000000"
+        android:cacheColorHint="#00000000"
+        android:autoStart="true" />
+</FrameLayout>
diff --git a/tests/HwAccelerationTest/res/layout/stack_item.xml b/tests/HwAccelerationTest/res/layout/stack_item.xml
new file mode 100644
index 0000000..3504018
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/stack_item.xml
@@ -0,0 +1,38 @@
+<?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:id="@+id/stack_item"  
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"> 
+    <FrameLayout  
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"> 
+        <ImageView android:id="@+id/textview_icon"
+            android:layout_height="250dip"
+            android:layout_width="250dip"
+            android:layout_gravity="center" />
+        <TextView android:id="@+id/mini_text"
+            android:layout_width="wrap_content" 
+            android:layout_height="wrap_content" 
+            android:layout_gravity="center" />
+    </FrameLayout>
+    <TextView 
+        android:layout_width="wrap_content" 
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:layout_gravity="center" />
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/res/layout/text_large.xml b/tests/HwAccelerationTest/res/layout/text_large.xml
new file mode 100644
index 0000000..85b374c
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/text_large.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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="6dip"
+    android:layout_marginLeft="6dip"
+    android:textAppearance="?android:attr/textAppearanceLargeInverse"
+    android:text="Widget "/> 
diff --git a/tests/HwAccelerationTest/res/layout/text_medium.xml b/tests/HwAccelerationTest/res/layout/text_medium.xml
new file mode 100644
index 0000000..8e195e6
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/text_medium.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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="6dip"
+    android:layout_marginLeft="6dip"
+    android:textAppearance="?android:attr/textAppearanceMediumInverse"
+    android:text="Widget "/> 
diff --git a/tests/HwAccelerationTest/res/layout/text_small.xml b/tests/HwAccelerationTest/res/layout/text_small.xml
new file mode 100644
index 0000000..45eee60
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/text_small.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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="6dip"
+    android:layout_marginLeft="6dip"
+    android:textAppearance="?android:attr/textAppearanceSmallInverse"
+    android:text="Widget "/> 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedBlendActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedBlendActivity.java
new file mode 100644
index 0000000..5baa20c
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedBlendActivity.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.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.ComposeShader;
+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 AdvancedBlendActivity 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 ComposeShader mComposeShader;
+        private ComposeShader mCompose2Shader;
+        private ComposeShader mCompose3Shader;
+        private ComposeShader mCompose4Shader;
+        private ComposeShader mCompose5Shader;
+        private ComposeShader mCompose6Shader;
+        private BitmapShader mScaled2Shader;
+
+        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.BLACK, Color.WHITE, Shader.TileMode.CLAMP);
+            
+            mComposeShader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.DARKEN);
+            mCompose2Shader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.LIGHTEN);
+            mCompose3Shader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.MULTIPLY);
+            mCompose4Shader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.SCREEN);
+            mCompose5Shader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.ADD);
+            mCompose6Shader = new ComposeShader(mHorGradient, mScaledShader,
+                    PorterDuff.Mode.OVERLAY);
+
+            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.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mCompose3Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.restore();
+            
+            canvas.save();
+            canvas.translate(40.0f + mDrawWidth + 40.0f, 40.0f);
+            
+            mPaint.setShader(mCompose4Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mCompose5Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mCompose6Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+            
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java
new file mode 100644
index 0000000..27974e7
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class AdvancedGradientsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new GradientsView(this));
+    }
+
+    static class GradientsView extends View {
+        private final Paint mPaint;
+        private final SweepGradient mSweepGradient;
+        private final RadialGradient mRadialGradient;
+        private final Matrix mMatrix;
+        private final Matrix mMatrix2;
+        private final Matrix mMatrix3;
+
+        GradientsView(Context c) {
+            super(c);
+
+            mSweepGradient = new SweepGradient(0.0f, 0.0f, 0xff000000, 0xffffffff);
+            mRadialGradient = new RadialGradient(0.0f, 0.0f, 100.0f, 0xff000000, 0xffffffff,
+                    Shader.TileMode.MIRROR);
+            
+            mMatrix = new Matrix();
+            mMatrix.setRotate(-45, 0.0f, 0.0f);
+            mMatrix.postTranslate(100.0f, 100.0f);
+
+            mMatrix2 = new Matrix();
+            mMatrix2.setScale(1.0f, 2.0f);
+            mMatrix2.postRotate(-45, 0.0f, 0.0f);
+
+            mMatrix3 = new Matrix();
+            mMatrix3.setTranslate(100.0f, 100.0f);            
+            
+            mPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+
+            canvas.save();
+            canvas.translate(130.0f, 100.0f);
+
+            mSweepGradient.setLocalMatrix(mMatrix3);
+            mPaint.setShader(mSweepGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+
+            canvas.translate(400.0f, 000.0f);
+            
+            mSweepGradient.setLocalMatrix(mMatrix);
+            mPaint.setShader(mSweepGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);            
+
+            canvas.translate(400.0f, 000.0f);
+            
+            mSweepGradient.setLocalMatrix(mMatrix2);
+            mPaint.setShader(mSweepGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+
+            canvas.translate(-800.0f, 300.0f);
+
+            mRadialGradient.setLocalMatrix(null);
+            mPaint.setShader(mRadialGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+            
+            canvas.translate(400.0f, 000.0f);
+            
+            mRadialGradient.setLocalMatrix(mMatrix);
+            mPaint.setShader(mRadialGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+
+            canvas.translate(400.0f, 000.0f);
+            
+            mRadialGradient.setLocalMatrix(mMatrix2);
+            mPaint.setShader(mRadialGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+            
+            
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java
new file mode 100644
index 0000000..1a68a93
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/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.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/android/test/hwui/BitmapsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsActivity.java
new file mode 100644
index 0000000..40543530
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/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.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/android/test/hwui/BitmapsAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
new file mode 100644
index 0000000..ef49c7f
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.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.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class BitmapsAlphaActivity 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);
+    }
+
+    static class BitmapsView extends View {
+        private Paint mBitmapPaint;
+        private final Bitmap mBitmap1;
+        private final Bitmap mBitmap2;
+        private Bitmap mBitmap3;
+
+        BitmapsView(Context c) {
+            super(c);
+
+            Log.d("OpenGLRenderer", "Loading sunset1, default options");
+            mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            Log.d("OpenGLRenderer", "Loading sunset2, default options");
+            mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset2);
+            Log.d("OpenGLRenderer", "Loading sunset3, forcing ARGB-8888");
+            BitmapFactory.Options opts = new BitmapFactory.Options();
+            opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+            mBitmap3 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset3, opts);
+            Log.d("OpenGLRenderer", "    has bitmap alpha? " + mBitmap3.hasAlpha());
+
+            mBitmapPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            
+            Log.d("OpenGLRenderer", "================= Draw");
+
+            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);
+            
+            canvas.translate(0.0f, mBitmap2.getHeight());
+            canvas.translate(0.0f, 25.0f);
+            canvas.drawBitmap(mBitmap3, 0.0f, 0.0f, null);
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsRectActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsRectActivity.java
new file mode 100644
index 0000000..b192209
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/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.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/android/test/hwui/ColorFiltersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersActivity.java
new file mode 100644
index 0000000..09d63d6
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/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.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/android/test/hwui/FramebufferBlendActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/FramebufferBlendActivity.java
new file mode 100644
index 0000000..1556bae
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/FramebufferBlendActivity.java
@@ -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.
+ */
+
+
+package com.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.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class FramebufferBlendActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new BlendView(this));
+    }
+
+    static class BlendView extends View {
+        private int mTexWidth;
+        private int mTexHeight;
+        private Paint mPaint;
+        private LinearGradient mHorGradient;
+        private Bitmap mTexture;
+
+        BlendView(Context c) {
+            super(c);
+
+            mTexture = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mTexWidth = mTexture.getWidth();
+            mTexHeight = mTexture.getHeight();
+
+            mHorGradient = new LinearGradient(0.0f, 0.0f, mTexWidth, 0.0f,
+                    Color.BLACK, Color.WHITE, Shader.TileMode.CLAMP);
+
+            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);
+
+            drawBlendedBitmap(canvas, PorterDuff.Mode.DARKEN);
+            drawBlendedBitmap(canvas, PorterDuff.Mode.LIGHTEN);
+            drawBlendedBitmap(canvas, PorterDuff.Mode.MULTIPLY);
+
+            canvas.restore();
+
+            canvas.save();
+            canvas.translate(40.0f + mTexWidth + 40.0f, 40.0f);
+
+            drawBlendedBitmap(canvas, PorterDuff.Mode.SCREEN);
+            drawBlendedBitmap(canvas, PorterDuff.Mode.ADD);
+            drawBlendedBitmapInverse(canvas, PorterDuff.Mode.OVERLAY);
+
+            canvas.restore();
+        }
+
+        private void drawBlendedBitmap(Canvas canvas, PorterDuff.Mode mode) {
+            mPaint.setShader(null);
+            mPaint.setXfermode(null);
+            canvas.drawBitmap(mTexture, 0.0f, 0.0f, mPaint);
+
+            mPaint.setShader(mHorGradient);
+            mPaint.setXfermode(new PorterDuffXfermode(mode));
+            canvas.drawRect(0.0f, 0.0f, mTexWidth, mTexHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mTexHeight);
+        }
+
+        private void drawBlendedBitmapInverse(Canvas canvas, PorterDuff.Mode mode) {
+            mPaint.setXfermode(null);
+            mPaint.setShader(mHorGradient);
+            canvas.drawRect(0.0f, 0.0f, mTexWidth, mTexHeight, mPaint);
+
+            mPaint.setXfermode(new PorterDuffXfermode(mode));
+            mPaint.setShader(null);
+            canvas.drawBitmap(mTexture, 0.0f, 0.0f, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mTexHeight);
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
new file mode 100644
index 0000000..769bfdd
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.SeekBar;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class GradientsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final FrameLayout layout = new FrameLayout(this);
+        final ShadersView shadersView = new ShadersView(this);
+        final GradientView gradientView = new GradientView(this);
+        final SeekBar rotateView = new SeekBar(this);
+        rotateView.setMax(360);
+        rotateView.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                gradientView.setRotationY((float)progress);
+            }
+        });
+        
+        layout.addView(shadersView);
+        layout.addView(gradientView, new FrameLayout.LayoutParams(
+                200, 200, Gravity.CENTER));
+        layout.addView(rotateView, new FrameLayout.LayoutParams(
+                300, FrameLayout.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM));
+
+        setContentView(layout);
+    }
+    
+    static class GradientView extends View {
+        private final Paint mPaint;
+
+        GradientView(Context c) {
+            super(c);
+
+            LinearGradient gradient = new LinearGradient(0, 0, 200, 0, 0xFF000000, 0,
+                    Shader.TileMode.CLAMP);
+            mPaint = new Paint();
+            mPaint.setShader(gradient);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            setMeasuredDimension(200, 200);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mPaint);
+        }
+    }
+
+    static class ShadersView extends View {
+        private final Paint mPaint;
+        private final float mDrawWidth;
+        private final float mDrawHeight;
+        private final LinearGradient mGradient;
+        private final Matrix mMatrix;
+
+        ShadersView(Context c) {
+            super(c);
+
+            mDrawWidth = 200;
+            mDrawHeight = 200;
+
+            mGradient = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+            mMatrix = new Matrix();
+
+            mPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+
+            // Gradients
+            canvas.save();
+            float top = 40.0f;
+            float right = 40.0f + mDrawWidth;
+            float left = 40.0f;
+            float bottom = 40.0f + mDrawHeight;
+
+            mPaint.setShader(mGradient);
+
+            mMatrix.setScale(1, mDrawWidth);
+            mMatrix.postRotate(90);
+            mMatrix.postTranslate(right, top);
+            mGradient.setLocalMatrix(mMatrix);
+            canvas.drawRect(right - mDrawWidth, top, right, top + mDrawHeight, mPaint);
+
+            top += 40.0f + mDrawHeight;
+            bottom += 40.0f + mDrawHeight;
+            
+            mMatrix.setScale(1, mDrawHeight);
+            mMatrix.postTranslate(left, top);
+            mGradient.setLocalMatrix(mMatrix);
+            canvas.drawRect(left, top, right, top + mDrawHeight, mPaint);
+            
+            left += 40.0f + mDrawWidth;
+            right += 40.0f + mDrawWidth;
+            top -= 40.0f + mDrawHeight;
+            bottom -= 40.0f + mDrawHeight;
+
+            mMatrix.setScale(1, mDrawHeight);
+            mMatrix.postRotate(180);
+            mMatrix.postTranslate(left, bottom);
+            mGradient.setLocalMatrix(mMatrix);
+            canvas.drawRect(left, bottom - mDrawHeight, right, bottom, mPaint);
+
+            top += 40.0f + mDrawHeight;
+            bottom += 40.0f + mDrawHeight;
+            
+            mMatrix.setScale(1, mDrawWidth);
+            mMatrix.postRotate(-90);
+            mMatrix.postTranslate(left, top);
+            mGradient.setLocalMatrix(mMatrix);
+            canvas.drawRect(left, top, left + mDrawWidth, bottom, mPaint);
+           
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LabelsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LabelsActivity.java
new file mode 100644
index 0000000..bae0500
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LabelsActivity.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 com.android.test.hwui;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class LabelsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.labels);
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LayersActivity.java
new file mode 100644
index 0000000..b705117
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/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.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/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
new file mode 100644
index 0000000..0dc836d
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
@@ -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.
+ */
+
+package com.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);
+
+            mSmallPaint.setAntiAlias(false);
+            canvas.drawLine(0.0f, 0.0f, 400.0f, 0.0f, mSmallPaint);
+            mSmallPaint.setAntiAlias(true);
+            canvas.drawLine(0.0f, 0.0f, 0.0f, 400.0f, mSmallPaint);
+            canvas.drawLine(0.0f, 400.0f, 400.0f, 400.0f, mSmallPaint);
+            
+            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/android/test/hwui/ListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
new file mode 100644
index 0000000..8fd4f6b
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.ContextMenu;
+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);
+        
+        registerForContextMenu(list);
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, v, menuInfo);
+        menu.setHeaderTitle("Context menu");
+        menu.add("List item 1");
+        menu.add("List item 2");
+        menu.add("List item 3");
+    }
+
+    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/android/test/hwui/MoreShadersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MoreShadersActivity.java
new file mode 100644
index 0000000..182a474
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/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.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/android/test/hwui/NewLayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java
new file mode 100644
index 0000000..d9a2893
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.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.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 NewLayersActivity 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();
+            mLayerPaint.setAlpha(127);
+            mRectPaint = new Paint();
+            mRectPaint.setAntiAlias(true);
+            mRectPaint.setTextSize(24.0f);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(128, 255, 128);
+
+            canvas.save();
+
+            canvas.translate(140.0f, 100.0f);
+            drawStuff(canvas, Canvas.ALL_SAVE_FLAG);
+
+            canvas.translate(0.0f, 200.0f);
+            drawStuff(canvas, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
+            
+            canvas.restore();
+        }
+
+        private void drawStuff(Canvas canvas, int saveFlags) {
+            int count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint, saveFlags);
+
+            mRectPaint.setColor(0x7fff0000);
+            canvas.drawRect(-20.0f, -20.0f, 220.0f, 120.0f, mRectPaint);
+
+            mRectPaint.setColor(0xff000000);
+            canvas.drawText("This is a very long string to overlap between layers and framebuffer",
+                    -100.0f, 50.0f, mRectPaint);
+
+            canvas.restoreToCount(count);
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/NinePatchesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/NinePatchesActivity.java
new file mode 100644
index 0000000..7410f79
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/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.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/android/test/hwui/PathsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java
new file mode 100644
index 0000000..8d9bf37
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class PathsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final PathsView view = new PathsView(this);
+        setContentView(view);
+    }
+
+    static class PathsView extends View {
+        private final Bitmap mBitmap1;
+        private final Paint mSmallPaint;
+        private final Paint mMediumPaint;
+        private final Paint mLargePaint;
+        private final BitmapShader mShader;
+        private final Path mPath;
+        private final RectF mPathBounds;
+        private final Paint mBoundsPaint;
+        private final Bitmap mBitmap;
+        private final float mOffset;
+        private final Paint mLinePaint;
+
+        PathsView(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);
+            mSmallPaint.setStyle(Paint.Style.STROKE);
+            
+            mLinePaint = new Paint();
+            mLinePaint.setAntiAlias(true);
+            mLinePaint.setColor(0xffff00ff);
+            mLinePaint.setStrokeWidth(1.0f);
+            mLinePaint.setStyle(Paint.Style.STROKE);
+
+            mMediumPaint = new Paint();
+            mMediumPaint.setAntiAlias(true);
+            mMediumPaint.setColor(0xff0000ff);
+            mMediumPaint.setStrokeWidth(10.0f);
+            mMediumPaint.setStyle(Paint.Style.STROKE);
+
+            mLargePaint = new Paint();
+            mLargePaint.setAntiAlias(true);
+            mLargePaint.setColor(0xff00ff00);
+            mLargePaint.setStrokeWidth(15.0f);
+            mLargePaint.setStyle(Paint.Style.FILL);
+
+            mShader = new BitmapShader(mBitmap1, BitmapShader.TileMode.MIRROR,
+                    BitmapShader.TileMode.MIRROR);
+
+            mPath = new Path();
+            mPath.moveTo(0.0f, 0.0f);
+            mPath.cubicTo(0.0f, 0.0f, 100.0f, 150.0f, 100.0f, 200.0f);
+            mPath.cubicTo(100.0f, 200.0f, 50.0f, 300.0f, -80.0f, 200.0f);
+            mPath.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
+
+            mPathBounds = new RectF();
+            mPath.computeBounds(mPathBounds, true);
+
+            mBoundsPaint = new Paint();
+            mBoundsPaint.setColor(0x4000ff00);
+
+            mOffset = mMediumPaint.getStrokeWidth();
+            final int width = (int) (mPathBounds.width() + mOffset * 3.0f + 0.5f);
+            final int height = (int) (mPathBounds.height() + mOffset * 3.0f + 0.5f);
+            mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
+            Canvas canvas = new Canvas(mBitmap);
+            canvas.translate(-mPathBounds.left + mOffset * 1.5f, -mPathBounds.top + mOffset * 1.5f);
+            canvas.drawPath(mPath, mMediumPaint);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.drawARGB(255, 255, 255, 255);
+
+            canvas.save();
+            canvas.translate(200.0f, 60.0f);
+            canvas.drawPath(mPath, mSmallPaint);
+            
+            canvas.translate(350.0f, 0.0f);
+            canvas.drawPath(mPath, mMediumPaint);
+
+            mLargePaint.setShader(mShader);
+            canvas.translate(350.0f, 0.0f);
+            canvas.drawPath(mPath, mLargePaint);
+            mLargePaint.setShader(null);
+            canvas.restore();
+            
+            canvas.save();
+            canvas.translate(200.0f, 360.0f);
+            canvas.drawPath(mPath, mSmallPaint);
+            canvas.drawRect(mPathBounds, mBoundsPaint);
+            
+            canvas.translate(350.0f, 0.0f);
+            canvas.drawBitmap(mBitmap, mPathBounds.left - mOffset * 1.5f,
+                    mPathBounds.top - mOffset * 1.5f, null);
+            canvas.drawRect(mPathBounds, mBoundsPaint);
+            canvas.drawLine(0.0f, -360.0f, 0.0f, 500.0f, mLinePaint);
+
+            mLargePaint.setShader(mShader);
+            canvas.translate(350.0f, 0.0f);
+            canvas.drawPath(mPath, mLargePaint);
+            canvas.drawRect(mPathBounds, mBoundsPaint);
+            mLargePaint.setShader(null);
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
new file mode 100644
index 0000000..5192bfe
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
@@ -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.
+ */
+
+package com.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.util.Log;
+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);
+
+            int count = canvas.getSaveCount();
+            Log.d("OpenGLRenderer", "count=" + count);
+            count = canvas.save();
+            Log.d("OpenGLRenderer", "count after save=" + count);
+            count = canvas.getSaveCount();
+            Log.d("OpenGLRenderer", "getSaveCount after save=" + count);
+            canvas.restore();
+            count = canvas.getSaveCount();
+            Log.d("OpenGLRenderer", "count after restore=" + count);
+            canvas.save();
+            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            canvas.save();
+            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            canvas.save();
+            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            canvas.restoreToCount(count);
+            count = canvas.getSaveCount();
+            Log.d("OpenGLRenderer", "count after restoreToCount=" + count);
+            count = canvas.saveLayer(0, 0, 10, 10, mBitmapPaint, Canvas.ALL_SAVE_FLAG);
+            Log.d("OpenGLRenderer", "count after saveLayer=" + count);
+            count = canvas.getSaveCount();
+            Log.d("OpenGLRenderer", "getSaveCount after saveLayer=" + count);
+            canvas.restore();
+            count = canvas.getSaveCount();
+            Log.d("OpenGLRenderer", "count after restore=" + count);
+
+            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/android/test/hwui/ResizeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ResizeActivity.java
new file mode 100644
index 0000000..04f9de1
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ResizeActivity.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 com.android.test.hwui;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ResizeActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        setContentView(R.layout.form);
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RotationActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RotationActivity.java
new file mode 100644
index 0000000..5c309b4
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/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.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/android/test/hwui/ShadersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java
new file mode 100644
index 0000000..2db1071
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.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.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, 1.0f, 0.0f,
+                    Color.RED, Color.GREEN, Shader.TileMode.CLAMP);
+            Matrix m3 = new Matrix();
+            m3.setScale(mDrawHeight, 1.0f);
+            m3.postRotate(-90.0f);
+            m3.postTranslate(0.0f, mDrawHeight);
+            mHorGradient.setLocalMatrix(m3);
+            
+            mDiagGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth / 1.5f, mDrawHeight,
+                    Color.BLUE, Color.MAGENTA, Shader.TileMode.CLAMP);
+            
+            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/android/test/hwui/SimplePathsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/SimplePathsActivity.java
new file mode 100644
index 0000000..c3e18a3
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/SimplePathsActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class SimplePathsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FrameLayout layout = new FrameLayout(this);
+        EditText text = new EditText(this);
+        layout.addView(text, new FrameLayout.LayoutParams(600, 350, Gravity.CENTER));
+        text.setText("This is an example of an EditText widget \n" +
+                "using simple paths to create the selection.");
+        //text.setSelection(0, text.getText().length());
+
+        setContentView(layout);
+    }
+}
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StackActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StackActivity.java
new file mode 100644
index 0000000..5655adf
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StackActivity.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 com.android.test.hwui;
+
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.StackView;
+import android.widget.TextView;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class StackActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.stack);
+
+        StackView stack = (StackView) findViewById(R.id.stack_view);
+        stack.setAdapter(new ArrayAdapter<Drawable>(this, android.R.layout.simple_list_item_1,
+                android.R.id.text1, new Drawable[] {
+            getResources().getDrawable(R.drawable.sunset1),
+            getResources().getDrawable(R.drawable.sunset2),
+        }) {
+            @Override
+            public View getView(int position, View convertView, ViewGroup parent) {
+                View item = convertView;
+                if (item == null) {
+                    item = LayoutInflater.from(getContext()).inflate(
+                            R.layout.stack_item, null, false);                    
+                }
+                ((ImageView) item.findViewById(R.id.textview_icon)).setImageDrawable(
+                        getItem(position % getCount()));
+                ((TextView) item.findViewById(R.id.mini_text)).setText("" + position);
+                return item;
+            }
+        });
+        stack.setDisplayedChild(0);
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextActivity.java
new file mode 100644
index 0000000..eb0df51
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextActivity.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.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;
+        private final Paint mStrikePaint;
+        private final Paint mScaledPaint;
+
+        CustomTextView(Context c) {
+            super(c);
+
+            mMediumPaint = new Paint();
+            mMediumPaint.setAntiAlias(true);
+            mMediumPaint.setColor(0xffff0000);
+
+            mLargePaint = new Paint();
+            mLargePaint.setAntiAlias(true);
+            mLargePaint.setTextSize(36.0f);
+
+            mStrikePaint = new Paint();
+            mStrikePaint.setAntiAlias(true);
+            mStrikePaint.setTextSize(16.0f);
+            mStrikePaint.setUnderlineText(true);
+
+            mScaledPaint = new Paint();
+            mScaledPaint.setAntiAlias(true);
+            mScaledPaint.setTextSize(16.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);
+            mMediumPaint.setShadowLayer(2.5f, 0.0f, 0.0f, 0xff000000);
+            canvas.drawText("Hello OpenGL renderer!", 100, 150, mMediumPaint);
+            mMediumPaint.clearShadowLayer();
+            canvas.drawText("Hello OpenGL renderer!", 100, 200, mLargePaint);
+
+            mLargePaint.setShadowLayer(2.5f, 3.0f, 3.0f, 0xff000000);
+            canvas.drawText("Hello OpenGL renderer!", 100, 400, mLargePaint);
+            mLargePaint.setShadowLayer(3.0f, 3.0f, 3.0f, 0xff00ff00);
+            mLargePaint.setAlpha(100);
+            canvas.drawText("Hello OpenGL renderer!", 100, 500, mLargePaint);
+            mLargePaint.setAlpha(255);
+            mLargePaint.clearShadowLayer();
+            
+            canvas.drawText("Hello OpenGL renderer!", 500, 40, mStrikePaint);
+            mStrikePaint.setStrikeThruText(true);
+            canvas.drawText("Hello OpenGL renderer!", 500, 70, mStrikePaint);
+            mStrikePaint.setUnderlineText(false);
+            canvas.drawText("Hello OpenGL renderer!", 500, 100, mStrikePaint);
+            mStrikePaint.setStrikeThruText(false);
+            mStrikePaint.setUnderlineText(true);
+            
+            mScaledPaint.setTextScaleX(0.5f);
+            canvas.drawText("Hello OpenGL renderer!", 500, 200, mScaledPaint);
+            mScaledPaint.setTextScaleX(2.0f);
+            canvas.drawText("Hello OpenGL renderer!", 500, 230, mScaledPaint);
+            mScaledPaint.setTextScaleX(1.0f);
+            canvas.drawText("Hello OpenGL renderer!", 500, 260, mScaledPaint);
+            
+            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/android/test/hwui/TextGammaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextGammaActivity.java
new file mode 100644
index 0000000..773d390
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextGammaActivity.java
@@ -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.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class TextGammaActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final LinearLayout layout = new LinearLayout(this);
+        layout.setOrientation(LinearLayout.VERTICAL);
+
+        final GammaTextView gamma = new GammaTextView(this);
+        layout.addView(gamma, new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT
+        ));
+
+        setContentView(layout);
+        
+        layout.post(new Runnable() {
+            @Override
+            public void run() {
+                Bitmap b = Bitmap.createBitmap(gamma.getWidth(), gamma.getHeight(),
+                        Bitmap.Config.ARGB_8888);
+                Canvas c = new Canvas(b);
+                c.drawColor(0, PorterDuff.Mode.CLEAR);
+                gamma.draw(c);
+
+                ImageView image = new ImageView(TextGammaActivity.this);
+                image.setImageBitmap(b);
+
+                layout.addView(image, new LinearLayout.LayoutParams(
+                        LinearLayout.LayoutParams.WRAP_CONTENT,
+                        LinearLayout.LayoutParams.WRAP_CONTENT
+                ));
+
+                startActivity(new Intent(TextGammaActivity.this, SubGammaActivity.class));
+            }
+        });
+
+        getWindow().setBackgroundDrawable(new ColorDrawable(0xffffffff));
+    }
+
+    static class GammaTextView extends LinearLayout {
+        GammaTextView(Context c) {
+            super(c);
+
+            setBackgroundColor(0xffffffff);
+
+            final LayoutInflater inflater = LayoutInflater.from(c);
+            inflater.inflate(R.layout.text_large, this, true);
+            inflater.inflate(R.layout.text_medium, this, true);
+            inflater.inflate(R.layout.text_small, this, true);
+        }
+    }
+
+    public static class SubGammaActivity extends Activity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            final LinearLayout layout = new LinearLayout(this);
+            layout.setOrientation(LinearLayout.VERTICAL);
+        
+            final GammaTextView gamma = new GammaTextView(this);
+            final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT
+            );
+            lp.setMargins(0, 74, 0, 0);
+            layout.addView(gamma, lp);
+
+            setContentView(layout);
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ThinPatchesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ThinPatchesActivity.java
new file mode 100644
index 0000000..4f605fa
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ThinPatchesActivity.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ThinPatchesActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        FrameLayout layout = new FrameLayout(this);
+        PatchView b = new PatchView(this);
+        b.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT));
+        layout.addView(b);
+        layout.setBackgroundColor(0xffffffff);
+        
+        setContentView(layout);
+    }
+
+    private class PatchView extends View {
+        private Drawable mPatch1, mPatch2;
+
+        public PatchView(Activity activity) {
+            super(activity);
+
+            final Resources resources = activity.getResources();
+            mPatch1 = resources.getDrawable(R.drawable.patch);
+            mPatch2 = resources.getDrawable(R.drawable.btn_toggle_on);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            final int width = 100;
+            final int height = 60;
+
+            final int left = (getWidth() - width) / 2;
+            final int top  = (getHeight() - height) / 2;
+
+            mPatch1.setBounds(left, top, left + width, top + height);
+            mPatch1.draw(canvas);
+
+            canvas.translate(0.0f, height + 20.0f);
+            
+            mPatch2.setBounds(left, top, left + width, top + height);
+            mPatch2.draw(canvas);
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Transform3dActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Transform3dActivity.java
new file mode 100644
index 0000000..6df66e6
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Transform3dActivity.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.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Camera;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class Transform3dActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Transform3dView view = new Transform3dView(this);
+        setContentView(view);
+    }
+
+    static class Transform3dView extends View {
+        private final Bitmap mBitmap1;
+        private Camera mCamera;
+        private Matrix mMatrix;
+
+        Transform3dView(Context c) {
+            super(c);
+
+            mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mCamera = new Camera();
+            mMatrix = new Matrix();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.drawARGB(255, 255, 255, 255);
+
+            final float centerX = getWidth() / 2.0f - mBitmap1.getWidth() / 2.0f;
+            final float centerY = getHeight() / 2.0f - mBitmap1.getHeight() / 2.0f;
+            final Camera camera = mCamera;
+            
+            final Matrix matrix = mMatrix;
+
+            rotate(centerX, centerY, camera, matrix, 32.0f);
+            drawBitmap(canvas, centerX, centerY, 0.0f, matrix);
+
+            rotate(centerX, centerY, camera, matrix, 12.0f);
+            drawBitmap(canvas, centerX, centerY, -mBitmap1.getWidth(), matrix);
+            
+            rotate(centerX, centerY, camera, matrix, 52.0f);
+            drawBitmap(canvas, centerX, centerY, mBitmap1.getWidth(), matrix);
+            
+            rotate(centerX, centerY, camera, matrix, 122.0f);
+            drawBitmap(canvas, centerX, centerY, mBitmap1.getWidth() * 2.0f, matrix);
+            
+        }
+
+        private void drawBitmap(Canvas canvas, float centerX, float centerY, float offset,
+                Matrix matrix) {
+            canvas.save();
+            canvas.translate(offset, 0.0f);
+            canvas.concat(matrix);
+            canvas.drawBitmap(mBitmap1, centerX, centerY, null);
+            canvas.restore();
+        }
+
+        private void rotate(float centerX, float centerY, Camera camera,
+                Matrix matrix, float angle) {
+            camera.save();
+            camera.rotateY(angle);
+            camera.getMatrix(matrix);
+            camera.restore();
+
+            matrix.preTranslate(-centerX, -centerY);
+            matrix.postTranslate(centerX, centerY);
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
new file mode 100644
index 0000000..763169a
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.ContextMenu;
+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 TransparentListActivity 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);
+        
+        getWindow().setBackgroundDrawable(getResources().getDrawable(R.drawable.default_wallpaper));
+        setContentView(R.layout.list_activity);
+
+        ListAdapter adapter = new SimpleListAdapter(this);
+
+        ListView list = (ListView) findViewById(R.id.list);
+        list.setAdapter(adapter);
+        list.setCacheColorHint(0);
+        list.setVerticalFadingEdgeEnabled(true);
+
+        registerForContextMenu(list);
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, v, menuInfo);
+        menu.setHeaderTitle("Context menu");
+        menu.add("List item 1");
+        menu.add("List item 2");
+        menu.add("List item 3");
+    }
+
+    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/android/test/hwui/XfermodeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/XfermodeActivity.java
new file mode 100644
index 0000000..411077f
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/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.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/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index b793d62..e30cf4a 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -16,12 +16,14 @@
 
 package com.android.statusbartest;
 
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.ContentResolver;
 import android.content.Intent;
-import android.app.Notification;
-import android.app.NotificationManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
 import android.os.Environment;
 import android.os.Vibrator;
 import android.os.Handler;
@@ -77,6 +79,112 @@
             }
         },
 
+        new Test("Cancel #1") {
+            public void run()
+            {
+                mNM.cancel(1);
+            }
+        },
+
+        new Test("Ticker 1 line") {
+            public void run() {
+                Notification n = new Notification(R.drawable.icon1, "tick tick tick",
+                        mActivityCreateTime);
+                n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
+                            "This is a notification!!!", makeIntent());
+                mNM.notify(1, n);
+            }
+        },
+
+        new Test("Ticker 1 line & icon") {
+            public void run() {
+                Notification n = new Notification(R.drawable.icon1, "tick tick tick",
+                        mActivityCreateTime);
+                n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
+                            "This is a notification!!!", makeIntent());
+                n.tickerIcons = new Bitmap[1];
+                n.tickerIcons[0] = loadBitmap(R.drawable.icon3);
+                mNM.notify(1, n);
+            }
+        },
+
+        new Test("Ticker 2 lines") {
+            public void run() {
+                Notification n = new Notification(R.drawable.icon1, "tick tick tick\ntock tock",
+                        mActivityCreateTime);
+                n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
+                            "This is a notification!!!", makeIntent());
+                mNM.notify(1, n);
+            }
+        },
+
+        new Test("Ticker title") {
+            public void run() {
+                Notification n = new Notification(R.drawable.icon1, null,
+                        mActivityCreateTime);
+                n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
+                            "This is a notification!!!", makeIntent());
+                n.tickerTitle = "This is a title";
+                mNM.notify(1, n);
+            }
+        },
+
+        new Test("Ticker subtitle") {
+            public void run() {
+                Notification n = new Notification(R.drawable.icon1, null,
+                        mActivityCreateTime);
+                n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
+                            "This is a notification!!!", makeIntent());
+                n.tickerSubtitle = "and a subtitle";
+                mNM.notify(1, n);
+            }
+        },
+
+        new Test("Ticker title & subtitle") {
+            public void run() {
+                Notification n = new Notification(R.drawable.icon1, null,
+                        mActivityCreateTime);
+                n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
+                            "This is a notification!!!", makeIntent());
+                n.tickerTitle = "This is a title it is really really longggggg long long long long";
+                n.tickerSubtitle = "and a subtitle it is really really longggggg long long long long long long long long long long long long long long long long";
+                mNM.notify(1, n);
+            }
+        },
+
+        new Test("Ticker text, title & subtitle") {
+            public void run() {
+                Notification n = new Notification(R.drawable.icon1, "not visible",
+                        mActivityCreateTime);
+                n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
+                            "This is a notification!!!", makeIntent());
+                n.tickerTitle = "This is a title";
+                n.tickerSubtitle = "and a subtitle";
+                mNM.notify(1, n);
+            }
+        },
+
+        new Test("Ticker title, subtitle & 2 icons") {
+            public void run() {
+                Notification n = new Notification(R.drawable.icon1, null,
+                        mActivityCreateTime);
+                n.setLatestEventInfo(NotificationTestList.this, "Persistent #1",
+                            "This is a notification!!!", makeIntent());
+                n.tickerTitle = "This is a title";
+                n.tickerSubtitle = "and a subtitle";
+
+                n.tickerIcons = new Bitmap[2];
+                n.tickerIcons[0] = loadBitmap(R.drawable.icon3);
+                n.tickerIcons[1] = loadBitmap(R.drawable.app_gmail);
+
+                mNM.notify(1, n);
+                /*
+                n.tickerIcons[0].recycle();
+                n.tickerIcons[1].recycle();
+                */
+            }
+        },
+
         new Test("No view") {
             public void run() {
                 Notification n = new Notification(R.drawable.icon1, "No view",
@@ -98,12 +206,16 @@
         new Test("Layout") {
             public void run()
             {
+                Notification n;
 
-                mNM.notify(1, new Notification(NotificationTestList.this,
+                n = new Notification(NotificationTestList.this,
                             R.drawable.ic_statusbar_missedcall,
                             null, System.currentTimeMillis()-(1000*60*60*24),
                             "(453) 123-2328",
-                            "", null));
+                            "", null);
+                n.flags |= Notification.FLAG_ONGOING_EVENT;
+
+                mNM.notify(1, n);
 
                 mNM.notify(2, new Notification(NotificationTestList.this,
                             R.drawable.ic_statusbar_email,
@@ -479,7 +591,7 @@
 
         new Test("Persistent #3") {
             public void run() {
-                Notification n = new Notification(R.drawable.icon2, "tock tock tock",
+                Notification n = new Notification(R.drawable.icon2, "tock tock tock\nmooooo",
                         System.currentTimeMillis());
                 n.setLatestEventInfo(NotificationTestList.this, "Persistent #3",
                             "Notify me!!!", makeIntent());
@@ -633,20 +745,6 @@
             }
         },
 
-        new Test("Ticker") {
-            public void run() {
-                Notification not = new Notification(
-                    R.drawable.app_gmail, 
-                    "New mail from joeo@example.com, on the topic of very long ticker texts",
-                    System.currentTimeMillis());
-                not.setLatestEventInfo(NotificationTestList.this,
-                    "A new message awaits",
-                    "The contents are very interesting and important",
-                    makeIntent());
-                mNM.notify(1, not);
-            }
-        },
-
         new Test("Crash") {
             public void run()
             {
@@ -715,5 +813,10 @@
                     time, label, "" + new java.util.Date(time), null));
 
     }
+
+    Bitmap loadBitmap(int resId) {
+        BitmapDrawable bd = (BitmapDrawable)getResources().getDrawable(resId);
+        return Bitmap.createBitmap(bd.getBitmap());
+    }
 }
 
diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
index 438a060..7e69ee4 100644
--- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
+++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
@@ -81,23 +81,32 @@
                 mStatusBarManager.setIcon("speakerphone", R.drawable.stat_sys_phone, 0);
             }
         },
-        new Test("Hide") {
+        new Test("Hide (FLAG_FULLSCREEN)") {
             public void run() {
                 Window win = getWindow();
-                WindowManager.LayoutParams winParams = win.getAttributes();
-                winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
-                win.setAttributes(winParams);
+                win.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+                        WindowManager.LayoutParams.FLAG_FULLSCREEN);
+                Log.d(TAG, "flags=" + Integer.toHexString(win.getAttributes().flags));
             }
         },
-        new Test("Show") {
+        new Test("Show (~FLAG_FULLSCREEN)") {
             public void run() {
                 Window win = getWindow();
-                WindowManager.LayoutParams winParams = win.getAttributes();
-                winParams.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN;
-                win.setAttributes(winParams);
+                win.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+                Log.d(TAG, "flags=" + Integer.toHexString(win.getAttributes().flags));
             }
         },
-        new Test("fullScreenIntent") {
+        new Test("Immersive: Enter") {
+            public void run() {
+                setImmersive(true);
+            }
+        },
+        new Test("Immersive: Exit") {
+            public void run() {
+                setImmersive(false);
+            }
+        },
+        new Test("Priority notification") {
             public void run() {
                 Notification not = new Notification(StatusBarTest.this,
                                 R.drawable.stat_sys_phone,
@@ -107,8 +116,9 @@
                                 "(888) 555-5038",
                                 null
                                 );
+                not.flags |= Notification.FLAG_HIGH_PRIORITY;
                 Intent fullScreenIntent = new Intent(StatusBarTest.this, TestAlertActivity.class);
-                int id = (int)System.currentTimeMillis();
+                int id = (int)System.currentTimeMillis(); // XXX HAX
                 fullScreenIntent.putExtra("id", id);
                 not.fullScreenIntent = PendingIntent.getActivity(
                     StatusBarTest.this,
@@ -158,6 +168,24 @@
                     }, 3000);
             }
         },
+        new Test("Disable Navigation") {
+            public void run() {
+                mStatusBarManager.disable(StatusBarManager.DISABLE_NAVIGATION);
+            }
+        },
+        new Test("Disable everything in 3 sec") {
+            public void run() {
+                mHandler.postDelayed(new Runnable() {
+                        public void run() {
+                            mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND
+                                    | StatusBarManager.DISABLE_NOTIFICATION_ICONS
+                                    | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+                                    | StatusBarManager.DISABLE_SYSTEM_INFO
+                                    | StatusBarManager.DISABLE_NAVIGATION);
+                        }
+                    }, 3000);
+            }
+        },
         new Test("Enable everything") {
             public void run() {
                 mStatusBarManager.disable(0);
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index f71ebb9..eaff0f4 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -198,8 +198,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",
@@ -390,8 +392,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 c8ba904..822262e 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());
@@ -2083,12 +2083,13 @@
     // tag:attribute pairs that should be checked in layout files.
     KeyedVector<String8, NamespaceAttributePair> kLayoutTagAttrPairs;
     addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class");
+    addTagAttrPair(&kLayoutTagAttrPairs, "fragment", NULL, "class");
     addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name");
 
     // tag:attribute pairs that should be checked in xml files.
     KeyedVector<String8, NamespaceAttributePair> kXmlTagAttrPairs;
     addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment");
-    addTagAttrPair(&kXmlTagAttrPairs, "Header", RESOURCES_ANDROID_NAMESPACE, "fragment");
+    addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, "fragment");
 
     const Vector<sp<AaptDir> >& dirs = assets->resDirs();
     const size_t K = dirs.size();
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 29644a6..1d6b18d 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2442,7 +2442,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 8551b0f..cee8546 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/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index d5d315e..1e1aba9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -69,11 +69,6 @@
         throw new UnsupportedOperationException("Can't create Canvas(int)");
     }
 
-    public Canvas(javax.microedition.khronos.opengles.GL gl) {
-        mLogger = null;
-        throw new UnsupportedOperationException("Can't create Canvas(javax.microedition.khronos.opengles.GL)");
-    }
-
     // custom constructors for our use.
     public Canvas(int width, int height, ILayoutLog logger) {
         mLogger = logger;
@@ -1174,15 +1169,6 @@
     }
 
     /* (non-Javadoc)
-     * @see android.graphics.Canvas#getGL()
-     */
-    @Override
-    public GL getGL() {
-        // TODO Auto-generated method stub
-        return super.getGL();
-    }
-
-    /* (non-Javadoc)
      * @see android.graphics.Canvas#getMatrix()
      */
     @Override
@@ -1257,15 +1243,6 @@
     }
 
     /* (non-Javadoc)
-     * @see android.graphics.Canvas#setViewport(int, int)
-     */
-    @Override
-    public void setViewport(int width, int height) {
-        // TODO Auto-generated method stub
-        super.setViewport(width, height);
-    }
-
-    /* (non-Javadoc)
      * @see android.graphics.Canvas#skew(float, float)
      */
     @Override
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 0910d79..4bc8855 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -116,10 +116,10 @@
     }
     
     @Override
-    public View createViewFromTag(String name, AttributeSet attrs) {
+    public View createViewFromTag(View parent, String name, AttributeSet attrs) {
         View view = null;
         try {
-            view = super.createViewFromTag(name, attrs);
+            view = super.createViewFromTag(parent, name, attrs);
         } catch (InflateException e) {
             // try to load the class from using the custom view loader
             try {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index f91f601..0553f5e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -30,6 +30,8 @@
 import com.android.tools.layoutlib.create.MethodAdapter;
 import com.android.tools.layoutlib.create.OverrideMethod;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -46,6 +48,7 @@
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.BridgeInflater;
+import android.view.DragEvent;
 import android.view.InputChannel;
 import android.view.IWindow;
 import android.view.IWindowSession;
@@ -1073,6 +1076,33 @@
         }
 
         @SuppressWarnings("unused")
+        public IBinder prepareDrag(IWindow window, boolean localOnly,
+                int thumbnailWidth, int thumbnailHeight, Surface outSurface)
+                throws RemoteException {
+            // pass for now
+            return null;
+        }
+
+        @SuppressWarnings("unused")
+        public boolean performDrag(IWindow window, IBinder dragToken,
+                float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+                ClipData data)
+                throws RemoteException {
+            // pass for now
+            return false;
+        }
+
+        @SuppressWarnings("unused")
+        public void dragRecipientEntered(IWindow window) throws RemoteException {
+            // pass for now
+        }
+
+        @SuppressWarnings("unused")
+        public void dragRecipientExited(IWindow window) throws RemoteException {
+            // pass for now
+        }
+
+        @SuppressWarnings("unused")
         public void setWallpaperPosition(IBinder window, float x, float y,
             float xStep, float yStep) {
             // pass for now.
@@ -1170,6 +1200,11 @@
             // pass for now.
         }
 
+        @SuppressWarnings("unused")
+        public void dispatchDragEvent(DragEvent event) {
+            // pass for now.
+        }
+
         public IBinder asBinder() {
             // pass for now.
             return null;
diff --git a/tools/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/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java
index e0dc55f..adb693d 100644
--- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java
+++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java
@@ -17,6 +17,8 @@
 package com.android.layoutlib.bridge;
 
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
 
 import junit.framework.TestCase;
 
@@ -26,7 +28,8 @@
         // TODO: we want to test all the classes. For now only Paint passes the tests.
 //        final String[] classes = CreateInfo.RENAMED_CLASSES;
         final String[] classes = new String[] {
-                "android.graphics.Paint",               "android.graphics._Original_Paint"
+                "android.graphics.Paint",               "android.graphics._Original_Paint",
+                "android.graphics.Canvas",               "android.graphics._Original_Canvas",
         };
         final int count = classes.length;
         for (int i = 0 ; i < count ; i += 2) {
@@ -52,12 +55,21 @@
         Method[] oldClassMethods = oldClass.getDeclaredMethods();
 
         for (Method oldMethod : oldClassMethods) {
-            // we ignore anything that starts with native
+            // we ignore anything that starts with native. This is because the class we are looking
+            // at has already been modified to remove the native modifiers.
             if (oldMethod.getName().startsWith("native")) {
                 continue;
             }
+
+            // or static and private
+            int privateStatic = Modifier.STATIC | Modifier.PRIVATE;
+            if ((oldMethod.getModifiers() & privateStatic) == privateStatic) {
+                continue;
+            }
+
             boolean found = false;
             for (Method newMethod : newClassMethods) {
+
                 if (compareMethods(newClass, newMethod, oldClass, oldMethod)) {
                     found = true;
                     break;
@@ -65,7 +77,31 @@
             }
 
             if (found == false) {
-                fail(String.format("Unable to find %1$s", oldMethod.toGenericString()));
+                // compute a full class name that's long but not too long.
+                StringBuilder sb = new StringBuilder(oldMethod.getName() + "(");
+                Type[] params = oldMethod.getGenericParameterTypes();
+                for (int j = 0; j < params.length; j++) {
+                    if (params[j] instanceof Class) {
+                        Class theClass = (Class)params[j];
+                        sb.append(theClass.getName());
+                        int dimensions = 0;
+                        while (theClass.isArray()) {
+                            dimensions++;
+                            theClass = theClass.getComponentType();
+                        }
+                        for (int i = 0; i < dimensions; i++) {
+                            sb.append("[]");
+                        }
+
+                    } else {
+                        sb.append(params[j].toString());
+                    }
+                if (j < (params.length - 1))
+                    sb.append(",");
+                }
+                sb.append(")");
+
+                fail(String.format("Missing %1$s.%2$s", newClass.getName(), sb.toString()));
             }
         }
 
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index c59e20d..65a64cd 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -195,5 +195,22 @@
 bridge will provide its own implementation.
 
 
+- References -
+--------------
+
+
+The JVM Specification 2nd edition:
+  http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
+
+Understanding bytecode:
+  http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
+
+Bytecode opcode list:
+  http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
+
+ASM user guide:
+  http://download.forge.objectweb.org/asm/asm-guide.pdf
+
+
 --
 end
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
new file mode 100644
index 0000000..9a48ea6
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a method that has been converted to a delegate by layoutlib_create.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LayoutlibDelegate {
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 7b55ed3e..590923f 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -28,9 +28,9 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.Map.Entry;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
 
@@ -60,38 +60,55 @@
      *  old-FQCN to rename and they get erased as they get renamed. At the end, classes still
      *  left here are not in the code base anymore and thus were not renamed. */
     private HashSet<String> mClassesNotRenamed;
-    /** A map { FQCN => map { list of return types to delete from the FQCN } }. */
+    /** A map { FQCN => set { list of return types to delete from the FQCN } }. */
     private HashMap<String, Set<String>> mDeleteReturns;
+    /** A map { FQCN => set { method names } } of methods to rewrite as delegates.
+     *  The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
+    private final HashMap<String, Set<String>> mDelegateMethods;
 
     /**
      * Creates a new generator that can generate the output JAR with the stubbed classes.
-     * 
+     *
      * @param log Output logger.
      * @param osDestJar The path of the destination JAR to create.
-     * @param injectClasses The list of class from layoutlib_create to inject in layoutlib.
-     * @param stubMethods The list of methods to stub out. Each entry must be in the form
-     *          "package.package.OuterClass$InnerClass#MethodName".
-     * @param renameClasses The list of classes to rename, must be an even list: the binary FQCN
-     *          of class to replace followed by the new FQCN.
-     * @param deleteReturns List of classes for which the methods returning them should be deleted.
-     * The array contains a list of null terminated section starting with the name of the class
-     * to rename in which the methods are deleted, followed by a list of return types identifying
-     * the methods to delete.
+     * @param createInfo Creation parameters. Must not be null.
      */
-    public AsmGenerator(Log log, String osDestJar,
-            Class<?>[] injectClasses,
-            String[] stubMethods,
-            String[] renameClasses, String[] deleteReturns) {
+    public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
         mLog = log;
         mOsDestJar = osDestJar;
-        mInjectClasses = injectClasses != null ? injectClasses : new Class<?>[0];
-        mStubMethods = stubMethods != null ? new HashSet<String>(Arrays.asList(stubMethods)) :
-                                             new HashSet<String>();
+        mInjectClasses = createInfo.getInjectedClasses();
+        mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
+
+        // Create the map/set of methods to change to delegates
+        mDelegateMethods = new HashMap<String, Set<String>>();
+        for (String signature : createInfo.getDelegateMethods()) {
+            int pos = signature.indexOf('#');
+            if (pos <= 0 || pos >= signature.length() - 1) {
+                continue;
+            }
+            String className = binaryToInternalClassName(signature.substring(0, pos));
+            String methodName = signature.substring(pos + 1);
+            Set<String> methods = mDelegateMethods.get(className);
+            if (methods == null) {
+                methods = new HashSet<String>();
+                mDelegateMethods.put(className, methods);
+            }
+            methods.add(methodName);
+        }
+        for (String className : createInfo.getDelegateClassNatives()) {
+            Set<String> methods = mDelegateMethods.get(className);
+            if (methods == null) {
+                methods = new HashSet<String>();
+                mDelegateMethods.put(className, methods);
+            }
+            methods.add(DelegateClassAdapter.ALL_NATIVES);
+        }
 
         // Create the map of classes to rename.
         mRenameClasses = new HashMap<String, String>();
         mClassesNotRenamed = new HashSet<String>();
-        int n = renameClasses == null ? 0 : renameClasses.length;
+        String[] renameClasses = createInfo.getRenamedClasses();
+        int n = renameClasses.length;
         for (int i = 0; i < n; i += 2) {
             assert i + 1 < n;
             // The ASM class names uses "/" separators, whereas regular FQCN use "."
@@ -100,38 +117,37 @@
             mRenameClasses.put(oldFqcn, newFqcn);
             mClassesNotRenamed.add(oldFqcn);
         }
-        
+
         // create the map of renamed class -> return type of method to delete.
         mDeleteReturns = new HashMap<String, Set<String>>();
-        if (deleteReturns != null) {
-            Set<String> returnTypes = null;
-            String renamedClass = null;
-            for (String className : deleteReturns) {
-                // if we reach the end of a section, add it to the main map
-                if (className == null) {
-                    if (returnTypes != null) {
-                        mDeleteReturns.put(renamedClass, returnTypes);
-                    }
-                    
-                    renamedClass = null;
-                    continue;
+        String[] deleteReturns = createInfo.getDeleteReturns();
+        Set<String> returnTypes = null;
+        String renamedClass = null;
+        for (String className : deleteReturns) {
+            // if we reach the end of a section, add it to the main map
+            if (className == null) {
+                if (returnTypes != null) {
+                    mDeleteReturns.put(renamedClass, returnTypes);
                 }
-    
-                // if the renamed class is null, this is the beginning of a section
-                if (renamedClass == null) {
-                    renamedClass = binaryToInternalClassName(className);
-                    continue;
-                }
-    
-                // just a standard return type, we add it to the list.
-                if (returnTypes == null) {
-                    returnTypes = new HashSet<String>();
-                }
-                returnTypes.add(binaryToInternalClassName(className));
+
+                renamedClass = null;
+                continue;
             }
+
+            // if the renamed class is null, this is the beginning of a section
+            if (renamedClass == null) {
+                renamedClass = binaryToInternalClassName(className);
+                continue;
+            }
+
+            // just a standard return type, we add it to the list.
+            if (returnTypes == null) {
+                returnTypes = new HashSet<String>();
+            }
+            returnTypes.add(binaryToInternalClassName(className));
         }
     }
-    
+
     /**
      * Returns the list of classes that have not been renamed yet.
      * <p/>
@@ -163,12 +179,12 @@
     public void setDeps(Map<String, ClassReader> deps) {
         mDeps = deps;
     }
-    
+
     /** Gets the map of classes to output as-is, except if they have native methods */
     public Map<String, ClassReader> getKeep() {
         return mKeep;
     }
-    
+
     /** Gets the map of dependencies that must be completely stubbed */
     public Map<String, ClassReader> getDeps() {
         return mDeps;
@@ -177,7 +193,7 @@
     /** Generates the final JAR */
     public void generate() throws FileNotFoundException, IOException {
         TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
-        
+
         for (Class<?> clazz : mInjectClasses) {
             String name = classToEntryPath(clazz);
             InputStream is = ClassLoader.getSystemResourceAsStream(name);
@@ -186,7 +202,7 @@
             name = classNameToEntryPath(transformName(cr.getClassName()));
             all.put(name, b);
         }
-        
+
         for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
             ClassReader cr = entry.getValue();
             byte[] b = transform(cr, true /* stubNativesOnly */);
@@ -211,8 +227,8 @@
 
     /**
      * Writes the JAR file.
-     * 
-     * @param outStream The file output stream were to write the JAR. 
+     *
+     * @param outStream The file output stream were to write the JAR.
      * @param all The map of all classes to output.
      * @throws IOException if an I/O error has occurred
      */
@@ -236,7 +252,7 @@
     String classNameToEntryPath(String className) {
         return className.replaceAll("\\.", "/").concat(".class");
     }
-    
+
     /**
      * Utility method to get the JAR entry path from a Class name.
      * e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
@@ -248,30 +264,32 @@
             name = "$" + clazz.getSimpleName() + name;
             clazz = parent;
         }
-        return classNameToEntryPath(clazz.getCanonicalName() + name);        
+        return classNameToEntryPath(clazz.getCanonicalName() + name);
     }
 
     /**
      * Transforms a class.
      * <p/>
      * There are 3 kind of transformations:
-     * 
+     *
      * 1- For "mock" dependencies classes, we want to remove all code from methods and replace
      * by a stub. Native methods must be implemented with this stub too. Abstract methods are
      * left intact. Modified classes must be overridable (non-private, non-final).
      * Native methods must be made non-final, non-private.
-     * 
+     *
      * 2- For "keep" classes, we want to rewrite all native methods as indicated above.
      * If a class has native methods, it must also be made non-private, non-final.
-     * 
+     *
      * Note that unfortunately static methods cannot be changed to non-static (since static and
      * non-static are invoked differently.)
      */
     byte[] transform(ClassReader cr, boolean stubNativesOnly) {
 
         boolean hasNativeMethods = hasNativeMethods(cr);
+
+        // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
         String className = cr.getClassName();
-        
+
         String newName = transformName(className);
         // transformName returns its input argument if there's no need to rename the class
         if (newName != className) {
@@ -288,13 +306,24 @@
         // Rewrite the new class from scratch, without reusing the constant pool from the
         // original class reader.
         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-        
+
         ClassVisitor rv = cw;
         if (newName != className) {
             rv = new RenameClassAdapter(cw, className, newName);
         }
-        
-        TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods, 
+
+        Set<String> delegateMethods = mDelegateMethods.get(className);
+        if (delegateMethods != null && !delegateMethods.isEmpty()) {
+            // If delegateMethods only contains one entry ALL_NATIVES and the class is
+            // known to have no native methods, just skip this step.
+            if (hasNativeMethods ||
+                    !(delegateMethods.size() == 1 &&
+                            delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
+                rv = new DelegateClassAdapter(mLog, rv, className, delegateMethods);
+            }
+        }
+
+        TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
                 mDeleteReturns.get(className),
                 newName, rv,
                 stubNativesOnly, stubNativesOnly || hasNativeMethods);
@@ -323,7 +352,7 @@
                 return newName + className.substring(pos);
             }
         }
-        
+
         return className;
     }
 
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 2ed8641..92892784 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -16,11 +16,70 @@
 
 package com.android.tools.layoutlib.create;
 
-public class CreateInfo {
+/**
+ * Describes the work to be done by {@link AsmGenerator}.
+ */
+public final class CreateInfo implements ICreateInfo {
+
+    /**
+     * Returns the list of class from layoutlib_create to inject in layoutlib.
+     * The list can be empty but must not be null.
+     */
+    public Class<?>[] getInjectedClasses() {
+        return INJECTED_CLASSES;
+    }
+
+    /**
+     * Returns the list of methods to rewrite as delegates.
+     * The list can be empty but must not be null.
+     */
+    public String[] getDelegateMethods() {
+        return DELEGATE_METHODS;
+    }
+
+    /**
+     * Returns the list of classes on which to delegate all native methods.
+     * The list can be empty but must not be null.
+     */
+    public String[] getDelegateClassNatives() {
+        return DELEGATE_CLASS_NATIVES;
+    }
+
+    /**
+     * Returns The list of methods to stub out. Each entry must be in the form
+     * "package.package.OuterClass$InnerClass#MethodName".
+     * The list can be empty but must not be null.
+     */
+    public String[] getOverriddenMethods() {
+        return OVERRIDDEN_METHODS;
+    }
+
+    /**
+     * Returns the list of classes to rename, must be an even list: the binary FQCN
+     * of class to replace followed by the new FQCN.
+     * The list can be empty but must not be null.
+     */
+    public String[] getRenamedClasses() {
+        return RENAMED_CLASSES;
+    }
+
+    /**
+     * Returns the list of classes for which the methods returning them should be deleted.
+     * The array contains a list of null terminated section starting with the name of the class
+     * to rename in which the methods are deleted, followed by a list of return types identifying
+     * the methods to delete.
+     * The list can be empty but must not be null.
+     */
+    public String[] getDeleteReturns() {
+        return DELETE_RETURNS;
+    }
+
+    //-----
+
     /**
      * The list of class from layoutlib_create to inject in layoutlib.
      */
-    public final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
+    private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
             OverrideMethod.class,
             MethodListener.class,
             MethodAdapter.class,
@@ -28,19 +87,37 @@
         };
 
     /**
+     * The list of methods to rewrite as delegates.
+     */
+    private final static String[] DELEGATE_METHODS = new String[] {
+        // TODO: comment out once DelegateClass is working
+        // "android.view.View#isInEditMode",
+        // "android.content.res.Resources$Theme#obtainStyledAttributes",
+    };
+
+    /**
+     * The list of classes on which to delegate all native methods.
+     */
+    private final static String[] DELEGATE_CLASS_NATIVES = new String[] {
+        // TODO: comment out once DelegateClass is working
+        // "android.graphics.Paint"
+    };
+
+    /**
      * The list of methods to stub out. Each entry must be in the form
      *  "package.package.OuterClass$InnerClass#MethodName".
      */
-    public final static String[] OVERRIDDEN_METHODS = new String[] {
-            "android.view.View#isInEditMode",
-            "android.content.res.Resources$Theme#obtainStyledAttributes",
-        };
+    private final static String[] OVERRIDDEN_METHODS = new String[] {
+        // TODO: remove once DelegateClass is working
+        "android.view.View#isInEditMode",
+        "android.content.res.Resources$Theme#obtainStyledAttributes",
+    };
 
     /**
      *  The list of classes to rename, must be an even list: the binary FQCN
      *  of class to replace followed by the new FQCN.
      */
-    public final static String[] RENAMED_CLASSES =
+    private final static String[] RENAMED_CLASSES =
         new String[] {
             "android.graphics.Bitmap",              "android.graphics._Original_Bitmap",
             "android.graphics.BitmapFactory",       "android.graphics._Original_BitmapFactory",
@@ -69,7 +146,7 @@
      * to rename in which the methods are deleted, followed by a list of return types identifying
      * the methods to delete.
      */
-    public final static String[] REMOVED_METHODS =
+    private final static String[] DELETE_RETURNS =
         new String[] {
             "android.graphics.Paint",       // class to delete methods from
             "android.graphics.Paint$Align", // list of type identifying methods to delete
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
new file mode 100644
index 0000000..9cba8a0
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.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 com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Set;
+
+/**
+ * A {@link DelegateClassAdapter} can transform some methods from a class into
+ * delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ */
+public class DelegateClassAdapter extends ClassAdapter {
+
+    public final static String ALL_NATIVES = "<<all_natives>>";
+
+    private final String mClassName;
+    private final Set<String> mDelegateMethods;
+    private final Log mLog;
+
+    /**
+     * Creates a new {@link DelegateClassAdapter} that can transform some methods
+     * from a class into delegates that defer the call to an associated delegate class.
+     * <p/>
+     * This is used to override specific methods and or all native methods in classes.
+     *
+     * @param log The logger object. Must not be null.
+     * @param cv the class visitor to which this adapter must delegate calls.
+     * @param className The internal class name of the class to visit,
+     *          e.g. <code>com/android/SomeClass$InnerClass</code>.
+     * @param delegateMethods The set of method names to modify and/or the
+     *          special constant {@link #ALL_NATIVES} to convert all native methods.
+     */
+    public DelegateClassAdapter(Log log,
+            ClassVisitor cv,
+            String className,
+            Set<String> delegateMethods) {
+        super(cv);
+        mLog = log;
+        mClassName = className;
+        mDelegateMethods = delegateMethods;
+    }
+
+    //----------------------------------
+    // Methods from the ClassAdapter
+
+    @Override
+    public MethodVisitor visitMethod(int access, String name, String desc,
+            String signature, String[] exceptions) {
+
+        boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+        boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+
+        boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
+                              mDelegateMethods.contains(name);
+
+        if (useDelegate) {
+            // remove native
+            access = access & ~Opcodes.ACC_NATIVE;
+        }
+
+        MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+        if (useDelegate) {
+            DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName,
+                                                                name, desc, isStatic);
+            if (isNative) {
+                // A native has no code to visit, so we need to generate it directly.
+                a.generateCode();
+            } else {
+                return a;
+            }
+        }
+        return mw;
+    }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
new file mode 100644
index 0000000..21d6682
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -0,0 +1,319 @@
+/*
+ * 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.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * This method adapter rewrites a method by discarding the original code and generating
+ * a call to a delegate. Original annotations are passed along unchanged.
+ * <p/>
+ * Calls are delegated to a class named <code>&lt;className&gt;_Delegate</code> with
+ * static methods matching the methods to be overridden here. The methods have the
+ * same return type. The argument type list is the same except the "this" reference is
+ * passed first for non-static methods.
+ * <p/>
+ * A new annotation is added.
+ * <p/>
+ * Note that native methods have, by definition, no code so there's nothing a visitor
+ * can visit. That means the caller must call {@link #generateCode()} directly for
+ * a native and use the visitor pattern for non-natives.
+ * <p/>
+ * Instances of this class are not re-usable. You need a new instance for each method.
+ */
+class DelegateMethodAdapter implements MethodVisitor {
+
+    /**
+     * Suffix added to delegate classes.
+     */
+    public static final String DELEGATE_SUFFIX = "_Delegate";
+
+    private static String CONSTRUCTOR = "<init>";
+    private static String CLASS_INIT = "<clinit>";
+
+    /** The parent method writer */
+    private MethodVisitor mParentVisitor;
+    /** Flag to output the first line number. */
+    private boolean mOutputFirstLineNumber = true;
+    /** The original method descriptor (return type + argument types.) */
+    private String mDesc;
+    /** True if the original method is static. */
+    private final boolean mIsStatic;
+    /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
+    private final String mClassName;
+    /** The method name. */
+    private final String mMethodName;
+    /** Logger object. */
+    private final Log mLog;
+    /** True if {@link #visitCode()} has been invoked. */
+    private boolean mVisitCodeCalled;
+
+    /**
+     * Creates a new {@link DelegateMethodAdapter} that will transform this method
+     * into a delegate call.
+     * <p/>
+     * See {@link DelegateMethodAdapter} for more details.
+     *
+     * @param log The logger object. Must not be null.
+     * @param mv the method visitor to which this adapter must delegate calls.
+     * @param className The internal class name of the class to visit,
+     *          e.g. <code>com/android/SomeClass$InnerClass</code>.
+     * @param methodName The simple name of the method.
+     * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
+     *          {@link Type#getArgumentTypes(String)})
+     * @param isStatic True if the method is declared static.
+     */
+    public DelegateMethodAdapter(Log log,
+            MethodVisitor mv,
+            String className,
+            String methodName,
+            String desc,
+            boolean isStatic) {
+        mLog = log;
+        mParentVisitor = mv;
+        mClassName = className;
+        mMethodName = methodName;
+        mDesc = desc;
+        mIsStatic = isStatic;
+
+        if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
+            // We're going to simplify by not supporting constructors.
+            // The only trick with a constructor is to find the proper super constructor
+            // and call it (and deciding if we should mirror the original method call to
+            // a custom constructor or call a default one.)
+            throw new UnsupportedOperationException(
+                    String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
+                            className, methodName, desc));
+        }
+    }
+
+    /**
+     * Generates the new code for the method.
+     * <p/>
+     * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
+     * (since they have no code to visit).
+     * <p/>
+     * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
+     * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
+     * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
+     * this method will be invoked from {@link MethodVisitor#visitEnd()}.
+     */
+    public void generateCode() {
+        /*
+         * The goal is to generate a call to a static delegate method.
+         * If this method is not-static, the first parameter will be this.
+         * All the parameters must be passed and then the eventual return type returned.
+         *
+         * Example, let's say we have a method such as
+         *   public void method_1(int a, Object b, ArrayList<String> c) { ... }
+         *
+         * We'll want to create a body that calls a delegate method like this:
+         *   TheClass_Delegate.method_1(this, a, b, c);
+         *
+         * The generated class name is the current class name with "_Delegate" appended to it.
+         * One thing to realize is that we don't care about generics -- since generic types
+         * are erased at runtime, they have no influence on the method being called.
+         */
+
+        // Add our annotation
+        AnnotationVisitor aw = mParentVisitor.visitAnnotation(
+                Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
+                true); // visible at runtime
+        aw.visitEnd();
+
+        if (!mVisitCodeCalled) {
+            // If this is a direct call to generateCode() as done by DelegateClassAdapter
+            // for natives, visitCode() hasn't been called yet.
+            mParentVisitor.visitCode();
+            mVisitCodeCalled = true;
+        }
+
+        int numVars = 0;
+
+        // Push "this" for an instance method, which is always ALOAD 0
+        if (!mIsStatic) {
+            mParentVisitor.visitVarInsn(Opcodes.ALOAD, numVars++);
+        }
+
+        // Push all other arguments
+        Type[] argTypes = Type.getArgumentTypes(mDesc);
+        for (Type t : argTypes) {
+            int size = t.getSize();
+            mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), numVars);
+            numVars += size;
+        }
+
+        // Construct the descriptor of the delegate. For a static method, it's the same
+        // however for an instance method we need to pass the 'this' reference first
+        String desc = mDesc;
+        if (!mIsStatic && argTypes.length > 0) {
+            Type[] argTypes2 = new Type[argTypes.length + 1];
+
+            argTypes2[0] = Type.getObjectType(mClassName);
+            System.arraycopy(argTypes, 0, argTypes2, 1, argTypes.length);
+
+            desc = Type.getMethodDescriptor(Type.getReturnType(mDesc), argTypes2);
+        }
+
+        String delegateClassName = mClassName + DELEGATE_SUFFIX;
+
+        // Invoke the static delegate
+        mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+                delegateClassName,
+                mMethodName,
+                desc);
+
+        Type returnType = Type.getReturnType(mDesc);
+        mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+        mParentVisitor.visitMaxs(numVars, numVars);
+        mParentVisitor.visitEnd();
+
+        // For debugging now. Maybe we should collect these and store them in
+        // a text file for helping create the delegates. We could also compare
+        // the text file to a golden and break the build on unsupported changes
+        // or regressions. Even better we could fancy-print something that looks
+        // like the expected Java method declaration.
+        mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
+    }
+
+    /* Pass down to visitor writer. In this implementation, either do nothing. */
+    public void visitCode() {
+        mVisitCodeCalled = true;
+        mParentVisitor.visitCode();
+    }
+
+    /*
+     * visitMaxs is called just before visitEnd if there was any code to rewrite.
+     * Skip the original.
+     */
+    public void visitMaxs(int maxStack, int maxLocals) {
+    }
+
+    /**
+     * End of visiting. Generate the messaging code.
+     */
+    public void visitEnd() {
+        generateCode();
+    }
+
+    /* Writes all annotation from the original method. */
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+        return mParentVisitor.visitAnnotation(desc, visible);
+    }
+
+    /* Writes all annotation default values from the original method. */
+    public AnnotationVisitor visitAnnotationDefault() {
+        return mParentVisitor.visitAnnotationDefault();
+    }
+
+    public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+            boolean visible) {
+        return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
+    }
+
+    /* Writes all attributes from the original method. */
+    public void visitAttribute(Attribute attr) {
+        mParentVisitor.visitAttribute(attr);
+    }
+
+    /*
+     * Only writes the first line number present in the original code so that source
+     * viewers can direct to the correct method, even if the content doesn't match.
+     */
+    public void visitLineNumber(int line, Label start) {
+        if (mOutputFirstLineNumber) {
+            mParentVisitor.visitLineNumber(line, start);
+            mOutputFirstLineNumber = false;
+        }
+    }
+
+    public void visitInsn(int opcode) {
+        // Skip original code.
+    }
+
+    public void visitLabel(Label label) {
+        // Skip original code.
+    }
+
+    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+        // Skip original code.
+    }
+
+    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+        // Skip original code.
+    }
+
+    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+        // Skip original code.
+    }
+
+    public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+        // Skip original code.
+    }
+
+    public void visitIincInsn(int var, int increment) {
+        // Skip original code.
+    }
+
+    public void visitIntInsn(int opcode, int operand) {
+        // Skip original code.
+    }
+
+    public void visitJumpInsn(int opcode, Label label) {
+        // Skip original code.
+    }
+
+    public void visitLdcInsn(Object cst) {
+        // Skip original code.
+    }
+
+    public void visitLocalVariable(String name, String desc, String signature,
+            Label start, Label end, int index) {
+        // Skip original code.
+    }
+
+    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+        // Skip original code.
+    }
+
+    public void visitMultiANewArrayInsn(String desc, int dims) {
+        // Skip original code.
+    }
+
+    public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+        // Skip original code.
+    }
+
+    public void visitTypeInsn(int opcode, String type) {
+        // Skip original code.
+    }
+
+    public void visitVarInsn(int opcode, int var) {
+        // Skip original code.
+    }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
new file mode 100644
index 0000000..40c1706
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+/**
+ * Interface describing the work to be done by {@link AsmGenerator}.
+ */
+public interface ICreateInfo {
+
+    /**
+     * Returns the list of class from layoutlib_create to inject in layoutlib.
+     * The list can be empty but must not be null.
+     */
+    public abstract Class<?>[] getInjectedClasses();
+
+    /**
+     * Returns the list of methods to rewrite as delegates.
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getDelegateMethods();
+
+    /**
+     * Returns the list of classes on which to delegate all native methods.
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getDelegateClassNatives();
+
+    /**
+     * Returns The list of methods to stub out. Each entry must be in the form
+     * "package.package.OuterClass$InnerClass#MethodName".
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getOverriddenMethods();
+
+    /**
+     * Returns the list of classes to rename, must be an even list: the binary FQCN
+     * of class to replace followed by the new FQCN.
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getRenamedClasses();
+
+    /**
+     * Returns the list of classes for which the methods returning them should be deleted.
+     * The array contains a list of null terminated section starting with the name of the class
+     * to rename in which the methods are deleted, followed by a list of return types identifying
+     * the methods to delete.
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getDeleteReturns();
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index 303f097..4adaff9 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -21,7 +21,28 @@
 import java.util.Set;
 
 
-
+/**
+ * Entry point for the layoutlib_create tool.
+ * <p/>
+ * The tool does not currently rely on any external configuration file.
+ * Instead the configuration is mostly done via the {@link CreateInfo} class.
+ * <p/>
+ * For a complete description of the tool and its implementation, please refer to
+ * the "README.txt" file at the root of this project.
+ * <p/>
+ * For a quick test, invoke this as follows:
+ * <pre>
+ * $ make layoutlib
+ * </pre>
+ * which does:
+ * <pre>
+ * $ make layoutlib_create &lt;bunch of framework jars&gt;
+ * $ out/host/linux-x86/framework/bin/layoutlib_create \
+ *        out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \
+ *        out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \
+ *        out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
+ * </pre>
+ */
 public class Main {
 
     public static void main(String[] args) {
@@ -42,12 +63,7 @@
         }
 
         try {
-            AsmGenerator agen = new AsmGenerator(log, osDestJar[0],
-                    CreateInfo.INJECTED_CLASSES,
-                    CreateInfo.OVERRIDDEN_METHODS,
-                    CreateInfo.RENAMED_CLASSES,
-                    CreateInfo.REMOVED_METHODS
-            );
+            AsmGenerator agen = new AsmGenerator(log, osDestJar[0], new CreateInfo());
 
             AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
                     new String[] { "android.view.View" },   // derived from
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index e294d56..f2d9755 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -26,7 +26,7 @@
 import java.util.Set;
 
 /**
- * Class adapter that can stub some or all of the methods of the class. 
+ * Class adapter that can stub some or all of the methods of the class.
  */
 class TransformClassAdapter extends ClassAdapter {
 
@@ -41,12 +41,12 @@
 
     /**
      * Creates a new class adapter that will stub some or all methods.
-     * @param logger 
-     * @param stubMethods 
+     * @param logger
+     * @param stubMethods  list of method signatures to always stub out
      * @param deleteReturns list of types that trigger the deletion of methods returning them.
      * @param className The name of the class being modified
      * @param cv The parent class writer visitor
-     * @param stubNativesOnly True if only native methods should be stubbed. False if all 
+     * @param stubNativesOnly True if only native methods should be stubbed. False if all
      *                        methods should be stubbed.
      * @param hasNative True if the method has natives, in which case its access should be
      *                  changed.
@@ -67,10 +67,10 @@
     @Override
     public void visit(int version, int access, String name,
             String signature, String superName, String[] interfaces) {
-        
+
         // This class might be being renamed.
         name = mClassName;
-        
+
         // remove protected or private and set as public
         access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
         access |= Opcodes.ACC_PUBLIC;
@@ -82,7 +82,7 @@
         mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
         super.visit(version, access, name, signature, superName, interfaces);
     }
-    
+
     /* Visits the header of an inner class. */
     @Override
     public void visitInnerClass(String name, String outerName, String innerName, int access) {
@@ -101,7 +101,7 @@
     @Override
     public MethodVisitor visitMethod(int access, String name, String desc,
             String signature, String[] exceptions) {
-        
+
         if (mDeleteReturns != null) {
             Type t = Type.getReturnType(desc);
             if (t.getSort() == Type.OBJECT) {
@@ -130,16 +130,16 @@
             (mStubAll ||
              (access & Opcodes.ACC_NATIVE) != 0) ||
              mStubMethods.contains(methodSignature)) {
-            
+
             boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
             boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
 
             // remove abstract, final and native
             access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
-            
+
             String invokeSignature = methodSignature + desc;
             mLog.debug("  Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
-            
+
             MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
             return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
                     isStatic, isNative);
@@ -149,7 +149,7 @@
             return super.visitMethod(access, name, desc, signature, exceptions);
         }
     }
-    
+
     /* Visits a field. Makes it public. */
     @Override
     public FieldVisitor visitField(int access, String name, String desc, String signature,
@@ -157,7 +157,7 @@
         // change access to public
         access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
         access |= Opcodes.ACC_PUBLIC;
-        
+
         return super.visitField(access, name, desc, signature, value);
     }
 
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index 603284e..d6dba6a 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
 
 import org.junit.After;
 import org.junit.Before;
@@ -46,9 +45,9 @@
 
     @Before
     public void setUp() throws Exception {
-        mLog = new LogTest.MockLog();
+        mLog = new MockLog();
         URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-        
+
         mOsJarPath = new ArrayList<String>();
         mOsJarPath.add(url.getFile());
 
@@ -69,9 +68,9 @@
                 "mock_android.dummy.InnerTest$DerivingClass",
                 "mock_android.dummy.InnerTest$MyGenerics1",
                 "mock_android.dummy.InnerTest$MyIntEnum",
-                "mock_android.dummy.InnerTest$MyStaticInnerClass",   
-                "mock_android.dummy.InnerTest$NotStaticInner1", 
-                "mock_android.dummy.InnerTest$NotStaticInner2",  
+                "mock_android.dummy.InnerTest$MyStaticInnerClass",
+                "mock_android.dummy.InnerTest$NotStaticInner1",
+                "mock_android.dummy.InnerTest$NotStaticInner2",
                 "mock_android.view.View",
                 "mock_android.view.ViewGroup",
                 "mock_android.view.ViewGroup$LayoutParams",
@@ -83,7 +82,7 @@
             },
             map.keySet().toArray());
     }
-    
+
     @Test
     public void testFindClass() throws IOException, LogAbortException {
         Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -91,7 +90,7 @@
 
         ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
                 zipClasses, found);
-        
+
         assertNotNull(cr);
         assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
         assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
@@ -172,14 +171,14 @@
                 "mock_android.widget.TableLayout",
             },
             found.keySet().toArray());
-        
+
         for (String key : found.keySet()) {
             ClassReader value = found.get(key);
             assertNotNull("No value for " + key, value);
             assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
         }
     }
-    
+
     @Test
     public void testDependencyVisitor() throws IOException, LogAbortException {
         Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -190,7 +189,7 @@
 
         ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep);
         DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
-        
+
         // get first level dependencies
         cr.accept(visitor, 0 /* flags */);
 
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 7cdf79a..f4ff389 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -20,8 +20,6 @@
 
 import static org.junit.Assert.assertArrayEquals;
 
-import com.android.tools.layoutlib.create.LogTest.MockLog;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -44,9 +42,9 @@
 
     @Before
     public void setUp() throws Exception {
-        mLog = new LogTest.MockLog();
+        mLog = new MockLog();
         URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-        
+
         mOsJarPath = new ArrayList<String>();
         mOsJarPath.add(url.getFile());
 
@@ -65,16 +63,41 @@
 
     @Test
     public void testClassRenaming() throws IOException, LogAbortException {
-        
-        AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar,
-            null, // classes to inject in the final JAR
-            null,  // methods to force override
-            new String[] {  // classes to rename (so that we can replace them)
-                "mock_android.view.View", "mock_android.view._Original_View",
-                "not.an.actual.ClassName", "anoter.fake.NewClassName",
-            },
-            null // methods deleted from their return type.
-            );
+
+        ICreateInfo ci = new ICreateInfo() {
+            public Class<?>[] getInjectedClasses() {
+                // classes to inject in the final JAR
+                return new Class<?>[0];
+            }
+
+            public String[] getDelegateMethods() {
+                return new String[0];
+            }
+
+            public String[] getDelegateClassNatives() {
+                return new String[0];
+            }
+
+            public String[] getOverriddenMethods() {
+                // methods to force override
+                return new String[0];
+            }
+
+            public String[] getRenamedClasses() {
+                // classes to rename (so that we can replace them)
+                return new String[] {
+                        "mock_android.view.View", "mock_android.view._Original_View",
+                        "not.an.actual.ClassName", "anoter.fake.NewClassName",
+                };
+            }
+
+            public String[] getDeleteReturns() {
+                 // methods deleted from their return type.
+                return new String[0];
+            }
+        };
+
+        AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
 
         AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
                 null,                 // derived from
@@ -83,7 +106,7 @@
                 });
         aa.analyze();
         agen.generate();
-        
+
         Set<String> notRenamed = agen.getClassesNotRenamed();
         assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
     }
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
index d6916ae..0135c40 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -33,8 +33,9 @@
     @Test
     public void testHasNative() throws IOException {
         MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
-        ClassReader cr = new ClassReader(
-                "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithNative");
+        String className =
+                this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName();
+        ClassReader cr = new ClassReader(className);
 
         cr.accept(cv, 0 /* flags */);
         assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
@@ -44,14 +45,17 @@
     @Test
     public void testHasNoNative() throws IOException {
         MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
-        ClassReader cr = new ClassReader(
-                "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithoutNative");
+        String className =
+            this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName();
+        ClassReader cr = new ClassReader(className);
 
         cr.accept(cv, 0 /* flags */);
         assertArrayEquals(new String[0], cv.getMethodsFound());
         assertFalse(cv.hasNativeMethods());
     }
 
+    //-------
+
     /**
      * Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
      */
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
new file mode 100644
index 0000000..9ad2e6e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tools.layoutlib.create;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+
+public class DelegateClassAdapterTest {
+
+    private MockLog mLog;
+
+    private static final String CLASS_NAME =
+        DelegateClassAdapterTest.class.getCanonicalName() + "$" +
+        ClassWithNative.class.getSimpleName();
+
+    @Before
+    public void setUp() throws Exception {
+        mLog = new MockLog();
+        mLog.setVerbose(true); // capture debug error too
+    }
+
+    /**
+     * Tests that a class not being modified still works.
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testNoOp() throws Exception {
+        // create an instance of the class that will be modified
+        // (load the class in a distinct class loader so that we can trash its definition later)
+        ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
+        Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(CLASS_NAME);
+        ClassWithNative instance1 = clazz1.newInstance();
+        assertEquals(42, instance1.add(20, 22));
+        try {
+            instance1.callNativeInstance(10, 3.1415, new Object[0] );
+            fail("Test should have failed to invoke callTheNativeMethod [1]");
+        } catch (UnsatisfiedLinkError e) {
+            // This is expected to fail since the native method is not implemented.
+        }
+
+        // Now process it but tell the delegate to not modify any method
+        ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+        HashSet<String> delegateMethods = new HashSet<String>();
+        String internalClassName = CLASS_NAME.replace('.', '/');
+        DelegateClassAdapter cv = new DelegateClassAdapter(
+                mLog, cw, internalClassName, delegateMethods);
+
+        ClassReader cr = new ClassReader(CLASS_NAME);
+        cr.accept(cv, 0 /* flags */);
+
+        // Load the generated class in a different class loader and try it again
+        final byte[] bytes = cw.toByteArray();
+
+        ClassLoader2 cl2 = new ClassLoader2(bytes) {
+            @Override
+            public void testModifiedInstance() throws Exception {
+                Class<?> clazz2 = loadClass(CLASS_NAME);
+                Object i2 = clazz2.newInstance();
+                assertNotNull(i2);
+                assertEquals(42, callAdd(i2, 20, 22));
+
+                try {
+                    callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
+                    fail("Test should have failed to invoke callTheNativeMethod [2]");
+                } catch (InvocationTargetException e) {
+                    // This is expected to fail since the native method has NOT been
+                    // overridden here.
+                    assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
+                }
+
+                // Check that the native method does NOT have the new annotation
+                Method[] m = clazz2.getDeclaredMethods();
+                assertEquals("native_instance", m[2].getName());
+                assertTrue(Modifier.isNative(m[2].getModifiers()));
+                Annotation[] a = m[2].getAnnotations();
+                assertEquals(0, a.length);
+            }
+        };
+        cl2.testModifiedInstance();
+    }
+
+    /**
+     * {@link DelegateMethodAdapter} does not support overriding constructors yet,
+     * so this should fail with an {@link UnsupportedOperationException}.
+     *
+     * Although not tested here, the message of the exception should contain the
+     * constructor signature.
+     */
+    @Test(expected=UnsupportedOperationException.class)
+    public void testConstructorsNotSupported() throws IOException {
+        ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+        String internalClassName = CLASS_NAME.replace('.', '/');
+
+        HashSet<String> delegateMethods = new HashSet<String>();
+        delegateMethods.add("<init>");
+        DelegateClassAdapter cv = new DelegateClassAdapter(
+                mLog, cw, internalClassName, delegateMethods);
+
+        ClassReader cr = new ClassReader(CLASS_NAME);
+        cr.accept(cv, 0 /* flags */);
+    }
+
+    @Test
+    public void testDelegateNative() throws Exception {
+        ClassWriter cw = new ClassWriter(0 /*flags*/);
+        String internalClassName = CLASS_NAME.replace('.', '/');
+
+        HashSet<String> delegateMethods = new HashSet<String>();
+        delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
+        DelegateClassAdapter cv = new DelegateClassAdapter(
+                mLog, cw, internalClassName, delegateMethods);
+
+        ClassReader cr = new ClassReader(CLASS_NAME);
+        cr.accept(cv, 0 /* flags */);
+
+        // Load the generated class in a different class loader and try it
+        final byte[] bytes = cw.toByteArray();
+
+        try {
+            ClassLoader2 cl2 = new ClassLoader2(bytes) {
+                @Override
+                public void testModifiedInstance() throws Exception {
+                    Class<?> clazz2 = loadClass(CLASS_NAME);
+                    Object i2 = clazz2.newInstance();
+                    assertNotNull(i2);
+
+                    // Use reflection to access inner methods
+                    assertEquals(42, callAdd(i2, 20, 22));
+
+                     Object[] objResult = new Object[] { null };
+                     int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
+                     assertEquals((int)(10 + 3.1415), result);
+                     assertSame(i2, objResult[0]);
+
+                     // Check that the native method now has the new annotation and is not native
+                     Method[] m = clazz2.getDeclaredMethods();
+                     assertEquals("native_instance", m[2].getName());
+                     assertFalse(Modifier.isNative(m[2].getModifiers()));
+                     Annotation[] a = m[2].getAnnotations();
+                     assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
+                }
+            };
+            cl2.testModifiedInstance();
+
+        } catch (Throwable t) {
+            // For debugging, dump the bytecode of the class in case of unexpected error.
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            TraceClassVisitor tcv = new TraceClassVisitor(pw);
+
+            ClassReader cr2 = new ClassReader(bytes);
+            cr2.accept(tcv, 0 /* flags */);
+
+            String msg = "\n" + t.getClass().getCanonicalName();
+            if (t.getMessage() != null) {
+                msg += ": " + t.getMessage();
+            }
+            msg = msg + "\nBytecode dump:\n" + sw.toString();
+
+            // Re-throw exception with new message
+            RuntimeException ex = new RuntimeException(msg, t);
+            throw ex;
+        }
+    }
+
+    //-------
+
+    /**
+     * A class loader than can define and instantiate our dummy {@link ClassWithNative}.
+     * <p/>
+     * The trick here is that this class loader will test our modified version of ClassWithNative.
+     * Trying to do so in the original class loader generates all sort of link issues because
+     * there are 2 different definitions of the same class name. This class loader will
+     * define and load the class when requested by name and provide helpers to access the
+     * instance methods via reflection.
+     */
+    private abstract class ClassLoader2 extends ClassLoader {
+        private final byte[] mClassWithNative;
+
+        public ClassLoader2(byte[] classWithNative) {
+            super(null);
+            mClassWithNative = classWithNative;
+        }
+
+        @SuppressWarnings("unused")
+        @Override
+        protected Class<?> findClass(String name) throws ClassNotFoundException {
+            try {
+                return super.findClass(name);
+            } catch (ClassNotFoundException e) {
+
+                if (CLASS_NAME.equals(name)) {
+                    // Load the modified ClassWithNative from its bytes representation.
+                    return defineClass(CLASS_NAME, mClassWithNative, 0, mClassWithNative.length);
+                }
+
+                try {
+                    // Load everything else from the original definition into the new class loader.
+                    ClassReader cr = new ClassReader(name);
+                    ClassWriter cw = new ClassWriter(0);
+                    cr.accept(cw, 0);
+                    byte[] bytes = cw.toByteArray();
+                    return defineClass(name, bytes, 0, bytes.length);
+
+                } catch (IOException ioe) {
+                    throw new RuntimeException(ioe);
+                }
+            }
+        }
+
+        /**
+         * Accesses {@link ClassWithNative#add(int, int)} via reflection.
+         */
+        public int callAdd(Object instance, int a, int b) throws Exception {
+            Method m = instance.getClass().getMethod("add",
+                    new Class<?>[] { int.class, int.class });
+
+            Object result = m.invoke(instance, new Object[] { a, b });
+            return ((Integer) result).intValue();
+        }
+
+        /**
+         * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])}
+         * via reflection.
+         */
+        public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
+                throws Exception {
+            Method m = instance.getClass().getMethod("callNativeInstance",
+                    new Class<?>[] { int.class, double.class, Object[].class });
+
+            Object result = m.invoke(instance, new Object[] { a, d, o });
+            return ((Integer) result).intValue();
+        }
+
+        public abstract void testModifiedInstance() throws Exception;
+    }
+
+    /**
+     * Dummy test class with a native method.
+     * The native method is not defined and any attempt to invoke it will
+     * throw an {@link UnsatisfiedLinkError}.
+     */
+    public static class ClassWithNative {
+        public ClassWithNative() {
+        }
+
+        public int add(int a, int b) {
+            return a + b;
+        }
+
+        public int callNativeInstance(int a, double d, Object[] o) {
+            return native_instance(a, d, o);
+        }
+
+        private native int native_instance(int a, double d, Object[] o);
+    }
+
+    /**
+     * The delegate that receives the call to {@link ClassWithNative}'s overridden methods.
+     */
+    public static class ClassWithNative_Delegate {
+        public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
+            if (o != null && o.length > 0) {
+                o[0] = instance;
+            }
+            return (int)(a + d);
+        }
+    }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
index 3f13158..1a5f653 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
@@ -24,33 +24,8 @@
 
 public class LogTest {
 
-    public static class MockLog extends Log {
-        StringBuilder mOut = new StringBuilder();
-        StringBuilder mErr = new StringBuilder();
-        
-        public String getOut() {
-            return mOut.toString();
-        }
-        
-        public String getErr() {
-            return mErr.toString();
-        }
-        
-        @Override
-        protected void outPrintln(String msg) {
-            mOut.append(msg);
-            mOut.append('\n');
-        }
-        
-        @Override
-        protected void errPrintln(String msg) {
-            mErr.append(msg);
-            mErr.append('\n');
-        }
-    }
-
     private MockLog mLog;
-    
+
     @Before
     public void setUp() throws Exception {
         mLog = new MockLog();
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
new file mode 100644
index 0000000..de750a3
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+
+public class MockLog extends Log {
+    StringBuilder mOut = new StringBuilder();
+    StringBuilder mErr = new StringBuilder();
+
+    public String getOut() {
+        return mOut.toString();
+    }
+
+    public String getErr() {
+        return mErr.toString();
+    }
+
+    @Override
+    protected void outPrintln(String msg) {
+        mOut.append(msg);
+        mOut.append('\n');
+    }
+
+    @Override
+    protected void errPrintln(String msg) {
+        mErr.append(msg);
+        mErr.append('\n');
+    }
+}
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index c23da20..7c1234a 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -717,6 +717,7 @@
     }
 
     private void grabWifiHighPerfLock() {
+        /* not available in master yet
         if (mWifiHighPerfLock == null) {
             Log.v(TAG, "acquire wifi high perf lock");
             mWifiHighPerfLock = ((WifiManager)
@@ -724,6 +725,7 @@
                     .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG);
             mWifiHighPerfLock.acquire();
         }
+        */
     }
 
     private void releaseWifiHighPerfLock() {
diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp
index f3ecac2..84c7166 100644
--- a/voip/jni/rtp/AmrCodec.cpp
+++ b/voip/jni/rtp/AmrCodec.cpp
@@ -73,7 +73,7 @@
     }
 
     // Handle mode-set and octet-align.
-    char *modes = strcasestr(fmtp, "mode-set=");
+    const char *modes = strcasestr(fmtp, "mode-set=");
     if (modes) {
         mMode = 0;
         mModeSet = 0;
diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp
index 92015a9..2ceebdc 100644
--- a/voip/jni/rtp/EchoSuppressor.cpp
+++ b/voip/jni/rtp/EchoSuppressor.cpp
@@ -16,6 +16,7 @@
 
 #include <stdio.h>
 #include <stdint.h>
+#include <string.h>
 #include <math.h>
 
 #define LOG_TAG "Echo"
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 198b1e6..f760d27 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -46,11 +46,11 @@
 
     List<ScanResult> getScanResults();
 
-    boolean disconnect();
+    void disconnect();
 
-    boolean reconnect();
+    void reconnect();
 
-    boolean reassociate();
+    void reassociate();
 
     WifiInfo getConnectionInfo();
 
@@ -89,5 +89,25 @@
     WifiConfiguration getWifiApConfiguration();
 
     void setWifiApConfiguration(in WifiConfiguration wifiConfig);
+
+    void startWifi();
+
+    void stopWifi();
+
+    void addToBlacklist(String bssid);
+
+    void clearBlacklist();
+
+    void connectNetworkWithConfig(in WifiConfiguration wifiConfig);
+
+    void connectNetworkWithId(int networkId);
+
+    void saveNetwork(in WifiConfiguration wifiConfig);
+
+    void forgetNetwork(int networkId);
+
+    void startWpsPbc(String bssid);
+
+    void startWpsPin(String bssid, int apPin);
 }
 
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
new file mode 100644
index 0000000..9634157
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -0,0 +1,1181 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wifi;
+
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.content.Intent;
+import android.net.DhcpInfo;
+import android.net.ProxyProperties;
+import android.net.wifi.WifiConfiguration.IpAssignment;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiConfiguration.ProxySettings;
+import android.net.wifi.WifiConfiguration.Status;
+import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class provides the API to manage configured
+ * wifi networks. The API is not thread safe is being
+ * used only from WifiStateMachine.
+ *
+ * It deals with the following
+ * - Add/update/remove a WifiConfiguration
+ *   The configuration contains two types of information.
+ *     = IP and proxy configuration that is handled by WifiConfigStore and
+ *       is saved to disk on any change.
+ *
+ *       The format of configuration file is as follows:
+ *       <version>
+ *       <netA_key1><netA_value1><netA_key2><netA_value2>...<EOS>
+ *       <netB_key1><netB_value1><netB_key2><netB_value2>...<EOS>
+ *       ..
+ *
+ *       (key, value) pairs for a given network are grouped together and can
+ *       be in any order. A "EOS" at the end of a set of (key, value) pairs
+ *       indicates that the next set of (key, value) pairs are for a new
+ *       network. A network is identified by a unique "id". If there is no
+ *       "id" key in the (key, value) pairs, the data is discarded. An IP
+ *       configuration includes the keys - "ipAssignment", "ipAddress", "gateway",
+ *       "netmask", "dns1" and "dns2". A proxy configuration includes "proxySettings",
+ *       "proxyHost", "proxyPort" and "exclusionList"
+ *
+ *       An invalid version on read would result in discarding the contents of
+ *       the file. On the next write, the latest version is written to file.
+ *
+ *       Any failures during read or write to the configuration file are ignored
+ *       without reporting to the user since the likelihood of these errors are
+ *       low and the impact on connectivity is low.
+ *
+ *     = SSID & security details that is pushed to the supplicant.
+ *       supplicant saves these details to the disk on calling
+ *       saveConfigCommand().
+ *
+ *       We have two kinds of APIs exposed:
+ *        > public API calls that provide fine grained control
+ *          - enableNetwork, disableNetwork, addOrUpdateNetwork(),
+ *          removeNetwork(). For these calls, the config is not persisted
+ *          to the disk. (TODO: deprecate these calls in WifiManager)
+ *        > The new API calls - selectNetwork(), saveNetwork() & forgetNetwork().
+ *          These calls persist the supplicant config to disk.
+ *
+ * - Maintain a list of configured networks for quick access
+ *
+ */
+class WifiConfigStore {
+
+    private static Context sContext;
+    private static final String TAG = "WifiConfigStore";
+
+    /* configured networks with network id as the key */
+    private static HashMap<Integer, WifiConfiguration> sConfiguredNetworks =
+            new HashMap<Integer, WifiConfiguration>();
+
+    /* A network id is a unique identifier for a network configured in the
+     * supplicant. Network ids are generated when the supplicant reads
+     * the configuration file at start and can thus change for networks.
+     * We store the IP configuration for networks along with a unique id
+     * that is generated from SSID and security type of the network. A mapping
+     * from the generated unique id to network id of the network is needed to
+     * map supplicant config to IP configuration. */
+    private static HashMap<Integer, Integer> sNetworkIds =
+            new HashMap<Integer, Integer>();
+
+    /* Tracks the highest priority of configured networks */
+    private static int sLastPriority = -1;
+
+    private static final String ipConfigFile = Environment.getDataDirectory() +
+            "/misc/wifi/ipconfig.txt";
+
+    private static final int IPCONFIG_FILE_VERSION = 1;
+
+    /**
+     * Initialize context, fetch the list of configured networks
+     * and enable all stored networks in supplicant.
+     */
+    static void initialize(Context context) {
+        Log.d(TAG, "Loading config and enabling all networks");
+        sContext = context;
+        loadConfiguredNetworks();
+        enableAllNetworks();
+    }
+
+    /**
+     * Fetch the list of currently configured networks
+     * @return List of networks
+     */
+    static List<WifiConfiguration> getConfiguredNetworks() {
+        List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
+        synchronized (sConfiguredNetworks) {
+            for(WifiConfiguration config : sConfiguredNetworks.values()) {
+                networks.add(new WifiConfiguration(config));
+            }
+        }
+        return networks;
+    }
+
+    /**
+     * enable all networks and save config. This will be a no-op if the list
+     * of configured networks indicates all networks as being enabled
+     */
+    static void enableAllNetworks() {
+        synchronized (sConfiguredNetworks) {
+            for(WifiConfiguration config : sConfiguredNetworks.values()) {
+                if(config != null && config.status == Status.DISABLED) {
+                    if(WifiNative.enableNetworkCommand(config.networkId, false)) {
+                        config.status = Status.ENABLED;
+                    } else {
+                        Log.e(TAG, "Enable network failed on " + config.networkId);
+                    }
+                }
+            }
+        }
+
+        WifiNative.saveConfigCommand();
+        sendConfigChangeBroadcast();
+    }
+
+    /**
+     * Selects the specified network config for connection. This involves
+     * addition/update of the specified config, updating the priority of
+     * all the networks and enabling the given network while disabling others.
+     *
+     * Selecting a network will leave the other networks disabled and
+     * a call to enableAllNetworks() needs to be issued upon a connection
+     * or a failure event from supplicant
+     *
+     * @param config The configuration details in WifiConfiguration
+     */
+    static void selectNetwork(WifiConfiguration config) {
+        if (config != null) {
+            int netId = addOrUpdateNetworkNative(config);
+            if (netId != INVALID_NETWORK_ID) {
+                selectNetwork(netId);
+            } else {
+                Log.e(TAG, "Failed to update network " + config);
+            }
+        }
+    }
+
+    /**
+     * Selects the specified network for connection. This involves
+     * updating the priority of all the networks and enabling the given
+     * network while disabling others.
+     *
+     * Selecting a network will leave the other networks disabled and
+     * a call to enableAllNetworks() needs to be issued upon a connection
+     * or a failure event from supplicant
+     *
+     * @param netId network to select for connection
+     */
+    static void selectNetwork(int netId) {
+        // Reset the priority of each network at start or if it goes too high.
+        if (sLastPriority == -1 || sLastPriority > 1000000) {
+            synchronized (sConfiguredNetworks) {
+                for(WifiConfiguration config : sConfiguredNetworks.values()) {
+                    if (config.networkId != INVALID_NETWORK_ID) {
+                        config.priority = 0;
+                        addOrUpdateNetworkNative(config);
+                    }
+                }
+            }
+            sLastPriority = 0;
+        }
+
+        // Set to the highest priority and save the configuration.
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = netId;
+        config.priority = ++sLastPriority;
+
+        addOrUpdateNetworkNative(config);
+        WifiNative.saveConfigCommand();
+
+        /* Enable the given network while disabling all other networks */
+        enableNetworkWithoutBroadcast(netId, true);
+
+       /* Avoid saving the config & sending a broadcast to prevent settings
+        * from displaying a disabled list of networks */
+    }
+
+    /**
+     * Add/update the specified configuration and save config
+     *
+     * @param config WifiConfiguration to be saved
+     */
+    static void saveNetwork(WifiConfiguration config) {
+        boolean newNetwork = (config.networkId == INVALID_NETWORK_ID);
+        int netId = addOrUpdateNetworkNative(config);
+        /* enable a new network */
+        if (newNetwork && netId != INVALID_NETWORK_ID) {
+            WifiNative.enableNetworkCommand(netId, false);
+            synchronized (sConfiguredNetworks) {
+                sConfiguredNetworks.get(netId).status = Status.ENABLED;
+            }
+        }
+        WifiNative.saveConfigCommand();
+        sendConfigChangeBroadcast();
+    }
+
+    /**
+     * Forget the specified network and save config
+     *
+     * @param netId network to forget
+     */
+    static void forgetNetwork(int netId) {
+        if (WifiNative.removeNetworkCommand(netId)) {
+            WifiNative.saveConfigCommand();
+            synchronized (sConfiguredNetworks) {
+                sConfiguredNetworks.remove(netId);
+            }
+            writeIpAndProxyConfigurations();
+            sendConfigChangeBroadcast();
+        } else {
+            Log.e(TAG, "Failed to remove network " + netId);
+        }
+    }
+
+    /**
+     * Add/update a network. Note that there is no saveConfig operation.
+     * This function is retained for compatibility with the public
+     * API. The more powerful saveNetwork() is used by the
+     * state machine
+     *
+     * @param config wifi configuration to add/update
+     */
+    static int addOrUpdateNetwork(WifiConfiguration config) {
+        int ret = addOrUpdateNetworkNative(config);
+        sendConfigChangeBroadcast();
+        return ret;
+    }
+
+    /**
+     * Remove a network. Note that there is no saveConfig operation.
+     * This function is retained for compatibility with the public
+     * API. The more powerful forgetNetwork() is used by the
+     * state machine for network removal
+     *
+     * @param netId network to be removed
+     */
+    static boolean removeNetwork(int netId) {
+        boolean ret = WifiNative.removeNetworkCommand(netId);
+        synchronized (sConfiguredNetworks) {
+            if (ret) sConfiguredNetworks.remove(netId);
+        }
+        sendConfigChangeBroadcast();
+        return ret;
+    }
+
+    /**
+     * Enable a network. Note that there is no saveConfig operation.
+     * This function is retained for compatibility with the public
+     * API. The more powerful selectNetwork()/saveNetwork() is used by the
+     * state machine for connecting to a network
+     *
+     * @param netId network to be removed
+     */
+    static boolean enableNetwork(int netId, boolean disableOthers) {
+        boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers);
+        sendConfigChangeBroadcast();
+        return ret;
+    }
+
+    static boolean enableNetworkWithoutBroadcast(int netId, boolean disableOthers) {
+        boolean ret = WifiNative.enableNetworkCommand(netId, disableOthers);
+
+        synchronized (sConfiguredNetworks) {
+            WifiConfiguration config = sConfiguredNetworks.get(netId);
+            if (config != null) config.status = Status.ENABLED;
+        }
+
+        if (disableOthers) {
+            markAllNetworksDisabledExcept(netId);
+        }
+        return ret;
+    }
+
+    /**
+     * Disable a network. Note that there is no saveConfig operation.
+     * @param netId network to be disabled
+     */
+    static boolean disableNetwork(int netId) {
+        boolean ret = WifiNative.disableNetworkCommand(netId);
+        synchronized (sConfiguredNetworks) {
+            WifiConfiguration config = sConfiguredNetworks.get(netId);
+            if (config != null) config.status = Status.DISABLED;
+        }
+        sendConfigChangeBroadcast();
+        return ret;
+    }
+
+    /**
+     * Save the configured networks in supplicant to disk
+     */
+    static boolean saveConfig() {
+        return WifiNative.saveConfigCommand();
+    }
+
+    /**
+     * Start WPS pin method configuration
+     */
+    static boolean startWpsPin(String bssid, int apPin) {
+        if (WifiNative.startWpsPinCommand(bssid, apPin)) {
+            /* WPS leaves all networks disabled */
+            markAllNetworksDisabled();
+            return true;
+        }
+        Log.e(TAG, "Failed to start WPS pin method configuration");
+        return false;
+    }
+
+    /**
+     * Start WPS push button configuration
+     */
+    static boolean startWpsPbc(String bssid) {
+        if (WifiNative.startWpsPbcCommand(bssid)) {
+            /* WPS leaves all networks disabled */
+            markAllNetworksDisabled();
+            return true;
+        }
+        Log.e(TAG, "Failed to start WPS push button configuration");
+        return false;
+    }
+
+    /**
+     * Fetch the IP configuration for a given network id
+     */
+    static DhcpInfo getIpConfiguration(int netId) {
+        synchronized (sConfiguredNetworks) {
+            WifiConfiguration config = sConfiguredNetworks.get(netId);
+            if (config != null) return new DhcpInfo(config.ipConfig);
+        }
+        return null;
+    }
+
+    /**
+     * Fetch the proxy properties for a given network id
+     */
+    static ProxyProperties getProxyProperties(int netId) {
+        synchronized (sConfiguredNetworks) {
+            WifiConfiguration config = sConfiguredNetworks.get(netId);
+            if (config != null && config.proxySettings == ProxySettings.STATIC) {
+                return new ProxyProperties(config.proxyProperties);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return if the specified network is using static IP
+     */
+    static boolean isUsingStaticIp(int netId) {
+        synchronized (sConfiguredNetworks) {
+            WifiConfiguration config = sConfiguredNetworks.get(netId);
+            if (config != null && config.ipAssignment == IpAssignment.STATIC) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static void sendConfigChangeBroadcast() {
+        if (!ActivityManagerNative.isSystemReady()) return;
+        Intent intent = new Intent(WifiManager.SUPPLICANT_CONFIG_CHANGED_ACTION);
+        sContext.sendBroadcast(intent);
+    }
+
+    static void loadConfiguredNetworks() {
+        String listStr = WifiNative.listNetworksCommand();
+        sLastPriority = 0;
+
+        synchronized (sConfiguredNetworks) {
+            sConfiguredNetworks.clear();
+            sNetworkIds.clear();
+
+            if (listStr == null)
+                return;
+
+            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);
+                if (config.priority > sLastPriority) {
+                    sLastPriority = config.priority;
+                }
+                sConfiguredNetworks.put(config.networkId, config);
+                sNetworkIds.put(configKey(config), config.networkId);
+            }
+        }
+        readIpAndProxyConfigurations();
+        sendConfigChangeBroadcast();
+    }
+
+    /* Mark all networks except specified netId as disabled */
+    private static void markAllNetworksDisabledExcept(int netId) {
+        synchronized (sConfiguredNetworks) {
+            for(WifiConfiguration config : sConfiguredNetworks.values()) {
+                if(config != null && config.networkId != netId) {
+                    config.status = Status.DISABLED;
+                }
+            }
+        }
+    }
+
+    private static void markAllNetworksDisabled() {
+        markAllNetworksDisabledExcept(INVALID_NETWORK_ID);
+    }
+
+    private static void writeIpAndProxyConfigurations() {
+
+        DataOutputStream out = null;
+        try {
+            out = new DataOutputStream(new BufferedOutputStream(
+                    new FileOutputStream(ipConfigFile)));
+
+            out.writeInt(IPCONFIG_FILE_VERSION);
+
+            synchronized (sConfiguredNetworks) {
+                for(WifiConfiguration config : sConfiguredNetworks.values()) {
+                    boolean writeToFile = false;
+
+                    switch (config.ipAssignment) {
+                        case STATIC:
+                            out.writeUTF("ipAssignment");
+                            out.writeUTF(config.ipAssignment.toString());
+                            out.writeUTF("ipAddress");
+                            out.writeInt(config.ipConfig.ipAddress);
+                            out.writeUTF("gateway");
+                            out.writeInt(config.ipConfig.gateway);
+                            out.writeUTF("netmask");
+                            out.writeInt(config.ipConfig.netmask);
+                            out.writeUTF("dns1");
+                            out.writeInt(config.ipConfig.dns1);
+                            out.writeUTF("dns2");
+                            out.writeInt(config.ipConfig.dns2);
+                            writeToFile = true;
+                            break;
+                        case DHCP:
+                            out.writeUTF("ipAssignment");
+                            out.writeUTF(config.ipAssignment.toString());
+                            writeToFile = true;
+                            break;
+                        case UNASSIGNED:
+                            /* Ignore */
+                            break;
+                        default:
+                            Log.e(TAG, "Ignore invalid ip assignment while writing");
+                            break;
+                    }
+
+                    switch (config.proxySettings) {
+                        case STATIC:
+                            out.writeUTF("proxySettings");
+                            out.writeUTF(config.proxySettings.toString());
+                            InetSocketAddress proxy = config.proxyProperties.getSocketAddress();
+                            if (proxy != null) {
+                                out.writeUTF("proxyHost");
+                                out.writeUTF(proxy.getHostName());
+                                out.writeUTF("proxyPort");
+                                out.writeInt(proxy.getPort());
+                                String exclusionList = config.proxyProperties.getExclusionList();
+                                if (exclusionList != null && exclusionList.length() > 0) {
+                                    out.writeUTF("exclusionList");
+                                    out.writeUTF(exclusionList);
+                                }
+                            }
+                            writeToFile = true;
+                            break;
+                        case NONE:
+                            out.writeUTF("proxySettings");
+                            out.writeUTF(config.proxySettings.toString());
+                            writeToFile = true;
+                            break;
+                        case UNASSIGNED:
+                            /* Ignore */
+                            break;
+                        default:
+                            Log.e(TAG, "Ignore invalid proxy settings while writing");
+                            break;
+                    }
+
+                    if (writeToFile) {
+                        out.writeUTF("id");
+                        out.writeInt(configKey(config));
+                        out.writeUTF("EOS");
+                    }
+                }
+            }
+
+        } catch (IOException e) {
+            Log.e(TAG, "Error writing data file");
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (Exception e) {}
+            }
+        }
+    }
+
+    private static void readIpAndProxyConfigurations() {
+
+        DataInputStream in = null;
+        try {
+            in = new DataInputStream(new BufferedInputStream(new FileInputStream(
+                    ipConfigFile)));
+
+            if (in.readInt() != IPCONFIG_FILE_VERSION) {
+                Log.e(TAG, "Bad version on IP configuration file, ignore read");
+                return;
+            }
+
+            while (true) {
+                int id = -1;
+                IpAssignment ipAssignment = IpAssignment.UNASSIGNED;
+                DhcpInfo ipConfig = new DhcpInfo();
+                ProxySettings proxySettings = ProxySettings.UNASSIGNED;
+                String proxyHost = null;
+                int proxyPort = -1;
+                String exclusionList = null;
+                String key;
+
+                do {
+                    key = in.readUTF();
+                    if (key.equals("id")) {
+                        id = in.readInt();
+                    } else if (key.equals("ipAssignment")) {
+                        ipAssignment = IpAssignment.valueOf(in.readUTF());
+                    } else if (key.equals("ipAddress")) {
+                        ipConfig.ipAddress = in.readInt();
+                    } else if (key.equals("gateway")) {
+                        ipConfig.gateway = in.readInt();
+                    } else if (key.equals("netmask")) {
+                        ipConfig.netmask = in.readInt();
+                    } else if (key.equals("dns1")) {
+                        ipConfig.dns1 = in.readInt();
+                    } else if (key.equals("dns2")) {
+                        ipConfig.dns2 = in.readInt();
+                    } else if (key.equals("proxySettings")) {
+                        proxySettings = ProxySettings.valueOf(in.readUTF());
+                    } else if (key.equals("proxyHost")) {
+                        proxyHost = in.readUTF();
+                    } else if (key.equals("proxyPort")) {
+                        proxyPort = in.readInt();
+                    } else if (key.equals("exclusionList")) {
+                        exclusionList = in.readUTF();
+                    } else if (key.equals("EOS")) {
+                        break;
+                    } else {
+                        Log.e(TAG, "Ignore unknown key " + key + "while reading");
+                    }
+                } while (true);
+
+                if (id != -1) {
+                    synchronized (sConfiguredNetworks) {
+                        WifiConfiguration config = sConfiguredNetworks.get(
+                                sNetworkIds.get(id));
+
+                        if (config == null) {
+                            Log.e(TAG, "configuration found for missing network, ignored");
+                        } else {
+                            switch (ipAssignment) {
+                                case STATIC:
+                                    config.ipAssignment = ipAssignment;
+                                    config.ipConfig = ipConfig;
+                                    break;
+                                case DHCP:
+                                    config.ipAssignment = ipAssignment;
+                                    break;
+                                case UNASSIGNED:
+                                    //Ignore
+                                    break;
+                                default:
+                                    Log.e(TAG, "Ignore invalid ip assignment while reading");
+                                    break;
+                            }
+
+                            switch (proxySettings) {
+                                case STATIC:
+                                    config.proxySettings = proxySettings;
+                                    ProxyProperties proxyProperties = new ProxyProperties();
+                                    proxyProperties.setSocketAddress(
+                                            new InetSocketAddress(proxyHost, proxyPort));
+                                    proxyProperties.setExclusionList(exclusionList);
+                                    config.proxyProperties = proxyProperties;
+                                    break;
+                                case NONE:
+                                    config.proxySettings = proxySettings;
+                                    break;
+                                case UNASSIGNED:
+                                    //Ignore
+                                    break;
+                                default:
+                                    Log.e(TAG, "Ignore invalid proxy settings while reading");
+                                    break;
+                            }
+                        }
+                    }
+                } else {
+                    Log.e(TAG,"Missing id while parsing configuration");
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Error parsing configuration");
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (Exception e) {}
+            }
+        }
+    }
+
+    private static int addOrUpdateNetworkNative(WifiConfiguration config) {
+        /*
+         * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
+         * network configuration. Otherwise, the networkId should
+         * refer to an existing configuration.
+         */
+        int netId = config.networkId;
+        boolean updateFailed = true;
+        boolean newNetwork = (netId == INVALID_NETWORK_ID);
+        // networkId of INVALID_NETWORK_ID 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 INVALID_NETWORK_ID;
+          }
+        }
+
+        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;
+                    }
+                }
+            }
+            updateFailed = false;
+        }
+
+        if (updateFailed) {
+            if (newNetwork) {
+                WifiNative.removeNetworkCommand(netId);
+                Log.d(TAG,
+                        "Failed to set a network variable, removed network: "
+                        + netId);
+            }
+            return INVALID_NETWORK_ID;
+        }
+
+        /* An update of the network variables requires reading them
+         * back from the supplicant to update sConfiguredNetworks.
+         * This is because some of the variables (SSID, wep keys &
+         * passphrases) reflect different values when read back than
+         * when written. For example, wep key is stored as * irrespective
+         * of the value sent to the supplicant
+         */
+        WifiConfiguration sConfig;
+        synchronized (sConfiguredNetworks) {
+            sConfig = sConfiguredNetworks.get(netId);
+        }
+        if (sConfig == null) {
+            sConfig = new WifiConfiguration();
+            sConfig.networkId = netId;
+            synchronized (sConfiguredNetworks) {
+                sConfiguredNetworks.put(netId, sConfig);
+            }
+        }
+        readNetworkVariables(sConfig);
+        writeIpAndProxyConfigurationsOnChange(sConfig, config);
+        return netId;
+    }
+
+    /* Compare current and new configuration and write to file on change */
+    private static void writeIpAndProxyConfigurationsOnChange(WifiConfiguration currentConfig,
+            WifiConfiguration newConfig) {
+        boolean newNetwork = (newConfig.networkId == INVALID_NETWORK_ID);
+        boolean writeConfigToFile = false;
+
+        if (newConfig.ipAssignment != IpAssignment.UNASSIGNED) {
+            if (newNetwork ||
+                    (currentConfig.ipAssignment != newConfig.ipAssignment) ||
+                    (currentConfig.ipConfig.ipAddress != newConfig.ipConfig.ipAddress) ||
+                    (currentConfig.ipConfig.gateway != newConfig.ipConfig.gateway) ||
+                    (currentConfig.ipConfig.netmask != newConfig.ipConfig.netmask) ||
+                    (currentConfig.ipConfig.dns1 != newConfig.ipConfig.dns1) ||
+                    (currentConfig.ipConfig.dns2 != newConfig.ipConfig.dns2)) {
+                currentConfig.ipAssignment = newConfig.ipAssignment;
+                currentConfig.ipConfig = newConfig.ipConfig;
+                writeConfigToFile = true;
+            }
+        }
+
+        if (newConfig.proxySettings != ProxySettings.UNASSIGNED) {
+            InetSocketAddress newSockAddr = newConfig.proxyProperties.getSocketAddress();
+            String newExclusionList = newConfig.proxyProperties.getExclusionList();
+
+            InetSocketAddress currentSockAddr = currentConfig.proxyProperties.getSocketAddress();
+            String currentExclusionList = currentConfig.proxyProperties.getExclusionList();
+
+            boolean socketAddressDiffers = false;
+            boolean exclusionListDiffers = false;
+
+            if (newSockAddr != null && currentSockAddr != null ) {
+                socketAddressDiffers = !currentSockAddr.equals(newSockAddr);
+            } else if (newSockAddr != null || currentSockAddr != null) {
+                socketAddressDiffers = true;
+            }
+
+            if (newExclusionList != null && currentExclusionList != null) {
+                exclusionListDiffers = currentExclusionList.equals(newExclusionList);
+            } else if (newExclusionList != null || currentExclusionList != null) {
+                exclusionListDiffers = true;
+            }
+
+            if (newNetwork ||
+                    (currentConfig.proxySettings != newConfig.proxySettings) ||
+                    socketAddressDiffers ||
+                    exclusionListDiffers) {
+                currentConfig.proxySettings = newConfig.proxySettings;
+                currentConfig.proxyProperties = newConfig.proxyProperties;
+                Log.d(TAG, "proxy change SSID = " + currentConfig.SSID + " proxyProperties: " +
+                        currentConfig.proxyProperties.toString());
+                writeConfigToFile = true;
+            }
+        }
+
+        if (writeConfigToFile) {
+            writeIpAndProxyConfigurations();
+            sendConfigChangeBroadcast();
+        }
+    }
+
+    /**
+     * 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 static 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);
+            }
+        }
+    }
+
+    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;
+    }
+
+    /* Returns a unique for a given configuration */
+    private static int configKey(WifiConfiguration config) {
+        String key;
+
+        if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+            key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
+        } else if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
+                config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+            key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
+        } else if (config.wepKeys[0] != null) {
+            key = config.SSID + "WEP";
+        } else {
+            key = config.SSID + KeyMgmt.strings[KeyMgmt.NONE];
+        }
+
+        return key.hashCode();
+    }
+
+    static String dump() {
+        StringBuffer sb = new StringBuffer();
+        String LS = System.getProperty("line.separator");
+        sb.append("sLastPriority ").append(sLastPriority).append(LS);
+        sb.append("Configured networks ").append(LS);
+        for (WifiConfiguration conf : getConfiguredNetworks()) {
+            sb.append(conf).append(LS);
+        }
+        return sb.toString();
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 01bc919..c4a1310 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -16,6 +16,8 @@
 
 package android.net.wifi;
 
+import android.net.DhcpInfo;
+import android.net.ProxyProperties;
 import android.os.Parcelable;
 import android.os.Parcel;
 
@@ -42,6 +44,8 @@
     public static final String priorityVarName = "priority";
     /** {@hide} */
     public static final String hiddenSSIDVarName = "scan_ssid";
+    /** {@hide} */
+    public static final int INVALID_NETWORK_ID = -1;
 
     /** {@hide} */
     public class EnterpriseField {
@@ -216,7 +220,7 @@
     /**
      * The network's SSID. Can either be an ASCII string,
      * which must be enclosed in double quotation marks
-     * (e.g., {@code &quot;MyNetwork&quot;}, or a string of
+     * (e.g., {@code "MyNetwork"}, or a string of
      * hex digits,which are not enclosed in quotes
      * (e.g., {@code 01a243f405}).
      */
@@ -239,7 +243,7 @@
     public String preSharedKey;
     /**
      * Up to four WEP keys. Either an ASCII string enclosed in double
-     * quotation marks (e.g., {@code &quot;abcdef&quot;} or a string
+     * quotation marks (e.g., {@code "abcdef"} or a string
      * of hex digits (e.g., {@code 0102030405}).
      * <p/>
      * When the value of one of these keys is read, the actual key is
@@ -294,9 +298,53 @@
      */
     public BitSet allowedGroupCiphers;
 
+    /**
+     * @hide
+     */
+    public enum IpAssignment {
+        /* Use statically configured IP settings. Configuration can be accessed
+         * with ipConfig */
+        STATIC,
+        /* Use dynamically configured IP settigns */
+        DHCP,
+        /* no IP details are assigned, this is used to indicate
+         * that any existing IP settings should be retained */
+        UNASSIGNED
+    }
+    /**
+     * @hide
+     */
+    public IpAssignment ipAssignment;
+    /**
+     * @hide
+     */
+    public DhcpInfo ipConfig;
+
+    /**
+     * @hide
+     */
+    public enum ProxySettings {
+        /* No proxy is to be used. Any existing proxy settings
+         * should be cleared. */
+        NONE,
+        /* Use statically configured proxy. Configuration can be accessed
+         * with proxyProperties */
+        STATIC,
+        /* no proxy details are assigned, this is used to indicate
+         * that any existing proxy settings should be retained */
+        UNASSIGNED
+    }
+    /**
+     * @hide
+     */
+    public ProxySettings proxySettings;
+    /**
+     * @hide
+     */
+    public ProxyProperties proxyProperties;
 
     public WifiConfiguration() {
-        networkId = -1;
+        networkId = INVALID_NETWORK_ID;
         SSID = null;
         BSSID = null;
         priority = 0;
@@ -312,6 +360,10 @@
         for (EnterpriseField field : enterpriseFields) {
             field.setValue(null);
         }
+        ipAssignment = IpAssignment.UNASSIGNED;
+        ipConfig = new DhcpInfo();
+        proxySettings = ProxySettings.UNASSIGNED;
+        proxyProperties = new ProxyProperties();
     }
 
     public String toString() {
@@ -393,6 +445,17 @@
             if (value != null) sbuf.append(value);
         }
         sbuf.append('\n');
+        if (ipAssignment == IpAssignment.STATIC) {
+            sbuf.append(" ").append("Static IP configuration:").append('\n');
+            sbuf.append(" ").append(ipConfig);
+        }
+        sbuf.append('\n');
+
+        if (proxySettings == ProxySettings.STATIC) {
+            sbuf.append(" ").append("Proxy configuration:").append('\n');
+            sbuf.append(" ").append(proxyProperties);
+        }
+        sbuf.append('\n');
         return sbuf.toString();
     }
 
@@ -432,6 +495,38 @@
         return 0;
     }
 
+    /** copy constructor {@hide} */
+    public WifiConfiguration(WifiConfiguration source) {
+        if (source != null) {
+            networkId = source.networkId;
+            status = source.status;
+            SSID = source.SSID;
+            BSSID = source.BSSID;
+            preSharedKey = source.preSharedKey;
+
+            wepKeys = new String[4];
+            for (int i = 0; i < wepKeys.length; i++)
+                wepKeys[i] = source.wepKeys[i];
+
+            wepTxKeyIndex = source.wepTxKeyIndex;
+            priority = source.priority;
+            hiddenSSID = source.hiddenSSID;
+            allowedKeyManagement   = (BitSet) source.allowedKeyManagement.clone();
+            allowedProtocols       = (BitSet) source.allowedProtocols.clone();
+            allowedAuthAlgorithms  = (BitSet) source.allowedAuthAlgorithms.clone();
+            allowedPairwiseCiphers = (BitSet) source.allowedPairwiseCiphers.clone();
+            allowedGroupCiphers    = (BitSet) source.allowedGroupCiphers.clone();
+
+            for (int i = 0; i < source.enterpriseFields.length; i++) {
+                enterpriseFields[i].setValue(source.enterpriseFields[i].value());
+            }
+            ipAssignment = source.ipAssignment;
+            ipConfig = new DhcpInfo(source.ipConfig);
+            proxySettings = source.proxySettings;
+            proxyProperties = new ProxyProperties(source.proxyProperties);
+        }
+    }
+
     /** Implement the Parcelable interface {@hide} */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(networkId);
@@ -454,6 +549,16 @@
         for (EnterpriseField field : enterpriseFields) {
             dest.writeString(field.value());
         }
+        dest.writeString(ipAssignment.name());
+        dest.writeInt(ipConfig.ipAddress);
+        dest.writeInt(ipConfig.netmask);
+        dest.writeInt(ipConfig.gateway);
+        dest.writeInt(ipConfig.dns1);
+        dest.writeInt(ipConfig.dns2);
+        dest.writeInt(ipConfig.serverAddress);
+        dest.writeInt(ipConfig.leaseDuration);
+        dest.writeString(proxySettings.name());
+        dest.writeParcelable(proxyProperties, flags);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -480,6 +585,17 @@
                 for (EnterpriseField field : config.enterpriseFields) {
                     field.setValue(in.readString());
                 }
+
+                config.ipAssignment = IpAssignment.valueOf(in.readString());
+                config.ipConfig.ipAddress = in.readInt();
+                config.ipConfig.netmask = in.readInt();
+                config.ipConfig.gateway = in.readInt();
+                config.ipConfig.dns1 = in.readInt();
+                config.ipConfig.dns2 = in.readInt();
+                config.ipConfig.serverAddress = in.readInt();
+                config.ipConfig.leaseDuration = in.readInt();
+                config.proxySettings = ProxySettings.valueOf(in.readString());
+                config.proxyProperties = in.readParcelable(null);
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index f883588..0b3a782 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -166,7 +166,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.
      *
@@ -175,7 +175,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.
@@ -185,7 +185,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.
      *
@@ -194,7 +194,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
@@ -204,7 +204,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
@@ -275,7 +275,14 @@
      * @see #ERROR_AUTHENTICATING
      */
     public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError";
-
+    /**
+     * Broadcast intent action indicating that the supplicant configuration changed.
+     * This can be as a result of adding/updating/deleting a network
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String SUPPLICANT_CONFIG_CHANGED_ACTION =
+        "android.net.wifi.supplicant.CONFIG_CHANGE";
     /**
      * An access point scan has completed, and results are available from the supplicant.
      * Call {@link #getScanResults()} to obtain the results.
@@ -294,11 +301,35 @@
     public static final String EXTRA_NEW_RSSI = "newRssi";
 
     /**
+     * Broadcast intent action indicating that the IP configuration
+     * changed on wifi.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String CONFIG_CHANGED_ACTION = "android.net.wifi.CONFIG_CHANGED";
+
+    /**
+     * The lookup key for a {@link android.net.LinkProperties} object associated with the
+     * Wi-Fi network. Retrieve with
+     * {@link android.content.Intent#getParcelableExtra(String)}.
+     * @hide
+     */
+    public static final String EXTRA_LINK_PROPERTIES = "linkProperties";
+
+    /**
+     * The lookup key for a {@link android.net.LinkCapabilities} object associated with the
+     * Wi-Fi network. Retrieve with
+     * {@link android.content.Intent#getParcelableExtra(String)}.
+     * @hide
+     */
+    public static final String EXTRA_LINK_CAPABILITIES = "linkCapabilities";
+
+    /**
      * The network IDs of the configured networks could have changed.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED";
-    
+
     /**
      * Activity Action: Pick a Wi-Fi network to connect to.
      * <p>Input: Nothing.
@@ -308,16 +339,6 @@
     public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
 
     /**
-     * In this Wi-Fi lock mode, Wi-Fi will behave as in the mode
-     * {@link #WIFI_MODE_FULL} but it operates at high performance
-     * at the expense of power. This mode should be used
-     * only when the wifi connection needs to have minimum loss and low
-     * latency as it can impact the battery life.
-     * @hide
-     */
-    public static final int WIFI_MODE_FULL_HIGH_PERF = 3;
-
-    /**
      * In this Wi-Fi lock mode, Wi-Fi will be kept active,
      * and will behave normally, i.e., it will attempt to automatically
      * establish a connection to a remembered access point that is
@@ -513,7 +534,8 @@
      */
     public boolean disconnect() {
         try {
-            return mService.disconnect();
+            mService.disconnect();
+            return true;
         } catch (RemoteException e) {
             return false;
         }
@@ -527,7 +549,8 @@
      */
     public boolean reconnect() {
         try {
-            return mService.reconnect();
+            mService.reconnect();
+            return true;
         } catch (RemoteException e) {
             return false;
         }
@@ -541,7 +564,8 @@
      */
     public boolean reassociate() {
         try {
-            return mService.reassociate();
+            mService.reassociate();
+            return true;
         } catch (RemoteException e) {
             return false;
         }
@@ -756,8 +780,9 @@
         } else if (rssi >= MAX_RSSI) {
             return numLevels - 1;
         } else {
-            int partitionSize = (MAX_RSSI - MIN_RSSI) / (numLevels - 1);
-            return (rssi - MIN_RSSI) / partitionSize;
+            float inputRange = (MAX_RSSI - MIN_RSSI);
+            float outputRange = (numLevels - 1);
+            return (int)((float)(rssi - MIN_RSSI) * outputRange / inputRange);
         }
     }
     
@@ -851,6 +876,192 @@
         }
     }
 
+   /**
+     * Start the driver and connect to network.
+     *
+     * This function will over-ride WifiLock and device idle status. For example,
+     * even if the device is idle or there is only a scan-only lock held,
+     * a start wifi would mean that wifi connection is kept active until
+     * a stopWifi() is sent.
+     *
+     * This API is used by WifiStateTracker
+     *
+     * @return {@code true} if the operation succeeds else {@code false}
+     * @hide
+     */
+    public boolean startWifi() {
+        try {
+            mService.startWifi();
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Disconnect from a network (if any) and stop the driver.
+     *
+     * This function will over-ride WifiLock and device idle status. Wi-Fi
+     * stays inactive until a startWifi() is issued.
+     *
+     * This API is used by WifiStateTracker
+     *
+     * @return {@code true} if the operation succeeds else {@code false}
+     * @hide
+     */
+    public boolean stopWifi() {
+        try {
+            mService.stopWifi();
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Add a bssid to the supplicant blacklist
+     *
+     * This API is used by WifiWatchdogService
+     *
+     * @return {@code true} if the operation succeeds else {@code false}
+     * @hide
+     */
+    public boolean addToBlacklist(String bssid) {
+        try {
+            mService.addToBlacklist(bssid);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Clear the supplicant blacklist
+     *
+     * This API is used by WifiWatchdogService
+     *
+     * @return {@code true} if the operation succeeds else {@code false}
+     * @hide
+     */
+    public boolean clearBlacklist() {
+        try {
+            mService.clearBlacklist();
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /* TODO: deprecate synchronous API and open up the following API */
+    /**
+     * Connect to a network with the given configuration. The network also
+     * gets added to the supplicant configuration.
+     *
+     * For a new network, this function is used instead of a
+     * sequence of addNetwork(), enableNetwork(), saveConfiguration() and
+     * reconnect()
+     *
+     * @param config the set of variables that describe the configuration,
+     *            contained in a {@link WifiConfiguration} object.
+     * @hide
+     */
+    public void connectNetwork(WifiConfiguration config) {
+        if (config == null) {
+            return;
+        }
+        try {
+            mService.connectNetworkWithConfig(config);
+        } catch (RemoteException e) { }
+    }
+
+    /**
+     * Connect to a network with the given networkId.
+     *
+     * This function is used instead of a enableNetwork(), saveConfiguration() and
+     * reconnect()
+     *
+     * @param networkId the network id identifiying the network in the
+     *                supplicant configuration list
+     * @hide
+     */
+    public void connectNetwork(int networkId) {
+        if (networkId < 0) {
+            return;
+        }
+        try {
+            mService.connectNetworkWithId(networkId);
+        } catch (RemoteException e) { }
+    }
+
+    /**
+     * Save the given network in the supplicant config. If the network already
+     * exists, the configuration is updated. A new network is enabled
+     * by default.
+     *
+     * For a new network, this function is used instead of a
+     * sequence of addNetwork(), enableNetwork() and saveConfiguration().
+     *
+     * For an existing network, it accomplishes the task of updateNetwork()
+     * and saveConfiguration()
+     *
+     * @param config the set of variables that describe the configuration,
+     *            contained in a {@link WifiConfiguration} object.
+     * @hide
+     */
+    public void saveNetwork(WifiConfiguration config) {
+        if (config == null) {
+            return;
+        }
+        try {
+            mService.saveNetwork(config);
+        } catch (RemoteException e) { }
+    }
+
+    /**
+     * Delete the network in the supplicant config.
+     *
+     * This function is used instead of a sequence of removeNetwork()
+     * and saveConfiguration().
+     *
+     * @param config the set of variables that describe the configuration,
+     *            contained in a {@link WifiConfiguration} object.
+     * @hide
+     */
+    public void forgetNetwork(int netId) {
+        if (netId < 0) {
+            return;
+        }
+        try {
+            mService.forgetNetwork(netId);
+        } catch (RemoteException e) { }
+    }
+
+    /**
+     * Start Wi-fi protected setup push button Configuration
+     *
+     * @param bssid BSSID of the access point
+     * @hide
+     */
+    public void startWpsPbc(String bssid) {
+        try {
+            mService.startWpsPbc(bssid);
+        } catch (RemoteException e) { }
+    }
+
+    /**
+     * Start Wi-fi Protected Setup pin method configuration
+     *
+     * @param bssid BSSID of the access point
+     * @param apPin PIN issued by the access point
+     *
+     * @hide
+     */
+    public void startWpsPin(String bssid, int apPin) {
+        try {
+            mService.startWpsPin(bssid, apPin);
+        } catch (RemoteException e) { }
+    }
+
     /**
      * Allows an application to keep the Wi-Fi radio awake.
      * Normally the Wi-Fi radio may turn off when the user has not used the device in a while.
@@ -1033,9 +1244,8 @@
     /**
      * Creates a new WifiLock.
      *
-     * @param lockType the type of lock to create. See {@link #WIFI_MODE_FULL},
-     * and {@link #WIFI_MODE_SCAN_ONLY} for descriptions of the types of Wi-Fi locks.
-     *
+     * @param lockType the type of lock to create. See {@link #WIFI_MODE_FULL} and
+     * {@link #WIFI_MODE_SCAN_ONLY} for descriptions of the types of Wi-Fi locks.
      * @param tag a tag for the WifiLock to identify it in debugging messages.  This string is 
      *            never shown to the user under normal conditions, but should be descriptive 
      *            enough to identify your application and the specific WifiLock within it, if it
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 266d801..af3132f 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -19,14 +19,13 @@
 import android.util.Log;
 import android.util.Config;
 import android.net.NetworkInfo;
-import android.net.NetworkStateTracker;
 
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
 
 /**
  * Listens for events from the wpa_supplicant server, and passes them on
- * to the {@link WifiStateTracker} for handling. Runs in its own thread.
+ * to the {@link WifiStateMachine} for handling. Runs in its own thread.
  *
  * @hide
  */
@@ -117,7 +116,7 @@
     private static Pattern mConnectedEventPattern =
         Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) ");
 
-    private final WifiStateTracker mWifiStateTracker;
+    private final WifiStateMachine mWifiStateMachine;
 
     /**
      * This indicates the supplicant connection for the monitor is closed
@@ -139,31 +138,27 @@
      */
     private static final int MAX_RECV_ERRORS    = 10;
 
-    public WifiMonitor(WifiStateTracker tracker) {
-        mWifiStateTracker = tracker;
+    public WifiMonitor(WifiStateMachine wifiStateMachine) {
+        mWifiStateMachine = wifiStateMachine;
     }
 
     public void startMonitoring() {
         new MonitorThread().start();
     }
 
-    public NetworkStateTracker getNetworkStateTracker() {
-        return mWifiStateTracker;
-    }
-
     class MonitorThread extends Thread {
         public MonitorThread() {
             super("WifiMonitor");
         }
-        
+
         public void run() {
 
             if (connectToSupplicant()) {
                 // Send a message indicating that it is now possible to send commands
                 // to the supplicant
-                mWifiStateTracker.notifySupplicantConnection();
+                mWifiStateMachine.notifySupplicantConnection();
             } else {
-                mWifiStateTracker.notifySupplicantLost();
+                mWifiStateMachine.notifySupplicantLost();
                 return;
             }
 
@@ -259,7 +254,7 @@
                     }
 
                     // notify and exit
-                    mWifiStateTracker.notifySupplicantLost();
+                    mWifiStateMachine.notifySupplicantLost();
                     break;
                 } else {
                     handleEvent(event, eventData);
@@ -272,7 +267,7 @@
             int connectTries = 0;
 
             while (true) {
-                if (mWifiStateTracker.connectToSupplicant()) {
+                if (WifiNative.connectToSupplicant()) {
                     return true;
                 }
                 if (connectTries++ < 3) {
@@ -285,7 +280,7 @@
         }
 
         private void handlePasswordKeyMayBeIncorrect() {
-            mWifiStateTracker.notifyPasswordKeyMayBeIncorrect();
+            mWifiStateMachine.notifyPasswordKeyMayBeIncorrect();
         }
 
         private void handleDriverEvent(String state) {
@@ -293,11 +288,11 @@
                 return;
             }
             if (state.equals("STOPPED")) {
-                mWifiStateTracker.notifyDriverStopped();
+                mWifiStateMachine.notifyDriverStopped();
             } else if (state.equals("STARTED")) {
-                mWifiStateTracker.notifyDriverStarted();
+                mWifiStateMachine.notifyDriverStarted();
             } else if (state.equals("HANGED")) {
-                mWifiStateTracker.notifyDriverHung();
+                mWifiStateMachine.notifyDriverHung();
             }
         }
 
@@ -318,7 +313,7 @@
                     break;
 
                 case SCAN_RESULTS:
-                    mWifiStateTracker.notifyScanResultsAvailable();
+                    mWifiStateMachine.notifyScanResultsAvailable();
                     break;
 
                 case UNKNOWN:
@@ -375,7 +370,7 @@
             if (newSupplicantState == SupplicantState.INVALID) {
                 Log.w(TAG, "Invalid supplicant state: " + newState);
             }
-            mWifiStateTracker.notifyStateChange(networkId, BSSID, newSupplicantState);
+            mWifiStateMachine.notifySupplicantStateChange(networkId, BSSID, newSupplicantState);
         }
     }
 
@@ -395,7 +390,7 @@
                 }
             }
         }
-        mWifiStateTracker.notifyStateChange(newState, BSSID, networkId);
+        mWifiStateMachine.notifyNetworkStateChange(newState, BSSID, networkId);
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 25f05c0..1251a25 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -41,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();
 
@@ -109,6 +111,10 @@
 
     public native static boolean setPowerModeCommand(int mode);
 
+    public native static int getBandCommand();
+
+    public native static boolean setBandCommand(int band);
+
     public native static int getPowerModeCommand();
 
     public native static boolean setNumAllowedChannelsCommand(int numChannels);
@@ -145,12 +151,14 @@
 
     public native static boolean clearBlacklistCommand();
 
+    public native static boolean startWpsPbcCommand(String bssid);
+
+    public native static boolean startWpsPinCommand(String bssid, int apPin);
+
     public native static boolean doDhcpRequest(DhcpInfo results);
 
     public native static String getDhcpError();
 
-    public native static boolean setSuspendOptimizationsCommand(boolean enabled);
-
     /**
      * 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/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
new file mode 100644
index 0000000..572abc0
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -0,0 +1,3191 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wifi;
+
+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;
+
+/**
+ * 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.LinkAddress;
+import android.net.NetworkInfo;
+import android.net.DhcpInfo;
+import android.net.NetworkUtils;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo.DetailedState;
+import android.net.LinkProperties;
+import android.net.ProxyProperties;
+import android.net.wifi.WifiConfiguration.Status;
+import android.os.Binder;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Process;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.app.backup.IBackupManager;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
+import android.content.Intent;
+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.Iterator;
+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,
+ * and all changes in connectivity state are initiated here.
+ *
+ * @hide
+ */
+//TODO: we still need frequent scanning for the case when
+// we issue disconnect but need scan results for open network notification
+public class WifiStateMachine extends HierarchicalStateMachine {
+
+    private static final String TAG = "WifiStateMachine";
+    private static final String NETWORKTYPE = "WIFI";
+    private static final boolean DBG = false;
+
+    /* TODO: fetch a configurable interface */
+    private static final String SOFTAP_IFACE = "wl0.1";
+
+    private WifiMonitor mWifiMonitor;
+    private INetworkManagementService nwService;
+    private ConnectivityManager mCm;
+
+    /* Scan results handling */
+    private List<ScanResult> mScanResults;
+    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 int mNumAllowedChannels = 0;
+    private int mLastSignalLevel = -1;
+    private String mLastBssid;
+    private int mLastNetworkId;
+    private boolean mEnableRssiPolling = false;
+    private boolean mPasswordKeyMayBeIncorrect = false;
+    private int mReconnectCount = 0;
+    private boolean mIsScanMode = false;
+
+    /**
+     * 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;
+
+    private LinkProperties mLinkProperties;
+
+    // Held during driver load and unload
+    private static PowerManager.WakeLock sWakeLock;
+
+    private Context mContext;
+
+    private DhcpInfo mDhcpInfo;
+    private WifiInfo mWifiInfo;
+    private NetworkInfo mNetworkInfo;
+    private SupplicantStateTracker mSupplicantStateTracker;
+    /* Connection to a specific network involves disabling all networks,
+     * this flag tracks if networks need to be re-enabled */
+    private boolean mEnableAllNetworks = false;
+    /* Track if WPS was started since we need to re-enable networks
+     * and load configuration afterwards */
+    private boolean mWpsStarted = false;
+
+    // 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;
+    /* Save configuration */
+    private static final int CMD_SAVE_CONFIG                      = 58;
+
+    /* 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 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;
+    /* Connect to a specified network (network id
+     * or WifiConfiguration) This involves increasing
+     * the priority of the network, enabling the network
+     * (while disabling others) and issuing a reconnect.
+     * Note that CMD_RECONNECT just does a reconnect to
+     * an existing network. All the networks get enabled
+     * upon a successful connection or a failure.
+     */
+    private static final int CMD_CONNECT_NETWORK                  = 90;
+    /* Save the specified network. This involves adding
+     * an enabled network (if new) and updating the
+     * config and issuing a save on supplicant config.
+     */
+    private static final int CMD_SAVE_NETWORK                     = 91;
+    /* Delete the specified network. This involves
+     * removing the network and issuing a save on
+     * supplicant config.
+     */
+    private static final int CMD_FORGET_NETWORK                   = 92;
+    /* Start Wi-Fi protected setup */
+    private static final int CMD_START_WPS                        = 93;
+
+
+    /**
+     * 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;
+
+    /* Auto allows 802.11A/B/G operation */
+    private static final int BAND_AUTO = 0;
+    /* 5GHz allows 802.11A operation */
+    private static final int BAND_5G = 1;
+    /* 2.4GHz allows 802.11B/G operation */
+    private static final int BAND_2G = 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();
+
+
+    /**
+     * One of  {@link WifiManager#WIFI_STATE_DISABLED},
+     *         {@link WifiManager#WIFI_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_STATE_UNKNOWN}
+     *
+     */
+    private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
+
+    /**
+     * 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 final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid());
+    private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid());
+
+    /**
+     * Keep track of whether WIFI is running.
+     */
+    private boolean mIsRunning = false;
+
+    /**
+     * Keep track of whether we last told the battery stats we had started.
+     */
+    private boolean mReportedRunning = false;
+
+    /**
+     * Most recently set source of starting WIFI.
+     */
+    private final WorkSource mRunningWifiUids = new WorkSource();
+
+    /**
+     * The last reported UIDs that were responsible for starting WIFI.
+     */
+    private final WorkSource mLastRunningWifiUids = new WorkSource();
+
+    private final IBatteryStats mBatteryStats;
+
+    public WifiStateMachine(Context context) {
+        super(TAG);
+
+        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);
+        mDhcpInfo = new DhcpInfo();
+        mWifiInfo = new WifiInfo();
+        mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
+        mSupplicantStateTracker = new SupplicantStateTracker(context, getHandler());
+
+        mLinkProperties = new LinkProperties();
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+                                    BluetoothProfile.A2DP);
+            adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+                                    BluetoothProfile.HEADSET);
+        }
+
+        mNetworkInfo.setIsAvailable(false);
+        mLinkProperties.clear();
+        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();
+                }
+        };
+
+        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();
+    }
+
+    /*********************************************************
+     * Methods exposed for public use
+     ********************************************************/
+
+    /**
+     * TODO: doc
+     */
+    public boolean syncPingSupplicant() {
+        return sendSyncMessage(CMD_PING_SUPPLICANT).boolValue;
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void startScan(boolean forceActive) {
+        sendMessage(obtainMessage(CMD_START_SCAN, forceActive ?
+                SCAN_ACTIVE : SCAN_PASSIVE, 0));
+    }
+
+    /**
+     * TODO: doc
+     */
+    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 {
+            sendMessage(CMD_STOP_SUPPLICANT);
+            /* Argument is the state that is entered upon success */
+            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
+        }
+    }
+
+    /**
+     * TODO: doc
+     */
+    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));
+        }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public int syncGetWifiState() {
+        return mWifiState.get();
+    }
+
+    /**
+     * TODO: doc
+     */
+    public String syncGetWifiStateByName() {
+        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]";
+        }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public int syncGetWifiApState() {
+        return mWifiApState.get();
+    }
+
+    /**
+     * TODO: doc
+     */
+    public String syncGetWifiApStateByName() {
+        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 syncRequestConnectionInfo() {
+        return mWifiInfo;
+    }
+
+    public DhcpInfo syncGetDhcpInfo() {
+        synchronized (mDhcpInfo) {
+            return new DhcpInfo(mDhcpInfo);
+        }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setDriverStart(boolean enable) {
+      if (enable) {
+          sendMessage(CMD_START_DRIVER);
+      } else {
+          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> syncGetScanResultsList() {
+        return mScanResults;
+    }
+
+    /**
+     * Disconnect from Access Point
+     */
+    public void disconnectCommand() {
+        sendMessage(CMD_DISCONNECT);
+    }
+
+    /**
+     * Initiate a reconnection to AP
+     */
+    public void reconnectCommand() {
+        sendMessage(CMD_RECONNECT);
+    }
+
+    /**
+     * Initiate a re-association to AP
+     */
+    public void reassociateCommand() {
+        sendMessage(CMD_REASSOCIATE);
+    }
+
+    /**
+     * Add a network synchronously
+     *
+     * @return network id of the new network
+     */
+    public int syncAddOrUpdateNetwork(WifiConfiguration config) {
+        return sendSyncMessage(CMD_ADD_OR_UPDATE_NETWORK, config).intValue;
+    }
+
+    public List<WifiConfiguration> syncGetConfiguredNetworks() {
+        return WifiConfigStore.getConfiguredNetworks();
+    }
+
+    /**
+     * Delete a network
+     *
+     * @param networkId id of the network to be removed
+     */
+    public boolean syncRemoveNetwork(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
+     *
+     * @param netId network id of the network
+     * @param disableOthers true, if all other networks have to be disabled
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     */
+    public boolean syncEnableNetwork(int netId, boolean disableOthers) {
+        return sendSyncMessage(CMD_ENABLE_NETWORK,
+                new EnableNetParams(netId, disableOthers)).boolValue;
+    }
+
+    /**
+     * Disable a network
+     *
+     * @param netId network id of the network
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     */
+    public boolean syncDisableNetwork(int netId) {
+        return sendSyncMessage(obtainMessage(CMD_DISABLE_NETWORK, netId, 0)).boolValue;
+    }
+
+    /**
+     * Blacklist a BSSID. This will avoid the AP if there are
+     * alternate APs to connect
+     *
+     * @param bssid BSSID of the network
+     */
+    public void addToBlacklist(String bssid) {
+        sendMessage(obtainMessage(CMD_BLACKLIST_NETWORK, bssid));
+    }
+
+    /**
+     * Clear the blacklist list
+     *
+     */
+    public void clearBlacklist() {
+        sendMessage(obtainMessage(CMD_CLEAR_BLACKLIST));
+    }
+
+    public void connectNetwork(int netId) {
+        sendMessage(obtainMessage(CMD_CONNECT_NETWORK, netId, 0));
+    }
+
+    public void connectNetwork(WifiConfiguration wifiConfig) {
+        /* arg1 is used to indicate netId, force a netId value of -1 when
+         * we are passing a configuration since the default value of
+         * 0 is a valid netId
+         */
+        sendMessage(obtainMessage(CMD_CONNECT_NETWORK, -1, 0, wifiConfig));
+    }
+
+    public void saveNetwork(WifiConfiguration wifiConfig) {
+        sendMessage(obtainMessage(CMD_SAVE_NETWORK, wifiConfig));
+    }
+
+    public void forgetNetwork(int netId) {
+        sendMessage(obtainMessage(CMD_FORGET_NETWORK, netId, 0));
+    }
+
+    public void startWpsPbc(String bssid) {
+        sendMessage(obtainMessage(CMD_START_WPS, bssid));
+    }
+
+    public void startWpsPin(String bssid, int apPin) {
+        sendMessage(obtainMessage(CMD_START_WPS, apPin, 0, bssid));
+    }
+
+    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 int syncGetRssi() {
+        return sendSyncMessage(CMD_GET_RSSI).intValue;
+    }
+
+    /**
+     * Get approx RSSI to currently connected network
+     *
+     * @return RSSI value, -1 on failure
+     */
+    public int syncGetRssiApprox() {
+        return sendSyncMessage(CMD_GET_RSSI_APPROX).intValue;
+    }
+
+    /**
+     * Get link speed to currently connected network
+     *
+     * @return link speed, -1 on failure
+     */
+    public int syncGetLinkSpeed() {
+        return sendSyncMessage(CMD_GET_LINK_SPEED).intValue;
+    }
+
+    /**
+     * Get MAC address of radio
+     *
+     * @return MAC address, null on failure
+     */
+    public String syncGetMacAddress() {
+        return sendSyncMessage(CMD_GET_MAC_ADDR).stringValue;
+    }
+
+    /**
+     * Start packet filtering
+     */
+    public void startPacketFiltering() {
+        sendMessage(CMD_START_PACKET_FILTERING);
+    }
+
+    /**
+     * Stop packet filtering
+     */
+    public void stopPacketFiltering() {
+        sendMessage(CMD_STOP_PACKET_FILTERING);
+    }
+
+    /**
+     * Set power mode
+     * @param mode
+     *     DRIVER_POWER_MODE_AUTO
+     *     DRIVER_POWER_MODE_ACTIVE
+     */
+    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.
+     */
+    public void setNumAllowedChannels() {
+        try {
+            setNumAllowedChannels(
+                    Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS));
+        } catch (Settings.SettingNotFoundException e) {
+            if (mNumAllowedChannels != 0) {
+                setNumAllowedChannels(mNumAllowedChannels);
+            }
+            // otherwise, use the driver default
+        }
+    }
+
+    /**
+     * Set the number of radio frequency channels that are allowed to be used
+     * in the current regulatory domain.
+     * @param numChannels the number of allowed channels. Must be greater than 0
+     * and less than or equal to 16.
+     */
+    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 int getNumAllowedChannels() {
+        return -1;
+    }
+
+    /**
+     * Set bluetooth coex mode:
+     *
+     * @param mode
+     *  BLUETOOTH_COEXISTENCE_MODE_ENABLED
+     *  BLUETOOTH_COEXISTENCE_MODE_DISABLED
+     *  BLUETOOTH_COEXISTENCE_MODE_SENSE
+     */
+    public void setBluetoothCoexistenceMode(int mode) {
+        sendMessage(obtainMessage(CMD_SET_BLUETOOTH_COEXISTENCE, mode, 0));
+    }
+
+    /**
+     * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
+     * some of the low-level scan parameters used by the driver are changed to
+     * reduce interference with A2DP streaming.
+     *
+     * @param isBluetoothPlaying whether to enable or disable this mode
+     */
+    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
+     *
+     * TODO: deprecate this
+     */
+    public boolean syncSaveConfig() {
+        return sendSyncMessage(CMD_SAVE_CONFIG).boolValue;
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void requestCmWakeLock() {
+        sendMessage(CMD_REQUEST_CM_WAKELOCK);
+    }
+
+    public void updateBatteryWorkSource(WorkSource newSource) {
+        synchronized (mRunningWifiUids) {
+            try {
+                if (newSource != null) {
+                    mRunningWifiUids.set(newSource);
+                }
+                if (mIsRunning) {
+                    if (mReportedRunning) {
+                        // If the work source has changed since last time, need
+                        // to remove old work from battery stats.
+                        if (mLastRunningWifiUids.diff(mRunningWifiUids)) {
+                            mBatteryStats.noteWifiRunningChanged(mLastRunningWifiUids,
+                                    mRunningWifiUids);
+                            mLastRunningWifiUids.set(mRunningWifiUids);
+                        }
+                    } else {
+                        // Now being started, report it.
+                        mBatteryStats.noteWifiRunning(mRunningWifiUids);
+                        mLastRunningWifiUids.set(mRunningWifiUids);
+                        mReportedRunning = true;
+                    }
+                } else {
+                    if (mReportedRunning) {
+                        // Last reported we were running, time to stop.
+                        mBatteryStats.noteWifiStopped(mLastRunningWifiUids);
+                        mLastRunningWifiUids.clear();
+                        mReportedRunning = false;
+                    }
+                }
+            } catch (RemoteException ignore) {
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        String LS = System.getProperty("line.separator");
+        sb.append("current HSM state: ").append(getCurrentState().getName()).append(LS);
+        sb.append("mLinkProperties ").append(mLinkProperties).append(LS);
+        sb.append("mWifiInfo ").append(mWifiInfo).append(LS);
+        sb.append("mDhcpInfo ").append(mDhcpInfo).append(LS);
+        sb.append("mNetworkInfo ").append(mNetworkInfo).append(LS);
+        sb.append("mNumAllowedChannels ").append(mNumAllowedChannels).append(LS);
+        sb.append("mLastSignalLevel ").append(mLastSignalLevel).append(LS);
+        sb.append("mLastBssid ").append(mLastBssid).append(LS);
+        sb.append("mLastNetworkId ").append(mLastNetworkId).append(LS);
+        sb.append("mEnableAllNetworks ").append(mEnableAllNetworks).append(LS);
+        sb.append("mEnableRssiPolling ").append(mEnableRssiPolling).append(LS);
+        sb.append("mPasswordKeyMayBeIncorrect ").append(mPasswordKeyMayBeIncorrect).append(LS);
+        sb.append("mReconnectCount ").append(mReconnectCount).append(LS);
+        sb.append("mIsScanMode ").append(mIsScanMode).append(LS);
+        sb.append("Supplicant status").append(LS)
+                .append(WifiNative.statusCommand()).append(LS).append(LS);
+
+        sb.append(WifiConfigStore.dump());
+        return sb.toString();
+    }
+
+    /*********************************************************
+     * Internal private functions
+     ********************************************************/
+
+    class SyncReturn {
+        boolean boolValue;
+        int intValue;
+        String stringValue;
+        Object objValue;
+    }
+
+    class SyncParams {
+        Object mParameter;
+        SyncReturn mSyncReturn;
+        SyncParams() {
+            mSyncReturn = new SyncReturn();
+        }
+        SyncParams(Object p) {
+            mParameter = p;
+            mSyncReturn = new SyncReturn();
+        }
+    }
+
+    /**
+     * message.obj is used to store SyncParams
+     */
+    private SyncReturn syncedSend(Message msg) {
+        SyncParams syncParams = (SyncParams) msg.obj;
+        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();
+            } else if (wifiState == WIFI_STATE_DISABLED) {
+                mBatteryStats.noteWifiOff();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to note battery stats in wifi");
+        }
+
+        mWifiState.set(wifiState);
+
+        if (DBG) Log.d(TAG, "setWifiState: " + syncGetWifiStateByName());
+
+        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();
+            } else if (wifiApState == WIFI_AP_STATE_DISABLED) {
+                mBatteryStats.noteWifiOff();
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "Failed to note battery stats in wifi");
+        }
+
+        // Update state
+        mWifiApState.set(wifiApState);
+
+        if (DBG) Log.d(TAG, "setWifiApState: " + syncGetWifiApStateByName());
+
+        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);
+    }
+
+    /**
+     * 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 {
+                    Log.w(TAG, "Misformatted scan result text with " +
+                          result.length + " fields: " + line);
+                }
+            }
+        }
+
+        return scanResult;
+    }
+
+    /**
+     * 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 String fetchSSID() {
+        String status = WifiNative.statusCommand();
+        if (status == null) {
+            return null;
+        }
+        // extract ssid from a series of "name=value"
+        String[] lines = status.split("\n");
+        for (String line : lines) {
+            String[] prop = line.split(" *= *");
+            if (prop.length < 2) continue;
+            String name = prop[0];
+            String value = prop[1];
+            if (name.equalsIgnoreCase("ssid")) return value;
+        }
+        return null;
+    }
+
+    private void configureLinkProperties() {
+
+        mLinkProperties.setInterfaceName(mInterfaceName);
+
+        // TODO - fix this for v6
+        synchronized (mDhcpInfo) {
+            mLinkProperties.addLinkAddress(new LinkAddress(
+                    NetworkUtils.intToInetAddress(mDhcpInfo.ipAddress),
+                    NetworkUtils.intToInetAddress(mDhcpInfo.netmask)));
+            mLinkProperties.setGateway(NetworkUtils.intToInetAddress(mDhcpInfo.gateway));
+            mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns1));
+            mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns2));
+        }
+
+        ProxyProperties proxyProperties = WifiConfigStore.getProxyProperties(mLastNetworkId);
+        if (proxyProperties != null) {
+            mLinkProperties.setHttpProxy(proxyProperties);
+            Log.d(TAG, "netId=" + mLastNetworkId  + " proxy configured: "
+                    + proxyProperties.toString());
+        }
+    }
+
+    private int getMaxDhcpRetries() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                                      Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT,
+                                      DEFAULT_MAX_DHCP_RETRIES);
+    }
+
+    /**
+     * 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 synchronized boolean shouldDisableCoexistenceMode() {
+        Set<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
+
+        return (devices.size() != 0 ? true : false);
+    }
+
+    private synchronized void checkIsBluetoothPlaying() {
+        boolean isBluetoothPlaying = false;
+        if (mBluetoothA2dp != null) {
+            Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedDevices();
+
+            for (BluetoothDevice device : connected) {
+                if (mBluetoothA2dp.isA2dpPlaying(device)) {
+                    isBluetoothPlaying = true;
+                    break;
+                }
+            }
+        }
+        setBluetoothScanMode(isBluetoothPlaying);
+    }
+
+    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+        new BluetoothProfile.ServiceListener() {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            synchronized (WifiStateMachine.this) {
+                if (profile == BluetoothProfile.HEADSET) {
+                    mBluetoothHeadset = (BluetoothHeadset) proxy;
+                } else if (profile == BluetoothProfile.A2DP) {
+                    mBluetoothA2dp = (BluetoothA2dp)proxy;
+                }
+            }
+        }
+
+        public void onServiceDisconnected(int profile) {
+            synchronized (WifiStateMachine.this) {
+                if (profile == BluetoothProfile.HEADSET) {
+                    mBluetoothHeadset = null;
+                } else if (profile == BluetoothProfile.A2DP) {
+                    mBluetoothA2dp = null;
+                }
+            }
+        }
+    };
+
+    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);
+        intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, mLinkProperties);
+        if (bssid != null)
+            intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    /* TODO: Unused for now, will be used when ip change on connected network is handled */
+    private void sendConfigChangeBroadcast() {
+        if (!ActivityManagerNative.isSystemReady()) return;
+        Intent intent = new Intent(WifiManager.CONFIG_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, mLinkProperties);
+        mContext.sendBroadcast(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.
+     * @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);
+        }
+    }
+
+    /**
+     * 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 */
+        mLinkProperties.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_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:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.boolValue = false;
+                    syncParams.mSyncReturn.intValue = -1;
+                    syncParams.mSyncReturn.stringValue = null;
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_ENABLE_RSSI_POLL:
+                    mEnableRssiPolling = (message.arg1 == 1);
+                    mSupplicantStateTracker.sendMessage(CMD_ENABLE_RSSI_POLL);
+                    break;
+                    /* Discard */
+                case CMD_LOAD_DRIVER:
+                case CMD_UNLOAD_DRIVER:
+                case CMD_START_SUPPLICANT:
+                case CMD_STOP_SUPPLICANT:
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_START_AP:
+                case CMD_STOP_AP:
+                case CMD_START_SCAN:
+                case CMD_DISCONNECT:
+                case CMD_RECONNECT:
+                case CMD_REASSOCIATE:
+                case CMD_RECONFIGURE_IP:
+                case SUP_CONNECTION_EVENT:
+                case SUP_DISCONNECTION_EVENT:
+                case DRIVER_START_EVENT:
+                case DRIVER_STOP_EVENT:
+                case NETWORK_CONNECTION_EVENT:
+                case NETWORK_DISCONNECTION_EVENT:
+                case SCAN_RESULTS_EVENT:
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                case PASSWORD_MAY_BE_INCORRECT_EVENT:
+                case CMD_BLACKLIST_NETWORK:
+                case CMD_CLEAR_BLACKLIST:
+                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_REQUEST_CM_WAKELOCK:
+                case CMD_CONNECT_NETWORK:
+                case CMD_SAVE_NETWORK:
+                case CMD_FORGET_NETWORK:
+                case CMD_START_WPS:
+                    break;
+                default:
+                    Log.e(TAG, "Error! unhandled message" + 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());
+            /* TODO: add a timeout to fail when driver load is hung.
+             * Similarly for driver unload.
+             */
+            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 */
+                    mLastBssid = null;
+                    mLastNetworkId = -1;
+                    mLastSignalLevel = -1;
+
+                    mWifiInfo.setMacAddress(WifiNative.getMacAddressCommand());
+
+                    WifiConfigStore.initialize(mContext);
+
+                    //TODO: initialize and fix multicast filtering
+                    //mWM.initializeMulticastFiltering();
+
+                    checkIsBluetoothPlaying();
+
+                    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;
+            WifiConfiguration config;
+            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);
+                    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();
+                    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;
+                    config = (WifiConfiguration) syncParams.mParameter;
+                    syncParams.mSyncReturn.intValue = WifiConfigStore.addOrUpdateNetwork(config);
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_REMOVE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.boolValue = WifiConfigStore.removeNetwork(
+                                message.arg1);
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_ENABLE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    syncParams = (SyncParams) message.obj;
+                    EnableNetParams enableNetParams = (EnableNetParams) syncParams.mParameter;
+                    syncParams.mSyncReturn.boolValue = WifiConfigStore.enableNetwork(
+                            enableNetParams.netId, enableNetParams.disableOthers);
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_DISABLE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.boolValue = WifiConfigStore.disableNetwork(
+                            message.arg1);
+                    notifyOnMsgObject(message);
+                    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_SAVE_CONFIG:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.boolValue = WifiConfigStore.saveConfig();
+                    notifyOnMsgObject(message);
+                    // 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_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;
+                case CMD_SAVE_NETWORK:
+                    config = (WifiConfiguration) message.obj;
+                    WifiConfigStore.saveNetwork(config);
+                    break;
+                case CMD_FORGET_NETWORK:
+                    WifiConfigStore.forgetNetwork(message.arg1);
+                    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:
+                case CMD_START_SCAN:
+                case CMD_DISCONNECT:
+                case CMD_REASSOCIATE:
+                case CMD_RECONNECT:
+                    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());
+
+            mIsRunning = true;
+            updateBatteryWorkSource(null);
+
+            /* Initialize channel count */
+            setNumAllowedChannels();
+            /*
+             * STOPSHIP
+             * TODO: We are having 11A issues that Broadcom is looking
+             * to resolve, this is a temporary fix to allow only 11B/G
+             * and help improve GoogleGuest connectivity
+             * We also need to add the UI for band control
+             */
+            WifiNative.setBandCommand(BAND_2G);
+
+            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");
+            mIsRunning = false;
+            updateBatteryWorkSource(null);
+            mScanResults = null;
+        }
+    }
+
+    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:
+                case CMD_START_SCAN:
+                case CMD_DISCONNECT:
+                case CMD_REASSOCIATE:
+                case CMD_RECONNECT:
+                    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());
+        }
+        @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:
+                    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:
+                    /* 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:
+                    WifiNative.disconnectCommand();
+                    break;
+                case CMD_RECONNECT:
+                    WifiNative.reconnectCommand();
+                    break;
+                case CMD_REASSOCIATE:
+                    WifiNative.reassociateCommand();
+                    break;
+                case CMD_CONNECT_NETWORK:
+                    int netId = message.arg1;
+                    WifiConfiguration config = (WifiConfiguration) message.obj;
+
+                    /* We connect to a specific network by issuing a select
+                     * to the WifiConfigStore. This enables the network,
+                     * while disabling all other networks in the supplicant.
+                     * Disabling a connected network will cause a disconnection
+                     * from the network. A reconnectCommand() will then initiate
+                     * a connection to the enabled network.
+                     */
+                    if (config != null) {
+                        WifiConfigStore.selectNetwork(config);
+                    } else {
+                        WifiConfigStore.selectNetwork(netId);
+                    }
+
+                    /* Save a flag to indicate that we need to enable all
+                     * networks after supplicant indicates a network
+                     * state change event
+                     */
+                    mEnableAllNetworks = true;
+
+                    WifiNative.reconnectCommand();
+
+                    /* Expect a disconnection from the old connection */
+                    transitionTo(mDisconnectingState);
+                    break;
+                case CMD_START_WPS:
+                    String bssid = (String) message.obj;
+                    int apPin = message.arg1;
+                    boolean success;
+                    if (apPin != 0) {
+                        /* WPS pin method configuration */
+                        success = WifiConfigStore.startWpsPin(bssid, apPin);
+                    } else {
+                        /* WPS push button configuration */
+                        success = WifiConfigStore.startWpsPbc(bssid);
+                    }
+                    /* During WPS setup, all other networks are disabled. After
+                     * a successful connect a new config is created in the supplicant.
+                     *
+                     * We need to enable all networks after a successful connection
+                     * or when supplicant goes inactive due to failure. Enabling all
+                     * networks after a disconnect is observed as done with connectNetwork
+                     * does not lead to a successful WPS setup.
+                     *
+                     * Upon success, the configuration list needs to be reloaded
+                     */
+                    if (success) {
+                        mWpsStarted = true;
+                        /* Expect a disconnection from the old connection */
+                        transitionTo(mDisconnectingState);
+                    }
+                    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;
+
+                    //TODO: make supplicant modification to push this in events
+                    mWifiInfo.setSSID(fetchSSID());
+                    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 mModifiedBluetoothCoexistenceMode;
+        int mPowerMode;
+        boolean mUseStaticIp;
+        Thread mDhcpThread;
+
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            mUseStaticIp = WifiConfigStore.isUsingStaticIp(mLastNetworkId);
+            if (!mUseStaticIp) {
+                mDhcpThread = null;
+                mModifiedBluetoothCoexistenceMode = false;
+                mPowerMode = DRIVER_POWER_MODE_AUTO;
+
+                // TODO(): Incorporate the else part in the state machine
+                // If mBluetoothHeadset == null, means it not conencted to the
+                // service yet.
+                if (mBluetoothHeadset != null && 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.
+                     */
+                    mModifiedBluetoothCoexistenceMode = true;
+
+                    // Disable the coexistence mode
+                    WifiNative.setBluetoothCoexistenceModeCommand(
+                            WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
+                }
+
+                mPowerMode =  WifiNative.getPowerModeCommand();
+                if (mPowerMode < 0) {
+                  // Handle the case where supplicant driver does not support
+                  // getPowerModeCommand.
+                    mPowerMode = DRIVER_POWER_MODE_AUTO;
+                }
+                if (mPowerMode != DRIVER_POWER_MODE_ACTIVE) {
+                    WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_ACTIVE);
+                }
+
+                Log.d(TAG, "DHCP request started");
+                mDhcpThread = new Thread(new Runnable() {
+                    public void run() {
+                        DhcpInfo dhcpInfo = new DhcpInfo();
+                        if (NetworkUtils.runDhcp(mInterfaceName, dhcpInfo)) {
+                            Log.d(TAG, "DHCP request succeeded");
+                            synchronized (mDhcpInfo) {
+                                mDhcpInfo = dhcpInfo;
+                            }
+                            sendMessage(CMD_IP_CONFIG_SUCCESS);
+                        } else {
+                            Log.d(TAG, "DHCP request failed: " +
+                                    NetworkUtils.getDhcpError());
+                            sendMessage(CMD_IP_CONFIG_FAILURE);
+                        }
+                    }
+                });
+                mDhcpThread.start();
+            } else {
+                DhcpInfo dhcpInfo = WifiConfigStore.getIpConfiguration(mLastNetworkId);
+                if (NetworkUtils.configureInterface(mInterfaceName, dhcpInfo)) {
+                    Log.v(TAG, "Static IP configuration succeeded");
+                    synchronized (mDhcpInfo) {
+                        mDhcpInfo = dhcpInfo;
+                    }
+                    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
+                  synchronized (mDhcpInfo) {
+                      mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);
+                      Log.d(TAG, "IP configuration: " + mDhcpInfo);
+                  }
+                  configureLinkProperties();
+                  setDetailedState(DetailedState.CONNECTED);
+                  sendNetworkStateChangeBroadcast(mLastBssid);
+                  //TODO: The framework is not detecting a DHCP renewal and a possible
+                  //IP change. we should detect this and send out a config change broadcast
+                  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);
+                      WifiConfigStore.disableNetwork(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:
+                  WifiNative.disconnectCommand();
+                  transitionTo(mDisconnectingState);
+                  break;
+                  /* Ignore connection to same network */
+              case CMD_CONNECT_NETWORK:
+                  int netId = message.arg1;
+                  if (mWifiInfo.getNetworkId() == netId) {
+                      break;
+                  }
+                  return NOT_HANDLED;
+                  /* 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 (mPowerMode != DRIVER_POWER_MODE_ACTIVE) {
+                  WifiNative.setPowerModeCommand(mPowerMode);
+              }
+
+              if (mModifiedBluetoothCoexistenceMode) {
+                  // 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");
+            /* A successful WPS connection */
+            if (mWpsStarted) {
+                WifiConfigStore.enableAllNetworks();
+                WifiConfigStore.loadConfiguredNetworks();
+                mWpsStarted = false;
+            }
+            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:
+                    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 connection to same network */
+                case CMD_CONNECT_NETWORK:
+                    int netId = message.arg1;
+                    if (mWifiInfo.getNetworkId() == netId) {
+                        break;
+                    }
+                    return NOT_HANDLED;
+                    /* 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;
+        }
+        @Override
+        public void exit() {
+            if (mEnableAllNetworks) {
+                mEnableAllNetworks = false;
+                WifiConfigStore.enableAllNetworks();
+            }
+        }
+    }
+
+    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 = new UninitializedState();
+        private HierarchicalState mInitializedState = new InitializedState();;
+        private HierarchicalState mInactiveState = new InactiveState();
+        private HierarchicalState mDisconnectState = new DisconnectedState();
+        private HierarchicalState mScanState = new ScanState();
+        private HierarchicalState mConnectState = new ConnectState();
+        private HierarchicalState mHandshakeState = new HandshakeState();
+        private HierarchicalState mCompletedState = new CompletedState();
+        private HierarchicalState mDormantState = new DormantState();
+
+        public SupplicantStateTracker(Context context, Handler target) {
+            super(TAG, target.getLooper());
+
+            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);
+            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;
+            }
+            @Override
+            public void exit() {
+                mNetworkInfo.setIsAvailable(true);
+            }
+        }
+
+        class InactiveState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+
+                 /* A failed WPS connection */
+                 if (mWpsStarted) {
+                     Log.e(TAG, "WPS set up failed, enabling other networks");
+                     WifiConfigStore.enableAllNetworks();
+                     mWpsStarted = false;
+                 }
+
+                 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);
+            }
+            @Override
+            public void exit() {
+                mNetworkInfo.setIsAvailable(true);
+            }
+        }
+
+
+        class DisconnectedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 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());
+                     WifiConfigStore.disableNetwork(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;
+
+                 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;
+
+                 if (mLoopDetectIndex > message.what) {
+                     mLoopDetectCount++;
+                 }
+                 if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
+                     WifiConfigStore.disableNetwork(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;
+
+                 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;
+
+                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);
+            }
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 5220713..f8c9bd6 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.
@@ -16,466 +16,110 @@
 
 package android.net.wifi;
 
-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 android.app.ActivityManagerNative;
-import android.net.NetworkInfo;
-import android.net.NetworkStateTracker;
-import android.net.DhcpInfo;
-import android.net.NetworkUtils;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkInfo.State;
-import android.os.Message;
-import android.os.Parcelable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.SystemProperties;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.WorkSource;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.Config;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothA2dp;
-import android.content.ContentResolver;
-import android.content.Intent;
+
+import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.database.ContentObserver;
-import com.android.internal.app.IBatteryStats;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.LinkCapabilities;
+import android.net.NetworkInfo;
+import android.net.LinkProperties;
+import android.net.NetworkStateTracker;
+import android.os.Handler;
+import android.os.Message;
 
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
- * Track the state of Wifi connectivity. All event handling is done here,
- * and all changes in connectivity state are initiated here.
+ * Track the state of wifi for connectivity service.
  *
  * @hide
  */
-public class WifiStateTracker extends NetworkStateTracker {
+public class WifiStateTracker implements NetworkStateTracker {
 
-    private static final boolean LOCAL_LOGD = Config.LOGD || false;
-    
+    private static final String NETWORKTYPE = "WIFI";
     private static final String TAG = "WifiStateTracker";
 
-    // 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;
+    private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
+    private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
+    private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
+    private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
 
-    // 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;
+    private LinkProperties mLinkProperties;
+    private LinkCapabilities mLinkCapabilities;
+    private NetworkInfo mNetworkInfo;
 
-    /**
-     * 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;
+    /* For sending events to connectivity service handler */
+    private Handler mCsHandler;
+    private Context mContext;
+    private BroadcastReceiver mWifiStateReceiver;
+    private WifiManager mWifiManager;
 
-    /**
-     * 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;
+    public WifiStateTracker() {
+        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
+        mLinkProperties = new LinkProperties();
+        mLinkCapabilities = new LinkCapabilities();
 
-    /**
-     * 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;
+        mNetworkInfo.setIsAvailable(false);
+        setTeardownRequested(false);
+    }
 
-    /**
-     * 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;
+    public void setTeardownRequested(boolean isRequested) {
+        mTeardownRequested.set(isRequested);
+    }
 
-    /**
-     * 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;
-
-    private WifiMonitor mWifiMonitor;
-    private WifiInfo mWifiInfo;
-    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 int mLastSignalLevel = -1;
-    private String mLastBssid;
-    private String mLastSsid;
-    private int mLastNetworkId = -1;
-    private boolean mUseStaticIp = false;
-    private int mReconnectCount;
-
-    /* Tracks if any network in the configuration is disabled */
-    private AtomicBoolean mIsAnyNetworkDisabled = new AtomicBoolean(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;
-
-    // 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.
-     */
-    private static final int ICON_NETWORKS_AVAILABLE =
-            com.android.internal.R.drawable.stat_notify_wifi_in_range;
-    /**
-     * When a notification is shown, we wait this amount before possibly showing it again.
-     */
-    private final long NOTIFICATION_REPEAT_DELAY_MS;
-    /**
-     * Whether the user has set the setting to show the 'available networks' notification.
-     */
-    private boolean mNotificationEnabled;
-    /**
-     * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
-     */
-    private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
-    /**
-     * The {@link System#currentTimeMillis()} must be at least this value for us
-     * to show the notification again.
-     */
-    private long mNotificationRepeatTime;
-    /**
-     * The Notification object given to the NotificationManager.
-     */
-    private Notification mNotification;
-    /**
-     * Whether the notification is being shown, as set by us. That is, if the
-     * user cancels the notification, we will not receive the callback so this
-     * will still be true. We only guarantee if this is false, then the
-     * notification is not showing.
-     */
-    private boolean mNotificationShown;
-    /**
-     * The number of continuous scans that must occur before consider the
-     * supplicant in a scanning state. This allows supplicant to associate with
-     * remembered networks that are in the scan results.
-     */
-    private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
-    /**
-     * The number of scans since the last network state change. When this
-     * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
-     * supplicant to actually be scanning. When the network state changes to
-     * something other than scanning, we reset this to 0.
-     */
-    private int mNumScansSinceNetworkStateChange;
-    /**
-     * Observes the static IP address settings.
-     */
-    private SettingsObserver mSettingsObserver;
-    
-    private boolean mIsScanModeActive;
-    private boolean mEnableRssiPolling;
-    private boolean mIsHighPerfEnabled;
-    private int mPowerModeRefCount = 0;
-    private int mOptimizationsDisabledRefCount = 0;
-
-    /**
-     * One of  {@link WifiManager#WIFI_STATE_DISABLED},
-     *         {@link WifiManager#WIFI_STATE_DISABLING},
-     *         {@link WifiManager#WIFI_STATE_ENABLED},
-     *         {@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);
-
-    // 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;
-
-    private static final String mRunStateNames[] = {
-            "Starting",
-            "Running",
-            "Stopping",
-            "Stopped"
-    };
-    private int mRunState;
-
-    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;
-
-    /**
-     * Keep track of whether we last told the battery stats we had started.
-     */
-    private boolean mReportedRunning = false;
-
-    /**
-     * Most recently set source of starting WIFI.
-     */
-    private final WorkSource mRunningWifiUids = new WorkSource();
-
-    /**
-     * The last reported UIDs that were responsible for starting WIFI.
-     */
-    private final WorkSource mLastRunningWifiUids = new WorkSource();
-
-    /**
-     * 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;
+    public boolean isTeardownRequested() {
+        return mTeardownRequested.get();
     }
 
     /**
-     * A structure for supplying information about a connection in
-     * the CONNECTED event message that comes from the WifiMonitor
-     * thread.
+     * Begin monitoring wifi connectivity
      */
-    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 void startMonitoring(Context context, Handler target) {
+        mCsHandler = target;
+        mContext = context;
 
-    public WifiStateTracker(Context context, Handler target) {
-        super(context, target, ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
-        
-        mWifiInfo = new WifiInfo();
-        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;
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.CONFIG_CHANGED_ACTION);
 
-        // Setting is in seconds
-        NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(), 
-                Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
-        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
-        mNotificationEnabledSettingObserver.register();
-
-        mSettingsObserver = new SettingsObserver(new Handler());
-
-        mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
-        sDnsPropNames = new String[] {
-            "dhcp." + mInterfaceName + ".dns1",
-            "dhcp." + mInterfaceName + ".dns2"
-        };
-        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
-
+        mWifiStateReceiver = new WifiStateReceiver();
+        mContext.registerReceiver(mWifiStateReceiver, filter);
     }
 
     /**
-     * Helper method: sets the supplicant state and keeps the network
-     * info updated.
-     * @param state the new state
+     * Disable connectivity to a network
+     * TODO: do away with return value after making MobileDataStateTracker async
      */
-    private void setSupplicantState(SupplicantState state) {
-        mWifiInfo.setSupplicantState(state);
-        updateNetworkInfo();
-        checkPollTimer();
-    }
-
-    public SupplicantState getSupplicantState() {
-        return mWifiInfo.getSupplicantState();
+    public boolean teardown() {
+        mTeardownRequested.set(true);
+        mWifiManager.stopWifi();
+        return true;
     }
 
     /**
-     * Helper method: sets the supplicant state and keeps the network
-     * info updated (string version).
-     * @param stateName the string name of the new state
+     * Re-enable connectivity to a network after a {@link #teardown()}.
+     * TODO: do away with return value after making MobileDataStateTracker async
      */
-    private void setSupplicantState(String stateName) {
-        mWifiInfo.setSupplicantState(stateName);
-        updateNetworkInfo();
-        checkPollTimer();
+    public boolean reconnect() {
+        mTeardownRequested.set(false);
+        mWifiManager.startWifi();
+        return true;
     }
 
     /**
-     * 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.
+     * 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
      */
-    private void setTornDownByConnMgr(boolean flag) {
-        mTornDownByConnMgr = flag;
-        updateNetworkInfo();
-    }
-
-    /**
-     * Return the IP addresses of the DNS servers available for the WLAN
-     * network interface.
-     * @return a list of DNS addresses, with no holes.
-     */
-    public String[] getNameServers() {
-        return getNameServerList(sDnsPropNames);
-    }
-
-    /**
-     * 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();
+    public boolean setRadio(boolean turnOn) {
+        mWifiManager.setWifiEnabled(turnOn);
+        return true;
     }
 
     /**
@@ -487,2201 +131,136 @@
      * 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.
-     */
-    public int getNetworkSubtype() {
-        return 0;
-    }
-
-    /**
-     * Helper method: updates the network info object to keep it in sync with
-     * the Wi-Fi state tracker.
-     */
-    private void updateNetworkInfo() {
-        mNetworkInfo.setIsAvailable(isAvailable());
-    }
-
-    /**
-     * 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}.
-     */
-    public boolean isConnectionCompleted() {
-        return mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED;
-    }
-
-    /**
-     * 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.
-     */
-    public boolean hasIpAddress() {
-        return mHaveIpAddress;
-    }
-
-    /**
-     * Send the tracker a notification that a user-entered password key
-     * may be incorrect (i.e., caused authentication to fail).
-     */
-    void notifyPasswordKeyMayBeIncorrect() {
-        sendEmptyMessage(EVENT_PASSWORD_KEY_MAY_BE_INCORRECT);
-    }
-
-    /**
-     * Send the tracker a notification that a connection to the supplicant
-     * daemon has been established.
-     */
-    void notifySupplicantConnection() {
-        sendEmptyMessage(EVENT_SUPPLICANT_CONNECTION);
-    }
-
-    /**
-     * 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 notifyStateChange(int networkId, String BSSID, SupplicantState newState) {
-        Message msg = Message.obtain(
-            this, EVENT_SUPPLICANT_STATE_CHANGED,
-            new SupplicantStateChangeResult(networkId, BSSID, newState));
-        msg.sendToTarget();
-    }
-
-    /**
-     * 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 notifyStateChange(DetailedState newState, String BSSID, int networkId) {
-        Message msg = Message.obtain(
-            this, EVENT_NETWORK_STATE_CHANGED,
-            new NetworkStateChangeResult(newState, BSSID, networkId));
-        msg.sendToTarget();
-    }
-
-    /**
-     * Send the tracker a notification that a scan has completed, and results
-     * are available.
-     */
-    void notifyScanResultsAvailable() {
-        // reset the supplicant's handling of scan results to "normal" mode
-        setScanResultHandling(SUPPL_SCAN_HANDLING_NORMAL);
-        sendEmptyMessage(EVENT_SCAN_RESULTS_AVAILABLE);
-    }
-
-    /**
-     * Send the tracker a notification that we can no longer communicate with
-     * the supplicant daemon.
-     */
-    void notifySupplicantLost() {
-        sendEmptyMessage(EVENT_SUPPLICANT_DISCONNECT);
-    }
-
-    /**
-     * Send the tracker a notification that the Wi-Fi driver has been stopped.
-     */
-    void notifyDriverStopped() {
-        mRunState = RUN_STATE_STOPPED;
-
-        // Send a driver stopped message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STOPPED, 0).sendToTarget();
-    }
-
-    /**
-     * Send the tracker a notification that the Wi-Fi driver has been restarted after
-     * having been stopped.
-     */
-    void notifyDriverStarted() {
-        // Send a driver started message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STARTED, 0).sendToTarget();
-    }
-
-    /**
-     * Send the tracker a notification that the Wi-Fi driver has hung and needs restarting.
-     */
-    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;
-    }
-
-    public void updateBatteryWorkSourceLocked(WorkSource newSource) {
-        try {
-            if (newSource != null) {
-                mRunningWifiUids.set(newSource);
-            }
-            if (mRunState == RUN_STATE_RUNNING) {
-                if (mReportedRunning) {
-                    // If the work source has changed since last time, need
-                    // to remove old work from battery stats.
-                    if (mLastRunningWifiUids.diff(mRunningWifiUids)) {
-                        mBatteryStats.noteWifiRunningChanged(mLastRunningWifiUids,
-                                mRunningWifiUids);
-                        mLastRunningWifiUids.set(mRunningWifiUids);
-                    }
-                } else {
-                    // Now being started, report it.
-                    mBatteryStats.noteWifiRunning(mRunningWifiUids);
-                    mLastRunningWifiUids.set(mRunningWifiUids);
-                    mReportedRunning = true;
-                }
-            } else if (mRunState == RUN_STATE_STOPPED) {
-                if (mReportedRunning) {
-                    // Last reported we were running, time to stop.
-                    mBatteryStats.noteWifiStopped(mLastRunningWifiUids);
-                    mLastRunningWifiUids.clear();
-                    mReportedRunning = false;
-                }
-            } else {
-                // State in transition -- nothing to update yet.
-            }
-        } 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();
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Set suspend mode optimizations. These include:
-     * - packet filtering
-     * - turn off roaming
-     * - DTIM settings
-     *
-     * Uses reference counting to keep the suspend optimizations disabled
-     * as long as one entity wants optimizations disabled.
-     *
-     * For example, WifiLock can keep suspend optimizations disabled
-     * or the user setting (wifi never sleeps) can keep suspend optimizations
-     * disabled. As long as one entity wants it disabled, it should stay
-     * that way
-     *
-     * @param enabled true if optimizations need enabled, false otherwise
-     */
-    public synchronized void setSuspendModeOptimizations(boolean enabled) {
-
-        /* It is good to plumb suspend optimization enable
-         * or disable even if ref count indicates already done
-         * since we could have a case of previous failure.
-         */
-        if (!enabled) {
-            mOptimizationsDisabledRefCount++;
-        } else {
-            mOptimizationsDisabledRefCount--;
-            if (mOptimizationsDisabledRefCount > 0) {
-                return;
-            } else {
-                /* Keep refcount from becoming negative */
-                mOptimizationsDisabledRefCount = 0;
-            }
-        }
-
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return;
-        }
-
-        WifiNative.setSuspendOptimizationsCommand(enabled);
-    }
-
-
-    /**
-     * Set high performance mode of operation. This would mean
-     * use active power mode and disable suspend optimizations
-     * @param enabled true if enabled, false otherwise
-     */
-    public synchronized void setHighPerfMode(boolean enabled) {
-        if (mIsHighPerfEnabled != enabled) {
-            if (enabled) {
-                setPowerMode(DRIVER_POWER_MODE_ACTIVE);
-                setSuspendModeOptimizations(false);
-            } else {
-                setPowerMode(DRIVER_POWER_MODE_AUTO);
-                setSuspendModeOptimizations(true);
-            }
-            mIsHighPerfEnabled = enabled;
-            Log.d(TAG,"high performance mode: " + enabled);
-        }
-    }
-
-
-    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;
-                synchronized (this) {
-                    updateBatteryWorkSourceLocked(null);
-                }
-                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;
-                mIsHighPerfEnabled = false;
-                mOptimizationsDisabledRefCount = 0;
-                mPowerModeRefCount = 0;
-                mTornDownByConnMgr = false;
-                mLastBssid = null;
-                mLastSsid = null;
-                mIsAnyNetworkDisabled.set(false);
-                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;
-                synchronized (this) {
-                    updateBatteryWorkSourceLocked(null);
-                }
-                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 {
-                    msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
-                    msg.sendToTarget();
-                }
-                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;
-                }
-                synchronized (this) {
-                    updateBatteryWorkSourceLocked(null);
-                }
-                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);
-            }
-        } 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);
-        }
-    }
-
-    /**
-     * 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
-     */
-    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);
-        }
-    }
-
-    /**
-     * 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.
-     */
-    private void scheduleDisconnect() {
-        mDisconnectPending = true;
-        if (!hasMessages(EVENT_DEFERRED_DISCONNECT)) {
-            sendEmptyMessageDelayed(EVENT_DEFERRED_DISCONNECT, DISCONNECT_DELAY_MSECS);
-        }
-    }
-
-    private void cancelDisconnect() {
-        mDisconnectPending = false;
-        removeMessages(EVENT_DEFERRED_DISCONNECT);
-    }
-
-    public DhcpInfo getDhcpInfo() {
-        return mDhcpInfo;
-    }
-
-    public synchronized 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);
-    }
-
-    public boolean isAnyNetworkDisabled() {
-        return mIsAnyNetworkDisabled.get();
-    }
-
-   /**
-     * 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();
-    }
-
-    /**
-     * 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();
-    }
-
-    /**
-     * Add a network
-     *
-     * @return network id of the new network
-     */
-    public synchronized int addNetwork() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return -1;
-        }
-        return WifiNative.addNetworkCommand();
-    }
-
-    /**
-     * 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);
-    }
-
-    /**
-     * Enable a network
-     *
-     * @param netId network id of the network
-     * @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;
-        }
-        if (disableOthers) mIsAnyNetworkDisabled.set(true);
-        return WifiNative.enableNetworkCommand(netId, disableOthers);
-    }
-
-    /**
-     * Enable all networks
-     *
-     * @param networks list of configured networks
-     */
-    public synchronized void enableAllNetworks(List<WifiConfiguration> networks) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return;
-        }
-        mIsAnyNetworkDisabled.set(false);
-        for (WifiConfiguration config : networks) {
-            if (config.status == WifiConfiguration.Status.DISABLED) {
-                WifiNative.enableNetworkCommand(config.networkId, false);
-            }
-        }
-    }
-
-    /**
-     * Disable a network
-     *
-     * @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;
-        }
-        mIsAnyNetworkDisabled.set(true);
-        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();
-    }
-
-    /**
-     * Blacklist a BSSID. This will avoid the AP if there are
-     * 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);
-    }
-
-    /**
-     * 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);
-    }
-
-    /**
-     * Get detailed status of the connection
-     *
-     * @return Example status result
-     *  bssid=aa:bb:cc:dd:ee:ff
-     *  ssid=TestNet
-     *  id=3
-     *  pairwise_cipher=NONE
-     *  group_cipher=NONE
-     *  key_mgmt=NONE
-     *  wpa_state=COMPLETED
-     *  ip_address=X.X.X.X
-     */
-    public synchronized String status() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return null;
-        }
-        return WifiNative.statusCommand();
-    }
-
-    /**
-     * 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();
-    }
-
-    /**
-     * Get approx RSSI to currently connected network
-     *
-     * @return RSSI value, -1 on failure
-     */
-    public synchronized int getRssiApprox() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getRssiApproxCommand();
-    }
-
-    /**
-     * Get link speed to currently connected network
-     *
-     * @return link speed, -1 on failure
-     */
-    public synchronized int getLinkSpeed() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getLinkSpeedCommand();
-    }
-
-    /**
-     * Get MAC address of radio
-     *
-     * @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();
-    }
-
-    /**
-     * 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();
-    }
-
-    /**
-     * 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();
-    }
-
-    /**
-     * Set power mode
-     * @param mode
-     *     DRIVER_POWER_MODE_AUTO
-     *     DRIVER_POWER_MODE_ACTIVE
-     *
-     * Uses reference counting to keep power mode active
-     * as long as one entity wants power mode to be active.
-     *
-     * For example, WifiLock high perf mode can keep power mode active
-     * or a DHCP session can keep it active. As long as one entity wants
-     * it enabled, it should stay that way
-     *
-     */
-    private synchronized void setPowerMode(int mode) {
-
-        /* It is good to plumb power mode change
-         * even if ref count indicates already done
-         * since we could have a case of previous failure.
-         */
-        switch(mode) {
-            case DRIVER_POWER_MODE_ACTIVE:
-                mPowerModeRefCount++;
-                break;
-            case DRIVER_POWER_MODE_AUTO:
-                mPowerModeRefCount--;
-                if (mPowerModeRefCount > 0) {
-                    return;
-                } else {
-                    /* Keep refcount from becoming negative */
-                    mPowerModeRefCount = 0;
-                }
-                break;
-        }
-
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return;
-        }
-
-        WifiNative.setPowerModeCommand(mode);
-    }
-
-    /**
-     * 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;
-        }
-        try {
-            return setNumAllowedChannels(
-                    Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS));
-        } catch (Settings.SettingNotFoundException e) {
-            if (mNumAllowedChannels != 0) {
-                WifiNative.setNumAllowedChannelsCommand(mNumAllowedChannels);
-            }
-            // otherwise, use the driver default
-        }
-        return true;
-    }
-
-    /**
-     * Set the number of radio frequency channels that are allowed to be used
-     * 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);
-    }
-
-    /**
-     * Get number of allowed channels
-     *
-     * @return channel count, -1 on failure
-     */
-    public synchronized int getNumAllowedChannels() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getNumAllowedChannelsCommand();
-    }
-
-    /**
-     * Set bluetooth coex mode:
-     *
-     * @param mode
-     *  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);
-    }
-
-    /**
-     * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
-     * some of the low-level scan parameters used by the driver are changed to
-     * reduce interference with A2DP streaming.
-     *
-     * @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);
-    }
-
-    /**
-     * 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
-     */
-    public synchronized boolean reloadConfig() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.reloadConfigCommand();
-    }
-
-    public boolean setRadio(boolean turnOn) {
-        return mWM.setWifiEnabled(turnOn);
-    }
-
-    /**
-     * {@inheritDoc}
-     * There are currently no Wi-Fi-specific features supported.
-     * @param feature the name of the feature
-     * @return {@code -1} indicating failure, always
+     * 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 startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
         return -1;
     }
 
     /**
-     * {@inheritDoc}
-     * There are currently no Wi-Fi-specific features supported.
-     * @param feature the name of the feature
-     * @return {@code -1} indicating failure, always
+     * 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
      */
     public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
         return -1;
     }
 
-    @Override
-    public void interpretScanResultsAvailable() {
-
-        // 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();
-        if ((state == NetworkInfo.State.DISCONNECTED)
-                || (state == NetworkInfo.State.UNKNOWN)) {
-
-            // Look for an open network
-            List<ScanResult> scanResults = getScanResultsList();
-            if (scanResults != null) {
-                int numOpenNetworks = 0;
-                for (int i = scanResults.size() - 1; i >= 0; i--) {
-                    ScanResult scanResult = scanResults.get(i);
-
-                    if (TextUtils.isEmpty(scanResult.capabilities)) {
-                        numOpenNetworks++;
-                    }
-                }
-            
-                if (numOpenNetworks > 0) {
-                    if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
-                        /*
-                         * We've scanned continuously at least
-                         * NUM_SCANS_BEFORE_NOTIFICATION times. The user
-                         * probably does not have a remembered network in range,
-                         * since otherwise supplicant would have tried to
-                         * associate and thus resetting this counter.
-                         */
-                        setNotificationVisible(true, numOpenNetworks, false, 0);
-                    }
-                    return;
-                }
-            }
-        }
-        
-        // No open networks in range, remove the notification
-        setNotificationVisible(false, 0, false, 0);
+    /**
+     * Check if private DNS route is set for the network
+     */
+    public boolean isPrivateDnsRouteSet() {
+        return mPrivateDnsRouteSet.get();
     }
 
     /**
-     * Display or don't display a notification that there are open Wi-Fi networks.
-     * @param visible {@code true} if notification should be visible, {@code false} otherwise
-     * @param numNetworks the number networks seen
-     * @param force {@code true} to force notification to be shown/not-shown,
-     * even if it is already shown/not-shown.
-     * @param delay time in milliseconds after which the notification should be made
-     * visible or invisible.
+     * Set a flag indicating private DNS route is set
      */
-    public 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;
-        }
-
-        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
-                // the queue later
-                mNotification = new Notification();
-                mNotification.when = 0;
-                mNotification.icon = ICON_NETWORKS_AVAILABLE;
-                mNotification.flags = Notification.FLAG_AUTO_CANCEL;
-                mNotification.contentIntent = PendingIntent.getActivity(mContext, 0,
-                        new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0);
-            }
-
-            CharSequence title = mContext.getResources().getQuantityText(
-                    com.android.internal.R.plurals.wifi_available, numNetworks);
-            CharSequence details = mContext.getResources().getQuantityText(
-                    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,
-                    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);
-        }
-
-        mTarget.sendMessageDelayed(message, delay);
-        
-        mNotificationShown = visible;
+    public void privateDnsRouteSet(boolean enabled) {
+        mPrivateDnsRouteSet.set(enabled);
     }
 
     /**
-     * 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.
+     * Fetch NetworkInfo for the network
      */
-    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);
-        }
-        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();
+    public NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
     }
 
-    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();
-        try {
-            if (Settings.System.getInt(cr, Settings.System.WIFI_USE_STATIC_IP) == 0) {
-                return;
-            }
-        } catch (Settings.SettingNotFoundException e) {
-            return;
-        }
-
-        try {
-            String addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_IP);
-            if (addr != null) {
-                mDhcpInfo.ipAddress = stringToIpAddr(addr);
-            } else {
-                return;
-            }
-            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_GATEWAY);
-            if (addr != null) {
-                mDhcpInfo.gateway = stringToIpAddr(addr);
-            } else {
-                return;
-            }
-            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_NETMASK);
-            if (addr != null) {
-                mDhcpInfo.netmask = stringToIpAddr(addr);
-            } else {
-                return;
-            }
-            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS1);
-            if (addr != null) {
-                mDhcpInfo.dns1 = stringToIpAddr(addr);
-            } else {
-                return;
-            }
-            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS2);
-            if (addr != null) {
-                mDhcpInfo.dns2 = stringToIpAddr(addr);
-            } else {
-                mDhcpInfo.dns2 = 0;
-            }
-        } catch (UnknownHostException e) {
-            return;
-        }
-        mUseStaticIp = true;
+    /**
+     * Fetch LinkProperties for the network
+     */
+    public LinkProperties getLinkProperties() {
+        return new LinkProperties(mLinkProperties);
     }
 
-    private static int stringToIpAddr(String addrString) throws UnknownHostException {
-        try {
-            String[] parts = addrString.split("\\.");
-            if (parts.length != 4) {
-                throw new UnknownHostException(addrString);
-            }
-
-            int a = Integer.parseInt(parts[0])      ;
-            int b = Integer.parseInt(parts[1]) <<  8;
-            int c = Integer.parseInt(parts[2]) << 16;
-            int d = Integer.parseInt(parts[3]) << 24;
-
-            return a | b | c | d;
-        } catch (NumberFormatException ex) {
-            throw new UnknownHostException(addrString);
-        }
+    /**
+     * A capability is an Integer/String pair, the capabilities
+     * are defined in the class LinkSocket#Key.
+     *
+     * @return a copy of this connections capabilities, may be empty but never null.
+     */
+    public LinkCapabilities getLinkCapabilities() {
+        return new LinkCapabilities(mLinkCapabilities);
     }
 
-    private int getMaxDhcpRetries() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                                      Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT,
-                                      DEFAULT_MAX_DHCP_RETRIES);
+    /**
+     * Fetch default gateway address for the network
+     */
+    public int getDefaultGatewayAddr() {
+        return mDefaultGatewayAddr.get();
     }
 
-    private class SettingsObserver extends ContentObserver {
-        public SettingsObserver(Handler handler) {
-            super(handler);
-            ContentResolver cr = mContext.getContentResolver();
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_USE_STATIC_IP), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_IP), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_GATEWAY), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_NETMASK), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_DNS1), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_DNS2), false, this);
-        }
-
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-
-            boolean wasStaticIp = mUseStaticIp;
-            int oIp, oGw, oMsk, oDns1, oDns2;
-            oIp = oGw = oMsk = oDns1 = oDns2 = 0;
-            if (wasStaticIp) {
-                oIp = mDhcpInfo.ipAddress;
-                oGw = mDhcpInfo.gateway;
-                oMsk = mDhcpInfo.netmask;
-                oDns1 = mDhcpInfo.dns1;
-                oDns2 = mDhcpInfo.dns2;
-            }
-            checkUseStaticIp();
-
-            if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) {
-                return;
-            }
-
-            boolean changed =
-                (wasStaticIp != mUseStaticIp) ||
-                    (wasStaticIp && (
-                        oIp   != mDhcpInfo.ipAddress ||
-                        oGw   != mDhcpInfo.gateway ||
-                        oMsk  != mDhcpInfo.netmask ||
-                        oDns1 != mDhcpInfo.dns1 ||
-                        oDns2 != mDhcpInfo.dns2));
-
-            if (changed) {
-                resetConnections(true);
-                configureInterface();
-                if (mUseStaticIp) {
-                    Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
-                    msg.sendToTarget();
-                }
-            }
-        }
+    /**
+     * Check if default route is set
+     */
+    public boolean isDefaultRouteSet() {
+        return mDefaultRouteSet.get();
     }
 
-    private class NotificationEnabledSettingObserver extends ContentObserver {
+    /**
+     * Set a flag indicating default route is set for the network
+     */
+    public void defaultRouteSet(boolean enabled) {
+        mDefaultRouteSet.set(enabled);
+    }
 
-        public NotificationEnabledSettingObserver(Handler handler) {
-            super(handler);
-        }
+    /**
+     * Return the system properties name associated with the tcp buffer sizes
+     * for this network.
+     */
+    public String getTcpBufferSizesPropName() {
+        return "net.tcp.buffersize.wifi";
+    }
 
-        public void register() {
-            ContentResolver cr = mContext.getContentResolver();
-            cr.registerContentObserver(Settings.Secure.getUriFor(
-                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
-            mNotificationEnabled = getValue();
-        }
-
+    private class WifiStateReceiver extends BroadcastReceiver {
         @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-
-            mNotificationEnabled = getValue();
-            if (!mNotificationEnabled) {
-                // Remove any notification that may be showing
-                setNotificationVisible(false, 0, true, 0);
+        public void onReceive(Context context, Intent intent) {
+           if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+                        WifiManager.EXTRA_NETWORK_INFO);
+                mLinkProperties = intent.getParcelableExtra(
+                        WifiManager.EXTRA_LINK_PROPERTIES);
+                if (mLinkProperties == null) {
+                    mLinkProperties = new LinkProperties();
+                }
+                mLinkCapabilities = intent.getParcelableExtra(
+                        WifiManager.EXTRA_LINK_CAPABILITIES);
+                if (mLinkCapabilities == null) {
+                    mLinkCapabilities = new LinkCapabilities();
+                }
+                Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
+                msg.sendToTarget();
+            } else if (intent.getAction().equals(WifiManager.CONFIG_CHANGED_ACTION)) {
+                mLinkProperties = (LinkProperties) intent.getParcelableExtra(
+                        WifiManager.EXTRA_LINK_PROPERTIES);
+                Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
+                msg.sendToTarget();
             }
-
-            resetNotificationTimer();
-        }
-
-        private boolean getValue() {
-            return Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
         }
     }
+
 }