Merge "Fix a SurfaceFlinger bug."
diff --git a/Android.mk b/Android.mk
index 63505ac..9492167 100644
--- a/Android.mk
+++ b/Android.mk
@@ -356,6 +356,7 @@
 # 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 := \
+    -knowntags ./frameworks/base/docs/knowntags.txt \
     -since ./frameworks/base/api/1.xml 1 \
     -since ./frameworks/base/api/2.xml 2 \
     -since ./frameworks/base/api/3.xml 3 \
@@ -365,11 +366,13 @@
     -since ./frameworks/base/api/7.xml 7 \
     -since ./frameworks/base/api/8.xml 8 \
     -since ./frameworks/base/api/current.xml HC \
-		-error 101 -error 102 -warning 103 -error 104 -error 106 -error 108 -warning 113 \
-		-error 114 \
+		-werror -hide 113 \
 		-overview $(LOCAL_PATH)/core/java/overview.html
 
-framework_docs_LOCAL_ADDITIONAL_JAVA_DIR:=$(call intermediates-dir-for,JAVA_LIBRARIES,framework)
+framework_docs_LOCAL_ADDITIONAL_JAVA_DIR:= $(call intermediates-dir-for,JAVA_LIBRARIES,framework)
+
+framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES := \
+    frameworks/base/docs/knowntags.txt
 
 sample_dir := development/samples
 
@@ -431,25 +434,12 @@
 framework_docs_SDK_VERSION:=2.2
   # release version (ie "Release x")  (full releases only)
 framework_docs_SDK_REL_ID:=1
-  # name of current SDK directory (full releases only)
-framework_docs_SDK_CURRENT_DIR:=$(framework_docs_SDK_VERSION)_r$(framework_docs_SDK_REL_ID)
   # flag to build offline docs for a preview release
 framework_docs_SDK_PREVIEW:=0
 
-## Latest ADT version identifiers, for reference from published docs
-framework_docs_ADT_VERSION:=0.9.8
-framework_docs_ADT_DOWNLOAD:=ADT-0.9.8.zip
-framework_docs_ADT_BYTES:=8703591
-framework_docs_ADT_CHECKSUM:=22070f8e52924605a3b3abf87c1ba39f
-
 framework_docs_LOCAL_DROIDDOC_OPTIONS += \
 		-hdf sdk.version $(framework_docs_SDK_VERSION) \
-		-hdf sdk.rel.id $(framework_docs_SDK_REL_ID) \
-		-hdf sdk.current $(framework_docs_SDK_CURRENT_DIR) \
-		-hdf adt.zip.version $(framework_docs_ADT_VERSION) \
-		-hdf adt.zip.download $(framework_docs_ADT_DOWNLOAD) \
-		-hdf adt.zip.bytes $(framework_docs_ADT_BYTES) \
-		-hdf adt.zip.checksum $(framework_docs_ADT_CHECKSUM) 
+		-hdf sdk.rel.id $(framework_docs_SDK_REL_ID)
 
 # ====  the api stubs and current.xml ===========================
 include $(CLEAR_VARS)
@@ -461,6 +451,7 @@
 LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
 LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR)
 LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_ADDITIONAL_JAVA_DIR)
+LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
 
 LOCAL_MODULE := api-stubs
 
@@ -491,6 +482,7 @@
 LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
 LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR)
 LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_ADDITIONAL_JAVA_DIR)
+LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
 
 LOCAL_MODULE := doc-comment-check
 
@@ -519,6 +511,7 @@
 LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
 LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR)
 LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_ADDITIONAL_JAVA_DIR)
+LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
 
 LOCAL_MODULE := offline-sdk
 
@@ -533,7 +526,7 @@
 		-hdf android.whichdoc offline
 
 ifeq ($(framework_docs_SDK_PREVIEW),true)
-  LOCAL_DROIDDOC_OPTIONS += -hdf sdk.current preview 
+  LOCAL_DROIDDOC_OPTIONS += -hdf sdk.preview true
 endif
 
 LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
@@ -560,6 +553,7 @@
 LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
 LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR)
 LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_ADDITIONAL_JAVA_DIR)
+LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
 
 LOCAL_MODULE := online-sdk
 
@@ -587,6 +581,7 @@
 LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
 LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR)
 LOCAL_ADDITIONAL_JAVA_DIR:=$(call intermediates-dir-for,JAVA_LIBRARIES,framework)
+LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
 
 LOCAL_MODULE := hidden
 LOCAL_DROIDDOC_OPTIONS:=\
@@ -620,6 +615,7 @@
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core
 LOCAL_JAVA_RESOURCE_DIRS := $(ext_res_dirs)
+LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := ext
 
 LOCAL_NO_EMMA_INSTRUMENT := true
diff --git a/api/9.xml b/api/9.xml
index da0b71b..3e7653c 100644
--- a/api/9.xml
+++ b/api/9.xml
@@ -167549,16 +167549,6 @@
  visibility="public"
 >
 </method>
-<field name="mDeviceId"
- type="int"
- transient="false"
- volatile="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-</field>
 </class>
 <class name="KeyCharacterMap"
  extends="java.lang.Object"
diff --git a/api/current.xml b/api/current.xml
index c882d54..3049bcb 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2176,6 +2176,17 @@
  visibility="public"
 >
 </field>
+<field name="actionLayout"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843580"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="actionModeBackground"
  type="int"
  transient="false"
@@ -2220,6 +2231,28 @@
  visibility="public"
 >
 </field>
+<field name="actionViewClass"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843581"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="activatedBackgroundIndicator"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843582"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="activityCloseEnterAnimation"
  type="int"
  transient="false"
@@ -2759,6 +2792,28 @@
  visibility="public"
 >
 </field>
+<field name="breadCrumbShortTitle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843589"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="breadCrumbTitle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843588"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="bufferType"
  type="int"
  transient="false"
@@ -6807,6 +6862,17 @@
  visibility="public"
 >
 </field>
+<field name="listPopupWindowStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843584"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="listPreferredItemHeight"
  type="int"
  transient="false"
@@ -7676,6 +7742,17 @@
  visibility="public"
 >
 </field>
+<field name="popupMenuStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843585"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="popupWindowStyle"
  type="int"
  transient="false"
@@ -9095,6 +9172,17 @@
  visibility="public"
 >
 </field>
+<field name="state_activated"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843583"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="state_active"
  type="int"
  transient="false"
@@ -9700,6 +9788,17 @@
  visibility="public"
 >
 </field>
+<field name="textAppearanceLargePopupMenu"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843586"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="textAppearanceMedium"
  type="int"
  transient="false"
@@ -9766,6 +9865,17 @@
  visibility="public"
 >
 </field>
+<field name="textAppearanceSmallPopupMenu"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843587"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="textCheckMark"
  type="int"
  transient="false"
@@ -16249,7 +16359,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="17039408"
+ value="17039393"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -16680,6 +16790,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"
@@ -17109,6 +17241,28 @@
  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"
@@ -17252,6 +17406,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"
@@ -17307,6 +17472,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"
@@ -17351,6 +17527,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"
@@ -29189,6 +29376,113 @@
 </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"
@@ -29196,6 +29490,30 @@
  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="findFragmentById"
  return="android.app.Fragment"
  abstract="true"
@@ -29222,6 +29540,19 @@
 <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"
@@ -29306,6 +29637,19 @@
 <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"
@@ -29318,6 +29662,66 @@
 >
 </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"
@@ -29465,6 +29869,58 @@
 <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"
@@ -33086,6 +33542,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"
@@ -33185,6 +33652,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"
@@ -39221,7 +39699,7 @@
  value="2743"
  static="true"
  final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </field>
@@ -53785,6 +54263,16 @@
  visibility="public"
 >
 </field>
+<field name="nativeLibraryDir"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="permission"
  type="java.lang.String"
  transient="false"
@@ -60654,6 +61142,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"
@@ -64039,10 +64538,32 @@
  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"
@@ -64061,6 +64582,17 @@
  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"
@@ -65886,7 +66418,7 @@
  return="void"
  abstract="false"
  native="false"
- synchronized="true"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -65925,7 +66457,7 @@
  return="void"
  abstract="false"
  native="false"
- synchronized="true"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -85360,7 +85892,7 @@
  visibility="public"
 >
 </field>
-<field name="mFacing"
+<field name="facing"
  type="int"
  transient="false"
  volatile="false"
@@ -85370,7 +85902,7 @@
  visibility="public"
 >
 </field>
-<field name="mOrientation"
+<field name="orientation"
  type="int"
  transient="false"
  volatile="false"
@@ -93945,6 +94477,8 @@
 >
 <parameter name="uid" type="int">
 </parameter>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
 </method>
 <method name="onDisable"
  return="void"
@@ -94074,6 +94608,8 @@
 >
 <parameter name="uid" type="int">
 </parameter>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
 </method>
 <method name="onRequiresCell"
  return="boolean"
@@ -94135,6 +94671,8 @@
 >
 <parameter name="minTime" type="long">
 </parameter>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
 </method>
 <method name="onSupportsAltitude"
  return="boolean"
@@ -101764,6 +102302,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"
@@ -101777,6 +102341,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"
@@ -101904,25 +102481,6 @@
 <parameter name="profile" type="android.media.CamcorderProfile">
 </parameter>
 </method>
-<method name="setTimeLapseParameters"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="enableTimeLapse" type="boolean">
-</parameter>
-<parameter name="useStillCameraForTimeLapse" type="boolean">
-</parameter>
-<parameter name="timeBetweenTimeLapseFrameCaptureMs" type="int">
-</parameter>
-<parameter name="encoderLevel" type="int">
-</parameter>
-</method>
 <method name="setVideoEncoder"
  return="void"
  abstract="false"
@@ -106435,6 +106993,19 @@
 <parameter name="title" type="java.lang.String">
 </parameter>
 </method>
+<method name="setVisibleInDownloadsUi"
+ return="android.net.DownloadManager.Request"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="isVisible" type="boolean">
+</parameter>
+</method>
 <field name="NETWORK_MOBILE"
  type="int"
  transient="false"
@@ -111458,6 +112029,19 @@
 <parameter name="refCounted" type="boolean">
 </parameter>
 </method>
+<method name="setWorkSource"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
+</method>
 </class>
 </package>
 <package name="android.opengl"
@@ -138310,6 +138894,19 @@
 <parameter name="value" type="boolean">
 </parameter>
 </method>
+<method name="setWorkSource"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="ws" type="android.os.WorkSource">
+</parameter>
+</method>
 </class>
 <class name="Process"
  extends="java.lang.Object"
@@ -139458,6 +140055,134 @@
 </parameter>
 </method>
 </class>
+<class name="WorkSource"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="WorkSource"
+ type="android.os.WorkSource"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="WorkSource"
+ type="android.os.WorkSource"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="orig" type="android.os.WorkSource">
+</parameter>
+</constructor>
+<method name="add"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="other" type="android.os.WorkSource">
+</parameter>
+</method>
+<method name="clear"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="diff"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="other" type="android.os.WorkSource">
+</parameter>
+</method>
+<method name="remove"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="other" type="android.os.WorkSource">
+</parameter>
+</method>
+<method name="set"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="other" type="android.os.WorkSource">
+</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>
+</class>
 </package>
 <package name="android.os.storage"
 >
@@ -141979,6 +142704,21 @@
 <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"
@@ -141994,6 +142734,21 @@
 <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"
@@ -142024,6 +142779,34 @@
 <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>
+</method>
 <field name="EXTRA_NO_HEADERS"
  type="java.lang.String"
  transient="false"
@@ -142057,15 +142840,28 @@
  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="false"
+ final="true"
  deprecated="not deprecated"
  visibility="public"
 >
+<implements name="android.os.Parcelable">
+</implements>
 <constructor name="PreferenceActivity.Header"
  type="android.preference.PreferenceActivity.Header"
  static="false"
@@ -142074,6 +142870,85 @@
  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"
@@ -142094,8 +142969,8 @@
  visibility="public"
 >
 </field>
-<field name="icon"
- type="android.graphics.drawable.Drawable"
+<field name="iconRes"
+ type="int"
  transient="false"
  volatile="false"
  static="false"
@@ -142104,8 +142979,8 @@
  visibility="public"
 >
 </field>
-<field name="iconRes"
- type="int"
+<field name="id"
+ type="long"
  transient="false"
  volatile="false"
  static="false"
@@ -147509,7 +148384,7 @@
  visibility="public"
 >
 </field>
-<field name="TYPE_MAINDEN_NAME"
+<field name="TYPE_MAIDEN_NAME"
  type="int"
  transient="false"
  volatile="false"
@@ -147520,6 +148395,17 @@
  visibility="public"
 >
 </field>
+<field name="TYPE_MAINDEN_NAME"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="deprecated"
+ visibility="public"
+>
+</field>
 <field name="TYPE_OTHER_NAME"
  type="int"
  transient="false"
@@ -148275,8 +149161,40 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<implements name="android.provider.ContactsContract.CommonDataKinds.CommonColumns">
+</implements>
 <implements name="android.provider.ContactsContract.DataColumnsWithJoins">
 </implements>
+<method name="getTypeLabel"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="res" type="android.content.res.Resources">
+</parameter>
+<parameter name="type" type="int">
+</parameter>
+<parameter name="label" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="getTypeLabelResource"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="type" type="int">
+</parameter>
+</method>
 <field name="CONTENT_ITEM_TYPE"
  type="java.lang.String"
  transient="false"
@@ -148299,6 +149217,39 @@
  visibility="public"
 >
 </field>
+<field name="TYPE_HOME"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TYPE_OTHER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TYPE_WORK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="ContactsContract.CommonDataKinds.StructuredName"
  extends="java.lang.Object"
@@ -163257,6 +164208,17 @@
  visibility="public"
 >
 </method>
+<method name="getPsc"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="setLacAndCid"
  return="void"
  abstract="false"
@@ -189544,17 +190506,6 @@
  visibility="public"
 >
 </field>
-<field name="SOURCE_CLASS_JOYSTICK"
- type="int"
- transient="false"
- volatile="false"
- value="16"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="SOURCE_CLASS_MASK"
  type="int"
  transient="false"
@@ -189610,39 +190561,6 @@
  visibility="public"
 >
 </field>
-<field name="SOURCE_GAMEPAD"
- type="int"
- transient="false"
- volatile="false"
- value="1025"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="SOURCE_JOYSTICK_LEFT"
- type="int"
- transient="false"
- volatile="false"
- value="16777232"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="SOURCE_JOYSTICK_RIGHT"
- type="int"
- transient="false"
- volatile="false"
- value="33554448"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="SOURCE_KEYBOARD"
  type="int"
  transient="false"
@@ -189790,7 +190708,7 @@
  native="false"
  synchronized="false"
  static="false"
- final="true"
+ final="false"
  deprecated="not deprecated"
  visibility="public"
 >
@@ -189838,26 +190756,6 @@
  visibility="public"
 >
 </field>
-<field name="mDeviceId"
- type="int"
- transient="false"
- volatile="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-</field>
-<field name="mSource"
- type="int"
- transient="false"
- volatile="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-</field>
 </class>
 <class name="InputQueue"
  extends="java.lang.Object"
@@ -193248,6 +194146,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"
@@ -193424,6 +194333,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"
@@ -197289,6 +198211,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"
@@ -198658,6 +199593,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"
@@ -198746,6 +199692,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"
@@ -199882,6 +200839,19 @@
 <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"
@@ -202748,6 +203718,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"
@@ -232575,6 +233558,21 @@
 <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="setOnClickPendingIntent"
  return="void"
  abstract="false"
@@ -232590,6 +233588,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"
@@ -233730,6 +234743,28 @@
  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"
@@ -233741,6 +234776,19 @@
  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"
@@ -233754,6 +234802,19 @@
 <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"
@@ -233795,6 +234856,19 @@
 <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"
@@ -233835,18 +234909,6 @@
 </parameter>
 </method>
 </class>
-<interface name="SearchView.FilterableListAdapter"
- abstract="true"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<implements name="android.widget.Filterable">
-</implements>
-<implements name="android.widget.ListAdapter">
-</implements>
-</interface>
 <interface name="SearchView.OnCloseListener"
  abstract="true"
  static="true"
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 4a1d27b..8ab94ad 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -60,7 +60,7 @@
     return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll;
 }
 
-static void playSource(OMXClient *client, const sp<MediaSource> &source) {
+static void playSource(OMXClient *client, sp<MediaSource> &source) {
     sp<MetaData> meta = source->getFormat();
 
     const char *mime;
@@ -81,6 +81,8 @@
         }
     }
 
+    source.clear();
+
     status_t err = rawSource->start();
 
     if (err != OK) {
diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
index d3ec3d9..e1d6619 100644
--- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
@@ -64,7 +64,7 @@
                             = IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
                     try {
                         IBinder lock = new Binder();
-                        pm.acquireWakeLock(PowerManager.FULL_WAKE_LOCK, lock, "svc power");
+                        pm.acquireWakeLock(PowerManager.FULL_WAKE_LOCK, lock, "svc power", null);
                         pm.setStayOnSetting(val);
                         pm.releaseWakeLock(lock, 0);
                     }
diff --git a/core/java/android/animation/AnimatorListenerAdapter.java b/core/java/android/animation/AnimatorListenerAdapter.java
index 6182389..e5d70a4 100644
--- a/core/java/android/animation/AnimatorListenerAdapter.java
+++ b/core/java/android/animation/AnimatorListenerAdapter.java
@@ -24,28 +24,28 @@
 public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener {
 
     /**
-     * {@inheritdoc}
+     * {@inheritDoc}
      */
     @Override
     public void onAnimationCancel(Animator animation) {
     }
 
     /**
-     * {@inheritdoc}
+     * {@inheritDoc}
      */
     @Override
     public void onAnimationEnd(Animator animation) {
     }
 
     /**
-     * {@inheritdoc}
+     * {@inheritDoc}
      */
     @Override
     public void onAnimationRepeat(Animator animation) {
     }
 
     /**
-     * {@inheritdoc}
+     * {@inheritDoc}
      */
     @Override
     public void onAnimationStart(Animator animation) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 3dcdc0a..2e8d682 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4058,6 +4058,7 @@
             fragment.mFragmentId = id;
             fragment.mTag = tag;
             fragment.mImmediateActivity = this;
+            fragment.mFragmentManager = mFragments;
             // If this fragment is newly instantiated (either right now, or
             // from last saved state), then give it the attributes to
             // initialize itself.
@@ -4203,6 +4204,7 @@
     }
     
     final void performStart() {
+        mFragments.mStateSaved = false;
         mCalled = false;
         mFragments.execPendingActions();
         mInstrumentation.callActivityOnStart(this);
@@ -4220,6 +4222,8 @@
     }
     
     final void performRestart() {
+        mFragments.mStateSaved = false;
+
         synchronized (mManagedCursors) {
             final int N = mManagedCursors.size();
             for (int i=0; i<N; i++) {
@@ -4358,6 +4362,7 @@
         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 {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 60fcdc4..c0e2987 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3154,18 +3154,11 @@
         /**
          * For system applications on userdebug/eng builds, log stack
          * traces of disk and network access to dropbox for analysis.
-         *
-         * Similar logic exists in SystemServer.java.
          */
         if ((data.appInfo.flags &
              (ApplicationInfo.FLAG_SYSTEM |
-              ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0 &&
-            !"user".equals(Build.TYPE)) {
-            StrictMode.setThreadPolicy(
-                StrictMode.DISALLOW_DISK_WRITE |
-                StrictMode.DISALLOW_DISK_READ |
-                StrictMode.DISALLOW_NETWORK |
-                StrictMode.PENALTY_DROPBOX);
+              ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
+            StrictMode.conditionallyEnableDebugLogging();
         }
 
         /**
diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackRecord.java
similarity index 83%
rename from core/java/android/app/BackStackEntry.java
rename to core/java/android/app/BackStackRecord.java
index 71fd5e5..e6cc0f9 100644
--- a/core/java/android/app/BackStackEntry.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -28,16 +29,20 @@
     final int mTransitionStyle;
     final String mName;
     final int mIndex;
-    
-    public BackStackState(FragmentManagerImpl fm, BackStackEntry bse) {
+    final int mBreadCrumbTitleRes;
+    final CharSequence mBreadCrumbTitleText;
+    final int mBreadCrumbShortTitleRes;
+    final CharSequence mBreadCrumbShortTitleText;
+
+    public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
         int numRemoved = 0;
-        BackStackEntry.Op op = bse.mHead;
+        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");
         }
@@ -64,21 +69,29 @@
         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 BackStackEntry instantiate(FragmentManagerImpl fm) {
-        BackStackEntry bse = new BackStackEntry(fm);
+
+    public BackStackRecord instantiate(FragmentManagerImpl fm) {
+        BackStackRecord bse = new BackStackRecord(fm);
         int pos = 0;
         while (pos < mOps.length) {
-            BackStackEntry.Op op = new BackStackEntry.Op();
+            BackStackRecord.Op op = new BackStackRecord.Op();
             op.cmd = mOps[pos++];
             if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
                     "BSE " + bse + " set base fragment #" + mOps[pos]);
@@ -103,10 +116,14 @@
         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;
     }
@@ -117,14 +134,18 @@
         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];
         }
@@ -134,18 +155,19 @@
 /**
  * @hide Entry of an operation on the fragment back stack.
  */
-final class BackStackEntry implements FragmentTransaction, Runnable {
+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;
@@ -155,7 +177,7 @@
         int exitAnim;
         ArrayList<Fragment> removed;
     }
-    
+
     Op mHead;
     Op mTail;
     int mNumOp;
@@ -167,11 +189,34 @@
     String mName;
     boolean mCommitted;
     int mIndex;
-    
-    public BackStackEntry(FragmentManagerImpl manager) {
+
+    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;
@@ -184,7 +229,7 @@
         op.exitAnim = mExitAnim;
         mNumOp++;
     }
-        
+
     public FragmentTransaction add(Fragment fragment, String tag) {
         doAddOp(0, fragment, tag, OP_ADD);
         return this;
@@ -205,7 +250,8 @@
             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 "
@@ -214,7 +260,7 @@
             }
             fragment.mTag = tag;
         }
-        
+
         if (containerViewId != 0) {
             if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
                 throw new IllegalStateException("Can't change container ID of fragment "
@@ -223,7 +269,7 @@
             }
             fragment.mContainerId = fragment.mFragmentId = containerViewId;
         }
-        
+
         Op op = new Op();
         op.cmd = opcmd;
         op.fragment = fragment;
@@ -233,27 +279,27 @@
     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;
     }
 
@@ -261,50 +307,74 @@
         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;
@@ -340,10 +410,10 @@
         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()");
@@ -404,23 +474,23 @@
                     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) {
@@ -464,10 +534,10 @@
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
                 }
             }
-            
+
             op = op.prev;
         }
-        
+
         if (doStateMove) {
             mManager.moveToState(mManager.mCurState,
                     FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
@@ -478,15 +548,15 @@
             mIndex = -1;
         }
     }
-    
+
     public String getName() {
         return mName;
     }
-    
+
     public int getTransition() {
         return mTransition;
     }
-    
+
     public int getTransitionStyle() {
         return mTransitionStyle;
     }
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 16d105a..1cbed79 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -94,6 +94,7 @@
         mInstance.mContainerId = mContainerId;
         mInstance.mTag = mTag;
         mInstance.mRetainInstance = mRetainInstance;
+        mInstance.mFragmentManager = activity.mFragments;
         
         return mInstance;
     }
@@ -318,6 +319,11 @@
     // 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;
@@ -578,10 +584,13 @@
     
     /**
      * Return the FragmentManager for interacting with fragments associated
-     * with this fragment's activity.
+     * with this fragment's activity.  Note that this will be non-null slightly
+     * before {@link #getActivity()}, in 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 mActivity.mFragments;
+        return mFragmentManager;
     }
 
     /**
@@ -858,7 +867,7 @@
      * {@link #onActivityCreated(Bundle)}.
      *
      * <p>This corresponds to {@link Activity#onSaveInstanceState(Bundle)
-     * Activity.onnSaveInstanceState(Bundle)} and most of the discussion there
+     * 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
diff --git a/core/java/android/app/FragmentBreadCrumbs.java b/core/java/android/app/FragmentBreadCrumbs.java
new file mode 100644
index 0000000..0d39d0b
--- /dev/null
+++ b/core/java/android/app/FragmentBreadCrumbs.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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++) {
+            FragmentManager.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);
+            }
+        }
+        int viewI = mTopEntry != null ? numEntries+1 : numEntries;
+        numViews = mContainer.getChildCount();
+        while (numViews > viewI) {
+            mContainer.removeViewAt(numViews-1);
+            numViews--;
+        }
+    }
+}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 85a9d60..9f958246 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -40,6 +40,48 @@
  */
 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.
      */
@@ -105,6 +147,28 @@
     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
@@ -182,16 +246,19 @@
     ArrayList<Fragment> mActive;
     ArrayList<Fragment> mAdded;
     ArrayList<Integer> mAvailIndices;
-    ArrayList<BackStackEntry> mBackStack;
+    ArrayList<BackStackRecord> mBackStack;
     
     // Must be accessed while locked.
-    ArrayList<BackStackEntry> mBackStackIndices;
+    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;
@@ -204,7 +271,7 @@
         }
     };
     public FragmentTransaction openTransaction() {
-        return new BackStackEntry(this);
+        return new BackStackRecord(this);
     }
 
     public boolean popBackStack() {
@@ -222,6 +289,27 @@
         return popBackStackState(mActivity.mHandler, null, id, flags);
     }
 
+    public int countBackStackEntries() {
+        return mBackStack != null ? mBackStack.size() : 0;
+    }
+
+    public BackStackEntry getBackStackEntry(int index) {
+        return mBackStack.get(index);
+    }
+
+    public void addOnBackStackChangedListener(OnBackStackChangedListener listener) {
+        if (mBackStackChangeListeners == null) {
+            mBackStackChangeListeners = new ArrayList<OnBackStackChangedListener>();
+        }
+        mBackStackChangeListeners.add(listener);
+    }
+
+    public void removeOnBackStackChangedListener(OnBackStackChangedListener listener) {
+        if (mBackStackChangeListeners != null) {
+            mBackStackChangeListeners.remove(listener);
+        }
+    }
+
     public void putFragment(Bundle bundle, String key, Fragment fragment) {
         if (fragment.mIndex < 0) {
             throw new IllegalStateException("Fragment " + fragment
@@ -484,6 +572,7 @@
                             throw new SuperNotCalledException("Fragment " + f
                                     + " did not call through to super.onDetach()");
                         }
+                        f.mImmediateActivity = null;
                         f.mActivity = null;
                     }
             }
@@ -678,6 +767,10 @@
     }
     
     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>();
@@ -690,11 +783,11 @@
         }
     }
     
-    public int allocBackStackIndex(BackStackEntry bse) {
+    public int allocBackStackIndex(BackStackRecord bse) {
         synchronized (this) {
             if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
                 if (mBackStackIndices == null) {
-                    mBackStackIndices = new ArrayList<BackStackEntry>();
+                    mBackStackIndices = new ArrayList<BackStackRecord>();
                 }
                 int index = mBackStackIndices.size();
                 if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
@@ -710,10 +803,10 @@
         }
     }
 
-    public void setBackStackIndex(int index, BackStackEntry bse) {
+    public void setBackStackIndex(int index, BackStackRecord bse) {
         synchronized (this) {
             if (mBackStackIndices == null) {
-                mBackStackIndices = new ArrayList<BackStackEntry>();
+                mBackStackIndices = new ArrayList<BackStackRecord>();
             }
             int N = mBackStackIndices.size();
             if (index < N) {
@@ -779,11 +872,20 @@
         }
     }
     
-    public void addBackStackState(BackStackEntry state) {
+    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<BackStackEntry>();
+            mBackStack = new ArrayList<BackStackRecord>();
         }
         mBackStack.add(state);
+        reportBackStackChanged();
     }
     
     boolean popBackStackState(Handler handler, String name, int id, int flags) {
@@ -795,11 +897,12 @@
             if (last < 0) {
                 return false;
             }
-            final BackStackEntry bss = mBackStack.remove(last);
+            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 {
@@ -809,7 +912,7 @@
                 // the stack.
                 index = mBackStack.size()-1;
                 while (index >= 0) {
-                    BackStackEntry bss = mBackStack.get(index);
+                    BackStackRecord bss = mBackStack.get(index);
                     if (name != null && name.equals(bss.getName())) {
                         break;
                     }
@@ -825,7 +928,7 @@
                     index--;
                     // Consume all following entries that match.
                     while (index >= 0) {
-                        BackStackEntry bss = mBackStack.get(index);
+                        BackStackRecord bss = mBackStack.get(index);
                         if ((name != null && name.equals(bss.getName()))
                                 || (id >= 0 && id == bss.mIndex)) {
                             index--;
@@ -838,8 +941,8 @@
             if (index == mBackStack.size()-1) {
                 return false;
             }
-            final ArrayList<BackStackEntry> states
-                    = new ArrayList<BackStackEntry>();
+            final ArrayList<BackStackRecord> states
+                    = new ArrayList<BackStackRecord>();
             for (int i=mBackStack.size()-1; i>index; i--) {
                 states.add(mBackStack.remove(i));
             }
@@ -850,6 +953,7 @@
                         if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
                         states.get(i).popFromBackStack(i == LAST);
                     }
+                    reportBackStackChanged();
                 }
             });
         }
@@ -888,6 +992,8 @@
     }
     
     Parcelable saveAllState() {
+        mStateSaved = true;
+
         if (mActive == null || mActive.size() <= 0) {
             return null;
         }
@@ -1029,6 +1135,22 @@
             }
         }
         
+        // 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);
@@ -1049,9 +1171,9 @@
         
         // Build the back stack.
         if (fms.mBackStack != null) {
-            mBackStack = new ArrayList<BackStackEntry>(fms.mBackStack.length);
+            mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length);
             for (int i=0; i<fms.mBackStack.length; i++) {
-                BackStackEntry bse = fms.mBackStack[i].instantiate(this);
+                BackStackRecord bse = fms.mBackStack[i].instantiate(this);
                 if (DEBUG) Log.v(TAG, "restoreAllState: adding bse #" + i
                         + " (index " + bse.mIndex + "): " + bse);
                 mBackStack.add(bse);
@@ -1070,18 +1192,22 @@
     }
     
     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);
     }
     
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 9d44106..09d8d26 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -88,7 +88,7 @@
 
     /**
      * @return <code>true</code> if this transaction contains no operations,
-     *         <code>false</code> otherwise.
+     * <code>false</code> otherwise.
      */
     public boolean isEmpty();
     
@@ -111,14 +111,65 @@
     /** Fragment is being removed */
     public final int TRANSIT_FRAGMENT_CLOSE = 2 | 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.
diff --git a/core/java/android/app/LoaderManagingFragment.java b/core/java/android/app/LoaderManagingFragment.java
index 5d417a0..af71170 100644
--- a/core/java/android/app/LoaderManagingFragment.java
+++ b/core/java/android/app/LoaderManagingFragment.java
@@ -148,7 +148,7 @@
         mStarted = false;
     }
 
-    /** TO DO: This needs to be turned into a retained fragment.
+    /* TO DO: This needs to be turned into a retained fragment.
     @Override
     public Object onRetainNonConfigurationInstance() {
         // Pass the loader along to the next guy
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 2fb746c..18602df 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -588,7 +588,9 @@
      */
     private void updateVoiceButton(boolean empty) {
         int visibility = View.GONE;
-        if (mSearchable.getVoiceSearchEnabled() && empty) {
+        if ((mAppSearchData == null || !mAppSearchData.getBoolean(
+                SearchManager.DISABLE_VOICE_SEARCH, false))
+                && mSearchable.getVoiceSearchEnabled() && empty) {
             Intent testIntent = null;
             if (mSearchable.getVoiceSearchLaunchWebSearch()) {
                 testIntent = mVoiceWebSearchIntent;
@@ -1081,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);
     }
@@ -1183,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();
             }
@@ -1209,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
@@ -1237,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,
@@ -1247,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 a1ca707..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).
@@ -395,6 +385,14 @@
     public final static String CONTEXT_IS_VOICE = "android.search.CONTEXT_IS_VOICE";
 
     /**
+     * This means that the voice icon should not be shown at all, because the
+     * current search engine does not support voice search.
+     * @hide
+     */
+    public final static String DISABLE_VOICE_SEARCH
+            = "android.search.DISABLE_VOICE_SEARCH";
+
+    /**
      * Reference to the shared system search service.
      */
     private static ISearchManager mService;
@@ -405,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;
@@ -419,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}
@@ -435,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
@@ -448,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,
@@ -587,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
@@ -599,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() {
@@ -623,7 +621,7 @@
          */
         public void onDismiss();
     }
-    
+
     /**
      * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor
      * search UI state.
@@ -639,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) {
@@ -648,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) {
@@ -759,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/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/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 03bcadc..33fd395 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -26,8 +26,10 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
+import android.util.Pair;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -463,11 +465,11 @@
     }
 
     /**
-     * Set the friendly Bluetooth name of the local Bluetoth adapter.
+     * Set the friendly Bluetooth name of the local Bluetooth adapter.
      * <p>This name is visible to remote Bluetooth devices.
-     * <p>Valid Bluetooth names are a maximum of 248 UTF-8 characters, however
-     * many remote devices can only display the first 40 characters, and some
-     * may be limited to just 20.
+     * <p>Valid Bluetooth names are a maximum of 248 bytes using UTF-8
+     * encoding, although many remote devices can only display the first
+     * 40 characters, and some may be limited to just 20.
      * <p>If Bluetooth state is not {@link #STATE_ON}, this API
      * will return false. After turning on Bluetooth,
      * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
@@ -486,7 +488,7 @@
     }
 
     /**
-     * Get the current Bluetooth scan mode of the local Bluetooth adaper.
+     * Get the current Bluetooth scan mode of the local Bluetooth adapter.
      * <p>The Bluetooth scan mode determines if the local adapter is
      * connectable and/or discoverable from remote Bluetooth devices.
      * <p>Possible values are:
@@ -609,7 +611,7 @@
     /**
      * Cancel the current device discovery process.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
-     * <p>Because discovery is a heavyweight precedure for the Bluetooth
+     * <p>Because discovery is a heavyweight procedure for the Bluetooth
      * adapter, this method should always be called before attempting to connect
      * to a remote device with {@link
      * android.bluetooth.BluetoothSocket#connect()}. Discovery is not managed by
@@ -863,6 +865,37 @@
         return socket;
     }
 
+    /**
+     * Read the local Out of Band Pairing Data
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @return Pair<byte[], byte[]> of Hash and Randomizer
+     *
+     * @hide
+     */
+    public Pair<byte[], byte[]> readOutOfBandData() {
+        if (getState() != STATE_ON) return null;
+        try {
+            byte[] hash = new byte[16];
+            byte[] randomizer = new byte[16];
+
+            byte[] ret = mService.readOutOfBandData();
+
+            if (ret  == null || ret.length != 32) return null;
+
+            hash = Arrays.copyOfRange(ret, 0, 16);
+            randomizer = Arrays.copyOfRange(ret, 16, 32);
+
+            if (DBG) {
+                Log.d(TAG, "readOutOfBandData:" + Arrays.toString(hash) +
+                  ":" + Arrays.toString(randomizer));
+            }
+            return new Pair<byte[], byte[]>(hash, randomizer);
+
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
     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/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index e77e76f..e577ec4 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -325,7 +325,9 @@
     /** The user will be prompted to enter the passkey displayed on remote device
      * @hide */
     public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
-
+    /** The user will be prompted to accept or deny the OOB pairing request
+     * @hide */
+    public static final int PAIRING_VARIANT_OOB_CONSENT = 5;
     /**
      * Used as an extra field in {@link #ACTION_UUID} intents,
      * Contains the {@link android.os.ParcelUuid}s of the remote device which
@@ -464,6 +466,52 @@
     }
 
     /**
+     * Start the bonding (pairing) process with the remote device using the
+     * Out Of Band mechanism.
+     *
+     * <p>This is an asynchronous call, it will return immediately. Register
+     * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when
+     * the bonding process completes, and its result.
+     *
+     * <p>Android system services will handle the necessary user interactions
+     * to confirm and complete the bonding process.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+     *
+     * @param hash - Simple Secure pairing hash
+     * @param randomizer - The random key obtained using OOB
+     * @return false on immediate error, true if bonding will begin
+     *
+     * @hide
+     */
+    public boolean createBondOutOfBand(byte[] hash, byte[] randomizer) {
+        try {
+            return sService.createBondOutOfBand(mAddress, hash, randomizer);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    /**
+     * Set the Out Of Band data for a remote device to be used later
+     * in the pairing mechanism. Users can obtain this data through other
+     * trusted channels
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+     *
+     * @param hash Simple Secure pairing hash
+     * @param randomizer The random key obtained using OOB
+     * @return false on error; true otherwise
+     *
+     * @hide
+     */
+    public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) {
+      try {
+        return sService.setDeviceOutOfBandData(mAddress, hash, randomizer);
+      } catch (RemoteException e) {Log.e(TAG, "", e);}
+      return false;
+    }
+
+    /**
      * Cancel an in-progress bonding request started with {@link #createBond}.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
      *
@@ -617,6 +665,14 @@
     }
 
     /** @hide */
+    public boolean setRemoteOutOfBandData() {
+        try {
+          return sService.setRemoteOutOfBandData(mAddress);
+      } catch (RemoteException e) {Log.e(TAG, "", e);}
+      return false;
+    }
+
+    /** @hide */
     public boolean cancelPairingUserInput() {
         try {
             return sService.cancelPairingUserInput(mAddress);
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index c4a40cd..cc231466 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -45,12 +45,15 @@
     boolean startDiscovery();
     boolean cancelDiscovery();
     boolean isDiscovering();
+    byte[] readOutOfBandData();
 
     boolean createBond(in String address);
+    boolean createBondOutOfBand(in String address, in byte[] hash, in byte[] randomizer);
     boolean cancelBondProcess(in String address);
     boolean removeBond(in String address);
     String[] listBonds();
     int getBondState(in String address);
+    boolean setDeviceOutOfBandData(in String address, in byte[] hash, in byte[] randomizer);
 
     String getRemoteName(in String address);
     int getRemoteClass(in String address);
@@ -61,6 +64,7 @@
     boolean setPin(in String address, in byte[] pin);
     boolean setPasskey(in String address, int passkey);
     boolean setPairingConfirmation(in String address, boolean confirm);
+    boolean setRemoteOutOfBandData(in String addres);
     boolean cancelPairingUserInput(in String address);
 
     boolean setTrust(in String address, in boolean value);
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index 9dd7b9f..c0a268f 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -37,8 +37,9 @@
 public abstract class AbstractThreadedSyncAdapter {
     /**
      * Kernel event log tag.  Also listed in data/etc/event-log-tags.
-     * @Deprecated
+     * @deprecated Private constant.  May go away in the next release.
      */
+    @Deprecated
     public static final int LOG_SYNC_DETAILS = 2743;
 
     private final Context mContext;
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 7945f3f..12e9bab 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -272,13 +272,14 @@
      * The ContentValues back references are represented as a ContentValues object where the
      * key refers to a column and the value is an index of the back reference whose
      * valued should be associated with the column.
+     * <p>
+     * This is intended to be a private method but it is exposed for
+     * unit testing purposes
      * @param backRefs an array of previous results
      * @param numBackRefs the number of valid previous results in backRefs
      * @return the ContentValues that should be used in this operation application after
      * expansion of back references. This can be called if either mValues or mValuesBackReferences
      * is null
-     * @VisibleForTesting this is intended to be a private method but it is exposed for
-     * unit testing purposes
      */
     public ContentValues resolveValueBackReferences(
             ContentProviderResult[] backRefs, int numBackRefs) {
@@ -308,13 +309,14 @@
      * the key is an index into the selection argument array (see {@link Builder#withSelection})
      * and the value is the index of the previous result that should be used for that selection
      * argument array slot.
+     * <p>
+     * This is intended to be a private method but it is exposed for
+     * unit testing purposes
      * @param backRefs an array of previous results
      * @param numBackRefs the number of valid previous results in backRefs
      * @return the ContentValues that should be used in this operation application after
      * expansion of back references. This can be called if either mValues or mValuesBackReferences
      * is null
-     * @VisibleForTesting this is intended to be a private method but it is exposed for
-     * unit testing purposes
      */
     public String[] resolveSelectionArgsBackReferences(
             ContentProviderResult[] backRefs, int numBackRefs) {
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index ce047f6..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;
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 7f749bb..950d339 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -47,6 +47,7 @@
 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;
 import android.text.format.Time;
@@ -129,8 +130,8 @@
 
     private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
 
-    private static final String SYNC_WAKE_LOCK_PREFIX = "SyncWakeLock";
-    private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
+    private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
+    private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
 
     private Context mContext;
 
@@ -1735,6 +1736,7 @@
             PowerManager.WakeLock oldWakeLock = mSyncWakeLock;
             try {
                 mSyncWakeLock = getSyncWakeLock(op.account.type, op.authority);
+				mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid));
                 mSyncWakeLock.acquire();
             } finally {
                 if (oldWakeLock != null && oldWakeLock != mSyncWakeLock) {
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index ae6a311..38d897e 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -354,8 +354,6 @@
 
     /**
      * Full path to the directory where native JNI libraries are stored.
-     * 
-     * {@hide}
      */
     public String nativeLibraryDir;
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8963d0c..1a3bcc4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -192,7 +192,7 @@
 
     /**
      * Signature check result: this is returned by {@link #checkSignatures}
-     * if the two packages have a matching signature.
+     * if all signatures on the two packages match.
      */
     public static final int SIGNATURE_MATCH = 0;
 
@@ -204,25 +204,25 @@
 
     /**
      * Signature check result: this is returned by {@link #checkSignatures}
-     * if the first package is not signed, but the second is.
+     * if the first package is not signed but the second is.
      */
     public static final int SIGNATURE_FIRST_NOT_SIGNED = -1;
 
     /**
      * Signature check result: this is returned by {@link #checkSignatures}
-     * if the second package is not signed, but the first is.
+     * if the second package is not signed but the first is.
      */
     public static final int SIGNATURE_SECOND_NOT_SIGNED = -2;
 
     /**
      * Signature check result: this is returned by {@link #checkSignatures}
-     * if both packages are signed but there is no matching signature.
+     * if not all signatures on both packages match.
      */
     public static final int SIGNATURE_NO_MATCH = -3;
 
     /**
      * Signature check result: this is returned by {@link #checkSignatures}
-     * if either of the given package names are not valid.
+     * if either of the packages are not valid.
      */
     public static final int SIGNATURE_UNKNOWN_PACKAGE = -4;
 
@@ -1245,20 +1245,14 @@
      *
      * @param pkg1 First package name whose signature will be compared.
      * @param pkg2 Second package name whose signature will be compared.
-     * @return Returns an integer indicating whether there is a matching
-     * signature: the value is >= 0 if there is a match (or neither package
-     * is signed), or < 0 if there is not a match.  The match result can be
-     * further distinguished with the success (>= 0) constants
-     * {@link #SIGNATURE_MATCH}, {@link #SIGNATURE_NEITHER_SIGNED}; or
-     * failure (< 0) constants {@link #SIGNATURE_FIRST_NOT_SIGNED},
-     * {@link #SIGNATURE_SECOND_NOT_SIGNED}, {@link #SIGNATURE_NO_MATCH},
-     * or {@link #SIGNATURE_UNKNOWN_PACKAGE}.
+     *
+     * @return Returns an integer indicating whether all signatures on the
+     * two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if
+     * all signatures match or < 0 if there is not a match ({@link
+     * #SIGNATURE_NO_MATCH} or {@link #SIGNATURE_UNKNOWN_PACKAGE}).
      *
      * @see #checkSignatures(int, int)
      * @see #SIGNATURE_MATCH
-     * @see #SIGNATURE_NEITHER_SIGNED
-     * @see #SIGNATURE_FIRST_NOT_SIGNED
-     * @see #SIGNATURE_SECOND_NOT_SIGNED
      * @see #SIGNATURE_NO_MATCH
      * @see #SIGNATURE_UNKNOWN_PACKAGE
      */
@@ -1273,20 +1267,14 @@
      *
      * @param uid1 First UID whose signature will be compared.
      * @param uid2 Second UID whose signature will be compared.
-     * @return Returns an integer indicating whether there is a matching
-     * signature: the value is >= 0 if there is a match (or neither package
-     * is signed), or < 0 if there is not a match.  The match result can be
-     * further distinguished with the success (>= 0) constants
-     * {@link #SIGNATURE_MATCH}, {@link #SIGNATURE_NEITHER_SIGNED}; or
-     * failure (< 0) constants {@link #SIGNATURE_FIRST_NOT_SIGNED},
-     * {@link #SIGNATURE_SECOND_NOT_SIGNED}, {@link #SIGNATURE_NO_MATCH},
-     * or {@link #SIGNATURE_UNKNOWN_PACKAGE}.
      *
-     * @see #checkSignatures(int, int)
+     * @return Returns an integer indicating whether all signatures on the
+     * two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if
+     * all signatures match or < 0 if there is not a match ({@link
+     * #SIGNATURE_NO_MATCH} or {@link #SIGNATURE_UNKNOWN_PACKAGE}).
+     *
+     * @see #checkSignatures(String, String)
      * @see #SIGNATURE_MATCH
-     * @see #SIGNATURE_NEITHER_SIGNED
-     * @see #SIGNATURE_FIRST_NOT_SIGNED
-     * @see #SIGNATURE_SECOND_NOT_SIGNED
      * @see #SIGNATURE_NO_MATCH
      * @see #SIGNATURE_UNKNOWN_PACKAGE
      */
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 9b14998..bfaeb82 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -374,6 +374,10 @@
         }
     }
 
+    public Uri getNotificationUri() {
+        return mNotifyUri;
+    }
+
     public boolean getWantsAllOnMoveCalls() {
         return false;
     }
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 90bb0e2..70a7fb6 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -66,7 +66,13 @@
     /** One of the values returned by {@link #getSqlStatementType(String)}. */
     public static final int STATEMENT_ABORT = 6;
     /** One of the values returned by {@link #getSqlStatementType(String)}. */
-    public static final int STATEMENT_OTHER = 7;
+    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
@@ -1255,6 +1261,13 @@
             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;
     }
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 58dab2b..a5e612b 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -76,12 +76,11 @@
         return buff.toString();
     }
 
+    // STOPSHIP remove this method before shipping
     private void checkRefCount() {
-        if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
-            if (mReferenceCount > 1000) {
-                throw new IllegalStateException("refcount: " + mReferenceCount + ", " +
-                        getObjInfo());
-            }
+        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 0b5a4df..588384b 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -31,12 +31,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
@@ -54,38 +54,13 @@
     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) {
-        mDatabase.verifyLockOwner();
-        // Only compile if we don't have a valid statement already or the caller has
-        // explicitly requested a recompile.
-        if (forceCompilation) {
-            // Note that the native_compile() takes care of destroying any previously
-            // existing programs before it compiles.
-            native_compile(sql);
-        }
+        nHandle = db.mNativeHandle;
+        native_compile(sql);
     }
 
     /* package */ void releaseSqlStatement() {
@@ -102,7 +77,7 @@
      */
     /* package */ synchronized boolean acquire() {
         if (mInUse) {
-            // someone already has acquired it.
+            // it is already in use.
             return false;
         }
         mInUse = true;
@@ -117,6 +92,13 @@
         return mInUse;
     }
 
+    /* package */ synchronized void releaseIfNotInUse() {
+        // if it is not in use, release its memory from the database
+        if (!isInUse()) {
+            releaseSqlStatement();
+        }
+    }
+
     /**
      * Make sure that the native resource is cleaned up.
      */
@@ -125,10 +107,15 @@
         try {
             if (nStatement == 0) return;
             // finalizer should NEVER get called
-            int len = mSqlStmt.length();
-            Log.w(TAG, "Releasing statement in a finalizer. Please ensure " +
-                    "that you explicitly call close() on your cursor: " +
-                    mSqlStmt.substring(0, (len > 100) ? 100 : len), mStackTrace);
+            // 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) {
+                int len = mSqlStmt.length();
+                Log.w(TAG, "Releasing statement in a finalizer. Please ensure " +
+                        "that you explicitly call close() on your cursor: " +
+                        mSqlStmt.substring(0, (len > 100) ? 100 : len), mStackTrace);
+            }
             releaseSqlStatement();
         } finally {
             super.finalize();
@@ -140,6 +127,8 @@
             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=");
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index d58a689..fa7763d 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -40,7 +40,7 @@
  * 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 */
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 6937da0..3df1790 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -293,11 +293,7 @@
                     return false;
                 }
                 // cache is full. eldest will be removed.
-                SQLiteCompiledSql entry = eldest.getValue();
-                if (!entry.isInUse()) {
-                    // this {@link SQLiteCompiledSql} is not in use. release it.
-                    entry.releaseSqlStatement();
-                }
+                eldest.getValue().releaseIfNotInUse();
                 // return true, so that this entry is removed automatically by the caller.
                 return true;
             }
@@ -308,8 +304,7 @@
      * SQL statement & schema.
      */
     public static final int MAX_SQL_CACHE_SIZE = 100;
-    private int mCacheFullWarnings;
-    private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1;
+    private boolean mCacheFullWarning;
 
     /** maintain stats about number of cache hits and misses */
     private int mNumCacheHits;
@@ -1895,11 +1890,12 @@
         executeSql(sql, bindArgs);
     }
 
-    private void executeSql(String sql, Object[] bindArgs) throws SQLException {
+    private int executeSql(String sql, Object[] bindArgs) throws SQLException {
         long timeStart = SystemClock.uptimeMillis();
+        int n;
         SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
         try {
-            statement.executeUpdateDelete();
+            n = statement.executeUpdateDelete();
         } catch (SQLiteDatabaseCorruptException e) {
             onCorruption();
             throw e;
@@ -1907,6 +1903,7 @@
             statement.close();
         }
         logTimeStat(sql, timeStart);
+        return n;
     }
 
     @Override
@@ -2090,12 +2087,6 @@
         }
     }
 
-    /*
-     * ============================================================================
-     *
-     *       The following methods deal with compiled-sql cache
-     * ============================================================================
-     */
     /**
      * Adds the given SQL and its compiled-statement-id-returned-by-sqlite to the
      * cache of compiledQueries attached to 'this'.
@@ -2111,26 +2102,21 @@
                 return;
             }
 
-            if (mCompiledQueries.size() == mMaxSqlCacheSize) {
+            if (!isCacheFullWarningLogged() && mCompiledQueries.size() == mMaxSqlCacheSize) {
                 /*
                  * cache size of {@link #mMaxSqlCacheSize} is not enough for this 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() + ". Use setMaxSqlCacheSize() to increase cachesize. ");
-                }
+                Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
+                        getPath() + ". Use setMaxSqlCacheSize() to increase cachesize. ");
+                setCacheFullWarningLogged();
             } 
             /* add the given SQLiteCompiledSql compiledStatement to cache.
              * no need to worry about the cache size - because {@link #mCompiledQueries}
              * self-limits its size to {@link #mMaxSqlCacheSize}.
              */
             mCompiledQueries.put(sql, compiledStatement);
-            if (SQLiteDebug.DEBUG_SQL_CACHE) {
-                Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" +
-                        mCompiledQueries.size() + "|" + sql);
-            }
         }
     }
 
@@ -2148,24 +2134,13 @@
      * 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) {
-            cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null;
-        }
-        if (cacheHit) {
-            mNumCacheHits++;
-        } else {
+    /* package */ synchronized SQLiteCompiledSql getCompiledStatementForSql(String sql) {
+        SQLiteCompiledSql compiledStatement = mCompiledQueries.get(sql);
+        if (compiledStatement == null) {
             mNumCacheMisses++;
+            return null;
         }
-
-        if (SQLiteDebug.DEBUG_SQL_CACHE) {
-            Log.v(TAG, "|cache_stats|" +
-                    getPath() + "|" + mCompiledQueries.size() +
-                    "|" + mNumCacheHits + "|" + mNumCacheMisses +
-                    "|" + cacheHit + "|" + sql);
-        }
+        mNumCacheHits++;
         return compiledStatement;
     }
 
@@ -2183,14 +2158,16 @@
      * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or
      * the value set with previous setMaxSqlCacheSize() call.
      */
-    public synchronized void setMaxSqlCacheSize(int cacheSize) {
-        if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
-            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 void setMaxSqlCacheSize(int cacheSize) {
+        synchronized(this) {
+            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;
         }
-        mMaxSqlCacheSize = cacheSize;
     }
 
     /* package */ boolean isSqlInStatementCache(String sql) {
@@ -2199,6 +2176,26 @@
         }
     }
 
+    private synchronized boolean isCacheFullWarningLogged() {
+        return mCacheFullWarning;
+    }
+
+    private synchronized void setCacheFullWarningLogged() {
+        mCacheFullWarning = true;
+    }
+
+    private synchronized int getCacheHitNum() {
+        return mNumCacheHits;
+    }
+
+    private synchronized int getCacheMissNum() {
+        return mNumCacheMisses;
+    }
+
+    private synchronized int getCachesize() {
+        return mCompiledQueries.size();
+    }
+
     /* package */ void finalizeStatementLater(int id) {
         if (!isOpen()) {
             // database already closed. this statement will already have been finalized.
@@ -2352,17 +2349,19 @@
      *
      * @param size the value the connection handle pool size should be set to.
      */
-    public synchronized void setConnectionPoolSize(int size) {
-        if (mConnectionPool == null) {
-            throw new IllegalStateException("connection pool not enabled");
+    public void setConnectionPoolSize(int size) {
+        synchronized(this) {
+            if (mConnectionPool == null) {
+                throw new IllegalStateException("connection pool not enabled");
+            }
+            int i = mConnectionPool.getMaxPoolSize();
+            if (size < i) {
+                throw new IllegalArgumentException(
+                        "cannot set max pool size to a value less than the current max value(=" +
+                        i + ")");
+            }
+            mConnectionPool.setMaxPoolSize(size);
         }
-        int i = mConnectionPool.getMaxPoolSize();
-        if (size < i) {
-            throw new IllegalArgumentException(
-                    "cannot set max pool size to a value less than the current max value(=" +
-                    i + ")");
-        }
-        mConnectionPool.setMaxPoolSize(size);
     }
 
     /* package */ SQLiteDatabase createPoolConnection(short connectionNum) {
@@ -2478,16 +2477,16 @@
                         }
                         if (pageCount > 0) {
                             dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
-                                    lookasideUsed, db.mNumCacheHits, db.mNumCacheMisses,
-                                    db.mCompiledQueries.size()));
+                                    lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(),
+                                    db.getCachesize()));
                         }
                     }
                     // if there are pooled connections, return the cache stats for them also.
                     if (db.mConnectionPool != null) {
                         for (SQLiteDatabase pDb : db.mConnectionPool.getConnectionList()) {
                             dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
-                                    + lastnode, 0, 0, 0, pDb.mNumCacheHits, pDb.mNumCacheMisses,
-                                    pDb.mCompiledQueries.size()));
+                                    + lastnode, 0, 0, 0, pDb.getCacheHitNum(),
+                                    pDb.getCacheMissNum(), pDb.getCachesize()));
                         }
                     }
                 } catch (SQLiteException e) {
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index c7c0c79..4747a9e 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -86,6 +86,10 @@
      */
     /* 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) {
         this(db, sql, null, true);
@@ -94,7 +98,25 @@
     /* package */ SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
             boolean compileFlag) {
         mSql = sql.trim();
-        mStatementType = DatabaseUtils.getSqlStatementType(mSql);
+        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);
         mDatabase = db;
@@ -112,8 +134,7 @@
 
     private void compileSql() {
         // only cache CRUD statements
-        if (mStatementType != DatabaseUtils.STATEMENT_SELECT &&
-                mStatementType != DatabaseUtils.STATEMENT_UPDATE) {
+        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.
@@ -163,14 +184,19 @@
         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();
-            } else {
-                // it is in compiled-sql cache. reset its CompiledSql#mInUse flag
-                mCompiledSql.release();
+        if ((mStatementType & STATEMENT_CACHEABLE) == 0) {
+            // this SQL statement was never in cache
+            mCompiledSql.releaseSqlStatement();
+        } else {
+            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();
+                } else {
+                    // it is in compiled-sql cache. reset its CompiledSql#mInUse flag
+                    mCompiledSql.release();
+                }
             }
         }
         mCompiledSql = null;
@@ -347,6 +373,16 @@
     }
 
     /* package */ synchronized 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();
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index a5a8a92..bd05e24 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -37,7 +37,6 @@
 @SuppressWarnings("deprecation")
 public class SQLiteStatement extends SQLiteProgram
 {
-
     private static final String TAG = "SQLiteStatement";
 
     private static final boolean READ = true;
@@ -45,9 +44,9 @@
 
     private SQLiteDatabase mOrigDb;
     private int mState;
-    /** possible value for {@link #mState}. indicates that a transaction is started.} */
+    /** 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.} */
+    /** possible value for {@link #mState}. indicates that a lock is acquired. */
     private static final int LOCK_ACQUIRED = 2;
 
     /**
@@ -72,7 +71,7 @@
     }
 
     /**
-     * Execute this SQL statement, if the the number of rows affected by exection of this SQL
+     * 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.
@@ -81,9 +80,17 @@
      */
     public int executeUpdateDelete() {
         synchronized(this) {
-            long timeStart = acquireAndLock(WRITE);
             try {
-                int numChanges = 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 {
@@ -103,8 +110,8 @@
      */
     public long executeInsert() {
         synchronized(this) {
-            long timeStart = acquireAndLock(WRITE);
             try {
+                long timeStart = acquireAndLock(WRITE);
                 long lastInsertedRowId = native_executeInsert();
                 mDatabase.logTimeStat(mSql, timeStart);
                 return lastInsertedRowId;
@@ -124,8 +131,8 @@
      */
     public long simpleQueryForLong() {
         synchronized(this) {
-            long timeStart = acquireAndLock(READ);
             try {
+                long timeStart = acquireAndLock(READ);
                 long retValue = native_1x1_long();
                 mDatabase.logTimeStat(mSql, timeStart);
                 return retValue;
@@ -145,8 +152,8 @@
      */
     public String simpleQueryForString() {
         synchronized(this) {
-            long timeStart = acquireAndLock(READ);
             try {
+                long timeStart = acquireAndLock(READ);
                 String retValue = native_1x1_string();
                 mDatabase.logTimeStat(mSql, timeStart);
                 return retValue;
@@ -166,8 +173,8 @@
      */
     public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() {
         synchronized(this) {
-            long timeStart = acquireAndLock(READ);
             try {
+                long timeStart = acquireAndLock(READ);
                 ParcelFileDescriptor retValue = native_1x1_blob_ashmem();
                 mDatabase.logTimeStat(mSql, timeStart);
                 return retValue;
@@ -200,8 +207,8 @@
         mState = 0;
         // use pooled database connection handles for SELECT SQL statements
         mDatabase.verifyDbIsOpen();
-        SQLiteDatabase db = (mStatementType != DatabaseUtils.STATEMENT_SELECT) ? mDatabase
-                : mDatabase.getDbConnection(mSql);
+        SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0)
+                ? mDatabase.getDbConnection(mSql) : mDatabase;
         // use the database connection obtained above
         mOrigDb = mDatabase;
         mDatabase = db;
@@ -218,13 +225,14 @@
          * beginTransaction() methods in SQLiteDatabase call lockForced() before
          * calling execSQL("BEGIN transaction").
          */
-        if (mStatementType == DatabaseUtils.STATEMENT_BEGIN) {
+        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 == DatabaseUtils.STATEMENT_UPDATE) {
+        } 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();
@@ -258,8 +266,10 @@
         } else if (mState == LOCK_ACQUIRED) {
             mDatabase.unlock();
         }
-        if (mStatementType == DatabaseUtils.STATEMENT_COMMIT ||
-                mStatementType == DatabaseUtils.STATEMENT_ABORT) {
+        if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
+                DatabaseUtils.STATEMENT_COMMIT ||
+                (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
+                DatabaseUtils.STATEMENT_ABORT) {
             mDatabase.resetTransactionUsingExecSqlFlag();
         }
         clearBindings();
@@ -276,4 +286,5 @@
     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/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 3cc89e5..26600f3 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -154,14 +154,21 @@
      * Information about a camera
      */
     public static class CameraInfo {
+        /**
+         * The facing of the camera is opposite to that of the screen.
+         */
         public static final int CAMERA_FACING_BACK = 0;
+
+        /**
+         * The facing of the camera is the same as that of the screen.
+         */
         public static final int CAMERA_FACING_FRONT = 1;
 
         /**
          * The direction that the camera faces to. It should be
          * CAMERA_FACING_BACK or CAMERA_FACING_FRONT.
          */
-        public int mFacing;
+        public int facing;
 
         /**
          * The orientation of the camera image. The value is the angle that the
@@ -175,7 +182,7 @@
          *
          * @see #setDisplayOrientation(int)
          */
-        public int mOrientation;
+        public int orientation;
     };
 
     /**
@@ -210,12 +217,14 @@
 
     /**
      * The id for the default camera.
+     * @see #open(int)
      */
     public static int CAMERA_ID_DEFAULT = 0;
 
     /**
      * Equivalent to Camera.open(Camera.CAMERA_ID_DEFAULT).
      * Creates a new Camera object to access the default camera.
+     * @see #open(int)
      */
     public static Camera open() {
         return new Camera(CAMERA_ID_DEFAULT);
@@ -787,7 +796,7 @@
      *         case Surface.ROTATION_270: degrees = 270; break;
      *     }
      *
-     *     int result = (info.mOrientation - degrees + 360) % 360;
+     *     int result = (info.orientation - degrees + 360) % 360;
      *     camera.setDisplayOrientation(result);
      * }
      * </pre>
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 0068724..a271075 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -24,6 +24,7 @@
 import android.os.ServiceManager;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.IRotationWatcher;
 import android.view.IWindowManager;
 import android.view.Surface;
@@ -446,12 +447,12 @@
                     int accuracy = status[0];
                     synchronized (sListeners) {
                         if (sensor == -1 || sListeners.isEmpty()) {
-                            if (sensor == -1) {
-                                // we lost the connection to the event stream. this happens
-                                // when the last listener is removed.
-                                Log.d(TAG, "_sensors_data_poll() failed, we bail out.");
+                            // we lost the connection to the event stream. this happens
+                            // when the last listener is removed or if there is an error
+                            if (sensor == -1 && !sListeners.isEmpty()) {
+                                // log a warning in case of abnormal termination
+                                Log.e(TAG, "_sensors_data_poll() failed, we bail out: sensors=" + sensor);
                             }
-
                             // we have no more listeners or polling failed, terminate the thread
                             sensors_destroy_queue(sQueue);
                             sQueue = 0;
@@ -487,7 +488,7 @@
         private final ArrayList<Sensor> mSensorList = new ArrayList<Sensor>();
         private final Handler mHandler;
         private SensorEvent mValuesPool;
-        public int mSensors;
+        public SparseBooleanArray mSensors = new SparseBooleanArray();
 
         ListenerDelegate(SensorEventListener listener, Sensor sensor, Handler handler) {
             mSensorEventListener = listener;
@@ -541,18 +542,17 @@
             return mSensorEventListener;
         }
 
-        int addSensor(Sensor sensor) {
-            mSensors |= 1<<sensor.getHandle();
+        void addSensor(Sensor sensor) {
+            mSensors.put(sensor.getHandle(), true);
             mSensorList.add(sensor);
-            return mSensors;
         }
         int removeSensor(Sensor sensor) {
-            mSensors &= ~(1<<sensor.getHandle());
+            mSensors.delete(sensor.getHandle());
             mSensorList.remove(sensor);
-            return mSensors;
+            return mSensors.size();
         }
         boolean hasSensor(Sensor sensor) {
-            return ((mSensors & (1<<sensor.getHandle())) != 0);
+            return mSensors.get(sensor.getHandle());
         }
         List<Sensor> getSensors() {
             return mSensorList;
@@ -971,6 +971,31 @@
         return registerListener(listener, sensor, rate, null);
     }
 
+    private boolean enableSensorLocked(Sensor sensor, int delay) {
+        boolean result = false;
+        for (ListenerDelegate i : sListeners) {
+            if (i.hasSensor(sensor)) {
+                String name = sensor.getName();
+                int handle = sensor.getHandle();
+                result = sensors_enable_sensor(sQueue, name, handle, delay);
+                break;
+            }
+        }
+        return result;
+    }
+
+    private boolean disableSensorLocked(Sensor sensor) {
+        for (ListenerDelegate i : sListeners) {
+            if (i.hasSensor(sensor)) {
+                // not an error, it's just that this sensor is still in use
+                return true;
+            }
+        }
+        String name = sensor.getName();
+        int handle = sensor.getHandle();
+        return sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE);
+    }
+
     /**
      * Registers a {@link android.hardware.SensorEventListener
      * SensorEventListener} for the given sensor.
@@ -1008,7 +1033,7 @@
         if (listener == null || sensor == null) {
             return false;
         }
-        boolean result;
+        boolean result = true;
         int delay = -1;
         switch (rate) {
             case SENSOR_DELAY_FASTEST:
@@ -1029,6 +1054,7 @@
         }
 
         synchronized (sListeners) {
+            // look for this listener in our list
             ListenerDelegate l = null;
             for (ListenerDelegate i : sListeners) {
                 if (i.getListener() == listener) {
@@ -1037,29 +1063,37 @@
                 }
             }
 
-            String name = sensor.getName();
-            int handle = sensor.getHandle();
+            // if we don't find it, add it to the list
             if (l == null) {
-                result = false;
                 l = new ListenerDelegate(listener, sensor, handler);
                 sListeners.add(l);
+                // if the list is not empty, start our main thread
                 if (!sListeners.isEmpty()) {
-                    result = sSensorThread.startLocked();
-                    if (result) {
-                        result = sensors_enable_sensor(sQueue, name, handle, delay);
-                        if (!result) {
-                            // there was an error, remove the listeners
+                    if (sSensorThread.startLocked()) {
+                        if (!enableSensorLocked(sensor, delay)) {
+                            // oops. there was an error
                             sListeners.remove(l);
+                            result = false;
                         }
+                    } else {
+                        // there was an error, remove the listener
+                        sListeners.remove(l);
+                        result = false;
                     }
+                } else {
+                    // weird, we couldn't add the listener
+                    result = false;
                 }
             } else {
-                result = sensors_enable_sensor(sQueue, name, handle, delay);
-                if (result) {
-                    l.addSensor(sensor);
+                l.addSensor(sensor);
+                if (!enableSensorLocked(sensor, delay)) {
+                    // oops. there was an error
+                    l.removeSensor(sensor);
+                    result = false;
                 }
             }
         }
+
         return result;
     }
 
@@ -1067,23 +1101,21 @@
         if (listener == null || sensor == null) {
             return;
         }
+
         synchronized (sListeners) {
             final int size = sListeners.size();
             for (int i=0 ; i<size ; i++) {
                 ListenerDelegate l = sListeners.get(i);
                 if (l.getListener() == listener) {
-                    // disable these sensors
-                    String name = sensor.getName();
-                    int handle = sensor.getHandle();
-                    sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE);
-                    // if we have no more sensors enabled on this listener,
-                    // take it off the list.
                     if (l.removeSensor(sensor) == 0) {
+                        // if we have no more sensors enabled on this listener,
+                        // take it off the list.
                         sListeners.remove(i);
                     }
                     break;
                 }
             }
+            disableSensorLocked(sensor);
         }
     }
 
@@ -1091,18 +1123,17 @@
         if (listener == null) {
             return;
         }
+
         synchronized (sListeners) {
             final int size = sListeners.size();
             for (int i=0 ; i<size ; i++) {
                 ListenerDelegate l = sListeners.get(i);
                 if (l.getListener() == listener) {
+                    sListeners.remove(i);
                     // disable all sensors for this listener
                     for (Sensor sensor : l.getSensors()) {
-                        String name = sensor.getName();
-                        int handle = sensor.getHandle();
-                        sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE);
+                        disableSensorLocked(sensor);
                     }
-                    sListeners.remove(i);
                     break;
                 }
             }
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
old mode 100755
new mode 100644
index 885a6b8..75c945b
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -307,6 +307,10 @@
         /** Create an empty key with no attributes. */
         public Key(Row parent) {
             keyboard = parent.parent;
+            height = parent.defaultHeight;
+            width = parent.defaultWidth;
+            gap = parent.defaultHorizontalGap;
+            edgeFlags = parent.rowEdgeFlags;
         }
         
         /** Create a key with the given top-left coordinate and extract its attributes from
@@ -587,9 +591,6 @@
             final Key key = new Key(row);
             key.x = x;
             key.y = y;
-            key.width = mDefaultWidth;
-            key.height = mDefaultHeight;
-            key.gap = mDefaultHorizontalGap;
             key.label = String.valueOf(c);
             key.codes = new int[] { c };
             column++;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 61fc9e6..b483f6c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -126,6 +126,17 @@
     public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
             "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
 
+
+    /**
+     * Broadcast Action: The network connection may not be good
+     * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
+     * {@code ConnectivityManager.EXTRA_NETWORK_INFO} to specify
+     * the network and it's condition.
+     * @hide
+     */
+    public static final String INET_CONDITION_ACTION =
+            "android.net.conn.INET_CONDITION_ACTION";
+
     /**
      * Broadcast Action: A tetherable connection has come or gone
      * TODO - finish the doc
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/DownloadManager.java b/core/java/android/net/DownloadManager.java
index e04367a..00c1aac 100644
--- a/core/java/android/net/DownloadManager.java
+++ b/core/java/android/net/DownloadManager.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.database.CursorWrapper;
@@ -276,6 +277,7 @@
         private String mMediaType;
         private boolean mRoamingAllowed = true;
         private int mAllowedNetworkTypes = ~0; // default to all network types allowed
+        private boolean mIsVisibleInDownloadsUi = true;
 
         /**
          * @param uri the HTTP URI to download.
@@ -285,8 +287,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;
         }
@@ -387,6 +389,17 @@
         }
 
         /**
+         * Set whether this download should be displayed in the system's Downloads UI. True by
+         * default.
+         * @param isVisible whether to display this download in the Downloads UI
+         * @return this object
+         */
+        public Request setVisibleInDownloadsUi(boolean isVisible) {
+            mIsVisibleInDownloadsUi = isVisible;
+            return this;
+        }
+
+        /**
          * @return ContentValues to be passed to DownloadProvider.insert()
          */
         ContentValues toContentValues(String packageName) {
@@ -418,6 +431,7 @@
 
             values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
             values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
+            values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
 
             return values;
         }
@@ -458,6 +472,7 @@
         private Integer mStatusFlags = null;
         private String mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
         private int mOrderDirection = ORDER_DESCENDING;
+        private boolean mOnlyIncludeVisibleInDownloadsUi = false;
 
         /**
          * Include only the download with the given ID.
@@ -479,6 +494,19 @@
         }
 
         /**
+         * Controls whether this query includes downloads not visible in the system's Downloads UI.
+         * @param value if true, this query will only include downloads that should be displayed in
+         *            the system's Downloads UI; if false (the default), this query will include
+         *            both visible and invisible downloads.
+         * @return this object
+         * @hide
+         */
+        public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
+            mOnlyIncludeVisibleInDownloadsUi = value;
+            return this;
+        }
+
+        /**
          * Change the sort order of the returned Cursor.
          *
          * @param column one of the COLUMN_* constants; currently, only
@@ -509,12 +537,12 @@
          * @param projection the projection to pass to ContentResolver.query()
          * @return the Cursor returned by ContentResolver.query()
          */
-        Cursor runQuery(ContentResolver resolver, String[] projection) {
-            Uri uri = Downloads.CONTENT_URI;
-            String selection = null;
+        Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
+            Uri uri = baseUri;
+            List<String> selectionParts = new ArrayList<String>();
 
             if (mId != null) {
-                uri = Uri.withAppendedPath(uri, mId.toString());
+                uri = ContentUris.withAppendedId(uri, mId);
             }
 
             if (mStatusFlags != null) {
@@ -536,9 +564,14 @@
                     parts.add("(" + statusClause(">=", 400)
                               + " AND " + statusClause("<", 600) + ")");
                 }
-                selection = joinStrings(" OR ", parts);
+                selectionParts.add(joinStrings(" OR ", parts));
             }
 
+            if (mOnlyIncludeVisibleInDownloadsUi) {
+                selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
+            }
+
+            String selection = joinStrings(" AND ", selectionParts);
             String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
             String orderBy = mOrderByColumn + " " + orderDirection;
 
@@ -565,6 +598,7 @@
 
     private ContentResolver mResolver;
     private String mPackageName;
+    private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
 
     /**
      * @hide
@@ -575,6 +609,19 @@
     }
 
     /**
+     * Makes this object access the download provider through /all_downloads URIs rather than
+     * /my_downloads URIs, for clients that have permission to do so.
+     * @hide
+     */
+    public void setAccessAllDownloads(boolean accessAllDownloads) {
+        if (accessAllDownloads) {
+            mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
+        } else {
+            mBaseUri = Downloads.Impl.CONTENT_URI;
+        }
+    }
+
+    /**
      * Enqueue a new download.  The download will start automatically once the download manager is
      * ready to execute it and connectivity is available.
      *
@@ -610,11 +657,11 @@
      * COLUMN_* constants.
      */
     public Cursor query(Query query) {
-        Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS);
+        Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS, mBaseUri);
         if (underlyingCursor == null) {
             return null;
         }
-        return new CursorTranslator(underlyingCursor);
+        return new CursorTranslator(underlyingCursor, mBaseUri);
     }
 
     /**
@@ -628,11 +675,38 @@
     }
 
     /**
+     * Restart the given download, which must have already completed (successfully or not).  This
+     * method will only work when called from within the download manager's process.
+     * @param id the ID of the download
+     * @hide
+     */
+    public void restartDownload(long id) {
+        Cursor cursor = query(new Query().setFilterById(id));
+        try {
+            if (!cursor.moveToFirst()) {
+                throw new IllegalArgumentException("No download with id " + id);
+            }
+            int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
+            if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
+                throw new IllegalArgumentException("Cannot restart incomplete download: " + id);
+            }
+        } finally {
+            cursor.close();
+        }
+
+        ContentValues values = new ContentValues();
+        values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
+        values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
+        values.putNull(Downloads.Impl._DATA);
+        values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
+        mResolver.update(getDownloadUri(id), values, null, null);
+    }
+
+    /**
      * Get the DownloadProvider URI for the download with the given ID.
      */
-    private Uri getDownloadUri(long id) {
-        Uri downloadUri = Uri.withAppendedPath(Downloads.CONTENT_URI, Long.toString(id));
-        return downloadUri;
+    Uri getDownloadUri(long id) {
+        return ContentUris.withAppendedId(mBaseUri, id);
     }
 
     /**
@@ -642,8 +716,11 @@
      * underlying data.
      */
     private static class CursorTranslator extends CursorWrapper {
-        public CursorTranslator(Cursor cursor) {
+        private Uri mBaseUri;
+
+        public CursorTranslator(Cursor cursor, Uri baseUri) {
             super(cursor);
+            mBaseUri = baseUri;
         }
 
         @Override
@@ -739,11 +816,24 @@
             }
 
             assert column.equals(COLUMN_LOCAL_URI);
-            String localUri = getUnderlyingString(Downloads._DATA);
+            return getLocalUri();
+        }
+
+        private String getLocalUri() {
+            String localUri = getUnderlyingString(Downloads.Impl._DATA);
             if (localUri == null) {
                 return null;
             }
-            return Uri.fromFile(new File(localUri)).toString();
+
+            long destinationType = getUnderlyingLong(Downloads.Impl.COLUMN_DESTINATION);
+            if (destinationType == Downloads.Impl.DESTINATION_FILE_URI) {
+                // return file URI for external download
+                return Uri.fromFile(new File(localUri)).toString();
+            }
+
+            // return content URI for cache download
+            long downloadId = getUnderlyingLong(Downloads.Impl._ID);
+            return ContentUris.withAppendedId(mBaseUri, downloadId).toString();
         }
 
         private long translateLong(String column) {
diff --git a/core/java/android/net/LinkCapabilities.aidl b/core/java/android/net/LinkCapabilities.aidl
new file mode 100644
index 0000000..5f47baf
--- /dev/null
+++ b/core/java/android/net/LinkCapabilities.aidl
@@ -0,0 +1,22 @@
+/*
+**
+** Copyright (C) 2009 Qualcomm Innovation Center, Inc.  All Rights Reserved.
+** Copyright (C) 2009 The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.net;
+
+parcelable LinkCapabilities;
+
diff --git a/core/java/android/net/LinkCapabilities.java b/core/java/android/net/LinkCapabilities.java
new file mode 100644
index 0000000..d10a759
--- /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 = "0";
+        /** 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/LinkSocket.java b/core/java/android/net/LinkSocket.java
new file mode 100644
index 0000000..d416ed0
--- /dev/null
+++ b/core/java/android/net/LinkSocket.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.
+     * @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 IOException {
+        if (DBG) log("bind(localAddr) EX throws IOException");
+        throw new IOException("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..183c767
--- /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 onCapabilityChanged(LinkSocket socket, LinkCapabilities changedCapabilities);
+}
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 63bdf8d..3df8ec0 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -57,6 +57,7 @@
     private Handler mTarget;
     private Context mContext;
     private LinkProperties mLinkProperties;
+    private LinkCapabilities mLinkCapabilities;
     private boolean mPrivateDnsRouteSet = false;
     private int mDefaultGatewayAddr = 0;
     private boolean mDefaultRouteSet = false;
@@ -231,8 +232,14 @@
                             mLinkProperties = intent.getParcelableExtra(
                                     Phone.DATA_LINK_PROPERTIES_KEY);
                             if (mLinkProperties == null) {
-                                Log.d(TAG,
-                                        "CONNECTED event did not supply link properties.");
+                                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;
@@ -517,7 +524,17 @@
         }
     }
 
+    /**
+     * @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/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 5420d8f..97c31fa 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -103,11 +103,21 @@
     public NetworkInfo getNetworkInfo();
 
     /**
-     * Fetch LinkProperties for the network
+     * 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.
      */
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 9166019..b822b27 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -49,7 +49,6 @@
 import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
 import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
 import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-import org.apache.harmony.xnet.provider.jsse.SSLParameters;
 
 /**
  * SSLSocketFactory implementation with several extra features:
@@ -211,7 +210,8 @@
     private SSLSocketFactory makeSocketFactory(TrustManager[] trustManagers) {
         try {
             OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
-            sslContext.engineInit(null, trustManagers, null, mSessionCache, null);
+            sslContext.engineInit(null, trustManagers, null);
+            sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache);
             return sslContext.engineGetSocketFactory();
         } catch (KeyManagementException e) {
             Log.wtf(TAG, e);
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index c36ad38..503c470 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -19,7 +19,7 @@
 
 import com.android.internal.net.DomainNameValidator;
 
-import org.apache.harmony.xnet.provider.jsse.SSLParameters;
+import org.apache.harmony.xnet.provider.jsse.SSLParametersImpl;
 
 import java.io.IOException;
 
@@ -187,7 +187,7 @@
         // report back to the user.
         //
         try {
-            SSLParameters.getDefaultTrustManager().checkServerTrusted(
+            SSLParametersImpl.getDefaultTrustManager().checkServerTrusted(
                 newServerCertificates, "RSA");
 
             // no errors!!!
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index cefa997..d77e9d9 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -98,7 +98,8 @@
                 }
             };
 
-            sslContext.engineInit(null, trustManagers, null, cache, null);
+            sslContext.engineInit(null, trustManagers, null);
+            sslContext.engineGetClientSessionContext().setPersistentCache(cache);
 
             synchronized (HttpsConnection.class) {
                 mSslSocketFactory = sslContext.engineGetSocketFactory();
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index d2f3d64..f0309d6 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -139,7 +139,7 @@
 
     private static final int CORE_POOL_SIZE = 5;
     private static final int MAXIMUM_POOL_SIZE = 128;
-    private static final int KEEP_ALIVE = 10;
+    private static final int KEEP_ALIVE = 1;
 
     private static final BlockingQueue<Runnable> sWorkQueue =
             new LinkedBlockingQueue<Runnable>(10);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index f5b1e57..f182a7a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -404,6 +404,7 @@
         
         public static final byte CMD_UPDATE = 0;
         public static final byte CMD_START = 1;
+        public static final byte CMD_OVERFLOW = 2;
         
         public byte cmd;
         
@@ -1703,6 +1704,8 @@
                 pw.print(" ");
                 if (rec.cmd == HistoryItem.CMD_START) {
                     pw.println(" START");
+                } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
+                    pw.println(" *OVERFLOW*");
                 } else {
                     if (rec.batteryLevel < 10) pw.print("00");
                     else if (rec.batteryLevel < 100) pw.print("0");
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/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 01cc408..0067e940 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -17,10 +17,13 @@
 
 package android.os;
 
+import android.os.WorkSource;
+
 /** @hide */
 interface IPowerManager
 {
-    void acquireWakeLock(int flags, IBinder lock, String tag);
+    void acquireWakeLock(int flags, IBinder lock, String tag, in WorkSource ws);
+    void updateWakeLockWorkSource(IBinder lock, in WorkSource ws);
     void goToSleep(long time);
     void goToSleepWithReason(long time, int reason);
     void releaseWakeLock(IBinder lock, int flags);
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 75dfdd2..e89f7c2 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -225,8 +225,8 @@
                 msg.next = prev.next;
                 prev.next = msg;
             }
-            nativeWake();
         }
+        nativeWake();
         return true;
     }
 
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index f4ca8bc..3876a3e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -209,6 +209,7 @@
         int mCount = 0;
         boolean mRefCounted = true;
         boolean mHeld = false;
+        WorkSource mWorkSource;
 
         WakeLock(int flags, String tag)
         {
@@ -247,7 +248,7 @@
             synchronized (mToken) {
                 if (!mRefCounted || mCount++ == 0) {
                     try {
-                        mService.acquireWakeLock(mFlags, mToken, mTag);
+                        mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource);
                     } catch (RemoteException e) {
                     }
                     mHeld = true;
@@ -313,6 +314,32 @@
             }
         }
 
+        public void setWorkSource(WorkSource ws) {
+            synchronized (mToken) {
+                if (ws != null && ws.size() == 0) {
+                    ws = null;
+                }
+                boolean changed = true;
+                if (ws == null) {
+                    mWorkSource = null;
+                } else if (mWorkSource == null) {
+                    changed = mWorkSource != null;
+                    mWorkSource = new WorkSource(ws);
+                } else {
+                    changed = mWorkSource.diff(ws);
+                    if (changed) {
+                        mWorkSource.set(ws);
+                    }
+                }
+                if (changed && mHeld) {
+                    try {
+                        mService.updateWakeLockWorkSource(mToken, mWorkSource);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+
         public String toString() {
             synchronized (mToken) {
                 return "WakeLock{"
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 7f7b02b..f571c42 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -201,6 +201,25 @@
     }
 
     /**
+     * Enable DropBox logging for debug phone builds.
+     *
+     * @hide
+     */
+    public static boolean conditionallyEnableDebugLogging() {
+        // For debug builds, log event loop stalls to dropbox for analysis.
+        // Similar logic also appears in ActivityThread.java for system apps.
+        if ("user".equals(Build.TYPE)) {
+            return false;
+        }
+        StrictMode.setThreadPolicy(
+            StrictMode.DISALLOW_DISK_WRITE |
+            StrictMode.DISALLOW_DISK_READ |
+            StrictMode.DISALLOW_NETWORK |
+            StrictMode.PENALTY_DROPBOX);
+        return true;
+    }
+
+    /**
      * Parses the BlockGuard policy mask out from the Exception's
      * getMessage() String value.  Kinda gross, but least
      * invasive.  :/
diff --git a/core/java/android/os/WorkSource.aidl b/core/java/android/os/WorkSource.aidl
new file mode 100644
index 0000000..1e7fabc
--- /dev/null
+++ b/core/java/android/os/WorkSource.aidl
@@ -0,0 +1,18 @@
+/* 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.os;
+
+parcelable WorkSource;
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
new file mode 100644
index 0000000..287c136
--- /dev/null
+++ b/core/java/android/os/WorkSource.java
@@ -0,0 +1,311 @@
+package android.os;
+
+/**
+ * Describes the source of some work that may be done by someone else.
+ * Currently the public representation of what a work source is is not
+ * defined; this is an opaque container.
+ */
+public class WorkSource implements Parcelable {
+    int mNum;
+    int[] mUids;
+
+    /**
+     * Internal statics to avoid object allocations in some operations.
+     * The WorkSource object itself is not thread safe, but we need to
+     * hold sTmpWorkSource lock while working with these statics.
+     */
+    static final WorkSource sTmpWorkSource = new WorkSource(0);
+    /**
+     * For returning newbie work from a modification operation.
+     */
+    static WorkSource sNewbWork;
+    /**
+     * For returning gone work form a modification operation.
+     */
+    static WorkSource sGoneWork;
+
+    /**
+     * Create an empty work source.
+     */
+    public WorkSource() {
+        mNum = 0;
+    }
+
+    /**
+     * Create a new WorkSource that is a copy of an existing one.
+     * If <var>orig</var> is null, an empty WorkSource is created.
+     */
+    public WorkSource(WorkSource orig) {
+        if (orig == null) {
+            mNum = 0;
+            return;
+        }
+        mNum = orig.mNum;
+        if (orig.mUids != null) {
+            mUids = orig.mUids.clone();
+        } else {
+            mUids = null;
+        }
+    }
+
+    /** @hide */
+    public WorkSource(int uid) {
+        mNum = 1;
+        mUids = new int[] { uid, 0 };
+    }
+
+    WorkSource(Parcel in) {
+        mNum = in.readInt();
+        mUids = in.createIntArray();
+    }
+
+    /** @hide */
+    public int size() {
+        return mNum;
+    }
+
+    /** @hide */
+    public int get(int index) {
+        return mUids[index];
+    }
+
+    /**
+     * Clear this WorkSource to be empty.
+     */
+    public void clear() {
+        mNum = 0;
+    }
+
+    /**
+     * Compare this WorkSource with another.
+     * @param other The WorkSource to compare against.
+     * @return If there is a difference, true is returned.
+     */
+    public boolean diff(WorkSource other) {
+        int N = mNum;
+        if (N != other.mNum) {
+            return true;
+        }
+        final int[] uids1 = mUids;
+        final int[] uids2 = other.mUids;
+        for (int i=0; i<N; i++) {
+            if (uids1[i] != uids2[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Replace the current contents of this work source with the given
+     * work source.  If <var>other</var> is null, the current work source
+     * will be made empty.
+     */
+    public void set(WorkSource other) {
+        if (other == null) {
+            mNum = 0;
+            return;
+        }
+        mNum = other.mNum;
+        if (other.mUids != null) {
+            if (mUids != null && mUids.length >= mNum) {
+                System.arraycopy(other.mUids, 0, mUids, 0, mNum);
+            } else {
+                mUids = other.mUids.clone();
+            }
+        } else {
+            mUids = null;
+        }
+    }
+
+    /** @hide */
+    public void set(int uid) {
+        mNum = 1;
+        if (mUids == null) mUids = new int[2];
+        mUids[0] = uid;
+    }
+
+    /** @hide */
+    public WorkSource[] setReturningDiffs(WorkSource other) {
+        synchronized (sTmpWorkSource) {
+            sNewbWork = null;
+            sGoneWork = null;
+            updateLocked(other, true, true);
+            if (sNewbWork != null || sGoneWork != null) {
+                WorkSource[] diffs = new WorkSource[2];
+                diffs[0] = sNewbWork;
+                diffs[1] = sGoneWork;
+                return diffs;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Merge the contents of <var>other</var> WorkSource in to this one.
+     *
+     * @param other The other WorkSource whose contents are to be merged.
+     * @return Returns true if any new sources were added.
+     */
+    public boolean add(WorkSource other) {
+        synchronized (sTmpWorkSource) {
+            return updateLocked(other, false, false);
+        }
+    }
+
+    /** @hide */
+    public WorkSource addReturningNewbs(WorkSource other) {
+        synchronized (sTmpWorkSource) {
+            sNewbWork = null;
+            updateLocked(other, false, true);
+            return sNewbWork;
+        }
+    }
+
+    /** @hide */
+    public boolean add(int uid) {
+        synchronized (sTmpWorkSource) {
+            sTmpWorkSource.mUids[0] = uid;
+            return updateLocked(sTmpWorkSource, false, false);
+        }
+    }
+
+    /** @hide */
+    public WorkSource addReturningNewbs(int uid) {
+        synchronized (sTmpWorkSource) {
+            sNewbWork = null;
+            sTmpWorkSource.mUids[0] = uid;
+            updateLocked(sTmpWorkSource, false, true);
+            return sNewbWork;
+        }
+    }
+
+    public boolean remove(WorkSource other) {
+        int N1 = mNum;
+        final int[] uids1 = mUids;
+        final int N2 = other.mNum;
+        final int[] uids2 = other.mUids;
+        boolean changed = false;
+        int i1 = 0;
+        for (int i2=0; i2<N2 && i1<N1; i2++) {
+            if (uids2[i2] == uids1[i1]) {
+                N1--;
+                if (i1 < N1) System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
+            }
+            while (i1 < N1 && uids2[i2] > uids1[i1]) {
+                i1++;
+            }
+        }
+
+        mNum = N1;
+
+        return changed;
+    }
+
+    private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) {
+        int N1 = mNum;
+        int[] uids1 = mUids;
+        final int N2 = other.mNum;
+        final int[] uids2 = other.mUids;
+        boolean changed = false;
+        int i1 = 0;
+        for (int i2=0; i2<N2; i2++) {
+            if (i1 >= N1 || uids2[i2] < uids1[i1]) {
+                // Need to insert a new uid.
+                changed = true;
+                if (uids1 == null) {
+                    uids1 = new int[4];
+                    uids1[0] = uids2[i2];
+                } else if (i1 >= uids1.length) {
+                    int[] newuids = new int[(uids1.length*3)/2];
+                    if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1);
+                    if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1);
+                    uids1 = newuids;
+                    uids1[i1] = uids2[i2];
+                } else {
+                    if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1);
+                    uids1[i1] = uids2[i2];
+                }
+                if (returnNewbs) {
+                    if (sNewbWork == null) {
+                        sNewbWork = new WorkSource(uids2[i2]);
+                    } else {
+                        sNewbWork.addLocked(uids2[i2]);
+                    }
+                }
+                N1++;
+                i1++;
+            } else {
+                if (!set) {
+                    // Skip uids that already exist or are not in 'other'.
+                    do {
+                        i1++;
+                    } while (i1 < N1 && uids2[i2] >= uids1[i1]);
+                } else {
+                    // Remove any uids that don't exist in 'other'.
+                    int start = i1;
+                    while (i1 < N1 && uids2[i2] > uids1[i1]) {
+                        if (sGoneWork == null) {
+                            sGoneWork = new WorkSource(uids1[i1]);
+                        } else {
+                            sGoneWork.addLocked(uids1[i1]);
+                        }
+                        i1++;
+                    }
+                    if (start < i1) {
+                        System.arraycopy(uids1, i1, uids1, start, i1-start);
+                        N1 -= i1-start;
+                        i1 = start;
+                    }
+                    // If there is a matching uid, skip it.
+                    if (i1 < N1 && uids2[i1] == uids1[i1]) {
+                        i1++;
+                    }
+                }
+            }
+        }
+
+        mNum = N1;
+        mUids = uids1;
+
+        return changed;
+    }
+
+    private void addLocked(int uid) {
+        if (mUids == null) {
+            mUids = new int[4];
+            mUids[0] = uid;
+            mNum = 1;
+            return;
+        }
+        if (mNum >= mUids.length) {
+            int[] newuids = new int[(mNum*3)/2];
+            System.arraycopy(mUids, 0, newuids, 0, mNum);
+            mUids = newuids;
+        }
+
+        mUids[mNum] = uid;
+        mNum++;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mNum);
+        dest.writeIntArray(mUids);
+    }
+
+    public static final Parcelable.Creator<WorkSource> CREATOR
+            = new Parcelable.Creator<WorkSource>() {
+        public WorkSource createFromParcel(Parcel in) {
+            return new WorkSource(in);
+        }
+        public WorkSource[] newArray(int size) {
+            return new WorkSource[size];
+        }
+    };
+}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index c28ccd3..e78d2af 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -22,6 +22,7 @@
 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;
@@ -29,10 +30,11 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Xml;
@@ -40,6 +42,7 @@
 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;
@@ -112,7 +115,10 @@
         PreferenceFragment.OnPreferenceStartFragmentCallback {
     private static final String TAG = "PreferenceActivity";
 
-    private static final String PREFERENCES_TAG = "android:preferences";
+    // 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
@@ -163,8 +169,12 @@
 
     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;
@@ -180,23 +190,35 @@
      */
     private static final int FIRST_REQUEST_CODE = 100;
 
-    private static final int MSG_BIND_PREFERENCES = 0;
-    private static final int MSG_BUILD_HEADERS = 1;
+    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;
-                case MSG_BUILD_HEADERS:
+                } 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) {
-                        switchToHeader(header.fragment, header.fragmentArguments);
+                        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;
+                } break;
             }
         }
     };
@@ -221,7 +243,7 @@
             View view;
 
             if (convertView == null) {
-                view = mInflater.inflate(com.android.internal.R.layout.preference_list_item,
+                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);
@@ -235,13 +257,7 @@
 
             // All view fields must be updated every time, because the view may be recycled 
             Header header = getItem(position);
-            if (header.icon == null) {
-                holder.icon.setImageDrawable(null);
-                holder.icon.setImageResource(header.iconRes);
-            } else {
-                holder.icon.setImageResource(0);
-                holder.icon.setImageDrawable(header.icon);
-            }
+            holder.icon.setImageResource(header.iconRes);
             holder.title.setText(header.title);
             if (TextUtils.isEmpty(header.summary)) {
                 holder.summary.setVisibility(View.GONE);
@@ -255,9 +271,24 @@
     }
 
     /**
+     * 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 class Header {
+    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
@@ -271,18 +302,24 @@
         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;
 
         /**
-         * Optional icon drawable to show for this header.  (If this is non-null,
-         * the iconRes will be ignored.)
-         */
-        public Drawable icon;
-
-        /**
          * Full class name of the fragment to display when this header is
          * selected.
          * @attr ref android.R.styleable#PreferenceHeader_fragment
@@ -299,6 +336,66 @@
          * 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
@@ -314,40 +411,68 @@
         String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
         Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
 
-        if (initialFragment != null && mSinglePane) {
-            // If we are just showing a fragment, we want to run in
-            // new fragment mode, but don't need to compute and show
-            // the headers.
-            getListView().setVisibility(View.GONE);
-            mPrefsContainer.setVisibility(View.VISIBLE);
-            switchToHeader(initialFragment, initialArguments);
+        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 {
-            // We need to try to build the headers.
-            onBuildHeaders(mHeaders);
+            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);
 
-            // If there are headers, then at this point we need to show
-            // them and, depending on the screen, we may also show in-line
-            // the currently selected preference fragment.
-            if (mHeaders.size() > 0) {
-                mAdapter = new HeaderAdapter(this, mHeaders);
-                setListAdapter(mAdapter);
-                if (!mSinglePane) {
-                    mPrefsContainer.setVisibility(View.VISIBLE);
-                    if (initialFragment == null) {
-                        Header h = onGetInitialHeader();
-                        initialFragment = h.fragment;
-                        initialArguments = h.fragmentArguments;
+            } 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);
+                        }
                     }
-                    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.
+            getListView().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.
-            } else {
-                mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
-                mPreferenceManager.setOnPreferenceTreeClickListener(this);
-            }
+            mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
+            mPreferenceManager.setOnPreferenceTreeClickListener(this);
         }
 
         getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
@@ -429,7 +554,11 @@
     public boolean onIsMultiPane() {
         Configuration config = getResources().getConfiguration();
         if ((config.screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK)
-                == Configuration.SCREENLAYOUT_SIZE_XLARGE
+                == Configuration.SCREENLAYOUT_SIZE_XLARGE) {
+            return true;
+        }
+        if ((config.screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK)
+                == Configuration.SCREENLAYOUT_SIZE_LARGE
                 && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
             return true;
         }
@@ -534,10 +663,17 @@
 
                     TypedArray sa = getResources().obtainAttributes(attrs,
                             com.android.internal.R.styleable.PreferenceHeader);
+                    header.id = sa.getInt(
+                            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(
@@ -621,6 +757,16 @@
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
 
+        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) {
@@ -680,7 +826,7 @@
     /**
      * Called when the user selects an item in the header list.  The default
      * implementation will call either {@link #startWithFragment(String, Bundle)}
-     * or {@link #switchToHeader(String, Bundle)} as appropriate.
+     * or {@link #switchToHeader(Header)} as appropriate.
      *
      * @param header The header that was selected.
      * @param position The header's position in the list.
@@ -690,7 +836,7 @@
             if (mSinglePane) {
                 startWithFragment(header.fragment, header.fragmentArguments);
             } else {
-                switchToHeader(header.fragment, header.fragmentArguments);
+                switchToHeader(header);
             }
         } else if (header.intent != null) {
             startActivity(header.intent);
@@ -716,6 +862,45 @@
     }
 
     /**
+     * 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) {
+        getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
+        Fragment f = Fragment.instantiate(this, fragmentName, args);
+        getFragmentManager().openTransaction().replace(
+                com.android.internal.R.id.prefs, f).commit();
+    }
+
+    /**
      * When in two-pane mode, switch the fragment pane to show the given
      * preference fragment.
      *
@@ -723,11 +908,64 @@
      * @param args Optional arguments to supply to the fragment.
      */
     public void switchToHeader(String fragmentName, Bundle args) {
-        getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
+        setSelectedHeader(null);
+        switchToHeaderInner(fragmentName, args);
+    }
 
-        Fragment f = Fragment.instantiate(this, fragmentName, args);
-        getFragmentManager().openTransaction().replace(
-                com.android.internal.R.id.prefs, f).commit();
+    /**
+     * 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);
+        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;
     }
 
     /**
@@ -739,17 +977,32 @@
      */
     public void startPreferenceFragment(Fragment fragment, boolean push) {
         FragmentTransaction transaction = getFragmentManager().openTransaction();
-        transaction.replace(com.android.internal.R.id.prefs, fragment);
+        startPreferenceFragment(fragment, transaction);
         if (push) {
             transaction.addToBackStack(BACK_STACK_PREFS);
         }
         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());
-        startPreferenceFragment(f, true);
+        FragmentTransaction transaction = getFragmentManager().openTransaction();
+        startPreferenceFragment(f, transaction);
+        transaction.setBreadCrumbTitle(pref.getTitle());
+        transaction.addToBackStack(BACK_STACK_PREFS);
+        transaction.commit();
         return true;
     }
 
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index 0b5dffe..479497a 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -106,7 +106,7 @@
      */
     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 Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index cb6e18f..14d6485 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -469,7 +469,7 @@
                     iconDb.releaseIconForPageUrl(cursor.getString(0));
                 } while (cursor.moveToNext());
 
-                cr.delete(BOOKMARKS_URI, whereClause, null);
+                cr.delete(History.CONTENT_URI, whereClause, null);
             }
         } catch (IllegalStateException e) {
             Log.e(LOGTAG, "deleteHistoryWhere", e);
diff --git a/core/java/android/provider/BrowserContract.java b/core/java/android/provider/BrowserContract.java
index f8a2a13..276bddc 100644
--- a/core/java/android/provider/BrowserContract.java
+++ b/core/java/android/provider/BrowserContract.java
@@ -47,6 +47,12 @@
     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.
@@ -122,21 +128,13 @@
         public static final String DIRTY = "dirty";
 
         /**
-         * 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";
-
-        /**
          * 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 BookmarkColumns {
+    interface CommonColumns {
         /**
          * The unique ID for a row.
          * <P>Type: INTEGER (long)</P>
@@ -156,6 +154,15 @@
         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>
@@ -178,10 +185,26 @@
         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 BookmarkColumns, SyncColumns {
+    public static final class Bookmarks implements CommonColumns, ImageColumns, SyncColumns {
         /**
          * This utility class cannot be instantiated.
          */
@@ -199,6 +222,16 @@
                 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
          */
@@ -237,6 +270,12 @@
         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>
@@ -251,6 +290,14 @@
         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.
@@ -261,9 +308,34 @@
     }
 
     /**
+     * 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 BookmarkColumns {
+    public static final class History implements CommonColumns, HistoryColumns, ImageColumns {
         /**
          * This utility class cannot be instantiated.
          */
@@ -283,26 +355,6 @@
          * 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 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 date the item created, in milliseconds since the epoch.
-         * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p>
-         */
-        public static final String DATE_CREATED = "created";
-
-        /**
-         * 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";
     }
 
     /**
@@ -396,4 +448,49 @@
             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";
+    }
 }
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index a6394e5..88ce0f0 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -1127,6 +1127,80 @@
     }
 
     /**
+     * CalendarCache stores some settings for calendar including the current
+     * time zone for the app. These settings are stored using a key/value
+     * scheme.
+     */
+    public interface CalendarCacheColumns {
+        /**
+         * The key for the setting. Keys are defined in CalendarChache in the
+         * Calendar provider.
+         * TODO Add keys to this file
+         */
+        public static final String KEY = "key";
+
+        /**
+         * The value of the given setting.
+         */
+        public static final String VALUE = "value";
+    }
+
+    public static class CalendarCache implements CalendarCacheColumns {
+        /**
+         * The URI to use for retrieving the properties from the Calendar db.
+         */
+        public static final Uri URI =
+                Uri.parse("content://" + AUTHORITY + "/properties");
+        public static final String[] POJECTION = { KEY, VALUE };
+
+        /**
+         * If updating a property, this must be provided as the selection. All
+         * other selections will fail. For queries this field can be omitted to
+         * retrieve all properties or used to query a single property. Valid
+         * keys include {@link #TIMEZONE_KEY_TYPE},
+         * {@link #TIMEZONE_KEY_INSTANCES}, and
+         * {@link #TIMEZONE_KEY_INSTANCES_PREVIOUS}, though the last one can
+         * only be read, not written.
+         */
+        public static final String WHERE = "key=?";
+
+        /**
+         * They key for updating the use of auto/home time zones in Calendar.
+         * Valid values are {@link #TIMEZONE_TYPE_AUTO} or
+         * {@link #TIMEZONE_TYPE_HOME}.
+         */
+        public static final String TIMEZONE_KEY_TYPE = "timezoneType";
+
+        /**
+         * The key for updating the time zone used by the provider when it
+         * generates the instances table. This should only be written if the
+         * type is set to {@link #TIMEZONE_TYPE_HOME}. A valid time zone id
+         * should be written to this field.
+         */
+        public static final String TIMEZONE_KEY_INSTANCES = "timezoneInstances";
+
+        /**
+         * The key for reading the last time zone set by the user. This should
+         * only be read by apps and it will be automatically updated whenever
+         * {@link #TIMEZONE_KEY_INSTANCES} is updated with
+         * {@link #TIMEZONE_TYPE_HOME} set.
+         */
+        public static final String TIMEZONE_KEY_INSTANCES_PREVIOUS = "timezoneInstancesPrevious";
+
+        /**
+         * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider
+         * should stay in sync with the device's time zone.
+         */
+        public static final String TIMEZONE_TYPE_AUTO = "auto";
+
+        /**
+         * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider
+         * should use a fixed time zone set by the user.
+         */
+        public static final String TIMEZONE_TYPE_HOME = "home";
+    }
+
+    /**
      * A few Calendar globals are needed in the CalendarProvider for expanding
      * the Instances table and these are all stored in the first (and only)
      * row of the CalendarMetaData table.
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index ecd9fe9..b2c1c2d 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -4020,7 +4020,7 @@
          * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li>
          * <li>{@link #TYPE_DEFAULT}</li>
          * <li>{@link #TYPE_OTHER_NAME}</li>
-         * <li>{@link #TYPE_MAINDEN_NAME}</li>
+         * <li>{@link #TYPE_MAIDEN_NAME}</li>
          * <li>{@link #TYPE_SHORT_NAME}</li>
          * <li>{@link #TYPE_INITIALS}</li>
          * </ul>
@@ -4046,6 +4046,9 @@
 
             public static final int TYPE_DEFAULT = 1;
             public static final int TYPE_OTHER_NAME = 2;
+            public static final int TYPE_MAIDEN_NAME = 3;
+            /** @deprecated Use TYPE_MAIDEN_NAME instead. */
+            @Deprecated
             public static final int TYPE_MAINDEN_NAME = 3;
             public static final int TYPE_SHORT_NAME = 4;
             public static final int TYPE_INITIALS = 5;
@@ -5357,17 +5360,30 @@
          * <td>{@link #DATA1}</td>
          * <td></td>
          * </tr>
+         * <tr>
+         * <td>int</td>
+         * <td>{@link #TYPE}</td>
+         * <td>{@link #DATA2}</td>
+         * <td>Allowed values are:
+         * <p>
+         * <ul>
+         * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li>
+         * <li>{@link #TYPE_HOME}</li>
+         * <li>{@link #TYPE_WORK}</li>
+         * <li>{@link #TYPE_OTHER}</li>
+         * </ul>
+         * </p>
+         * </td>
+         * </tr>
+         * <tr>
+         * <td>String</td>
+         * <td>{@link #LABEL}</td>
+         * <td>{@link #DATA3}</td>
+         * <td></td>
+         * </tr>
          * </table>
          */
-        public static final class SipAddress implements DataColumnsWithJoins {
-            // TODO: Ultimately this class will probably implement
-            // CommonColumns too (in addition to DataColumnsWithJoins)
-            // since it may make sense to have multiple SIP addresses with
-            // different types+labels, just like with phone numbers.
-            //
-            // But that can be extended in the future without breaking any
-            // public API, so let's keep this class ultra-simple for now.
-
+        public static final class SipAddress implements DataColumnsWithJoins, CommonColumns {
             /**
              * This utility class cannot be instantiated
              */
@@ -5376,11 +5392,44 @@
             /** MIME type used when storing this in data table. */
             public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
 
+            public static final int TYPE_HOME = 1;
+            public static final int TYPE_WORK = 2;
+            public static final int TYPE_OTHER = 3;
+
             /**
              * The SIP address.
              * <P>Type: TEXT</P>
              */
             public static final String SIP_ADDRESS = DATA1;
+            // ...and TYPE and LABEL come from the CommonColumns interface.
+
+            /**
+             * Return the string resource that best describes the given
+             * {@link #TYPE}. Will always return a valid resource.
+             */
+            public static final int getTypeLabelResource(int type) {
+                switch (type) {
+                    case TYPE_HOME: return com.android.internal.R.string.sipAddressTypeHome;
+                    case TYPE_WORK: return com.android.internal.R.string.sipAddressTypeWork;
+                    case TYPE_OTHER: return com.android.internal.R.string.sipAddressTypeOther;
+                    default: return com.android.internal.R.string.sipAddressTypeCustom;
+                }
+            }
+
+            /**
+             * Return a {@link CharSequence} that best describes the given type,
+             * possibly substituting the given {@link #LABEL} value
+             * for {@link #TYPE_CUSTOM}.
+             */
+            public static final CharSequence getTypeLabel(Resources res, int type,
+                    CharSequence label) {
+                if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) {
+                    return label;
+                } else {
+                    final int labelRes = getTypeLabelResource(type);
+                    return res.getText(labelRes);
+                }
+            }
         }
     }
 
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 6bf0d5b..74c7372 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -60,7 +60,7 @@
      * @hide
      */
     public static final Uri CONTENT_URI =
-        Uri.parse("content://downloads/download");
+        Uri.parse("content://downloads/my_downloads");
 
     /**
      * Broadcast Action: this is sent by the download manager to the app
@@ -637,10 +637,17 @@
                 "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION";
 
         /**
-         * The content:// URI for the data table in the provider
+         * The content:// URI to access downloads owned by the caller's UID.
          */
         public static final Uri CONTENT_URI =
-            Uri.parse("content://downloads/download");
+                Uri.parse("content://downloads/my_downloads");
+
+        /**
+         * The content URI for accessing all downloads across all UIDs (requires the
+         * ACCESS_ALL_DOWNLOADS permission).
+         */
+        public static final Uri ALL_DOWNLOADS_CONTENT_URI =
+                Uri.parse("content://downloads/all_downloads");
 
         /**
          * Broadcast Action: this is sent by the download manager to the app
@@ -880,6 +887,14 @@
          */
         public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types";
 
+        /**
+         * Whether or not this download should be displayed in the system's Downloads UI.  Defaults
+         * to true.
+         * <P>Type: INTEGER</P>
+         * <P>Owner can Init/Read</P>
+         */
+        public static final String COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI = "is_visible_in_downloads_ui";
+
         /*
          * Lists the destinations that an application can specify for a download.
          */
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index d3718f8..1417ef5 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -339,6 +339,18 @@
              */
             public static final String MEDIA_ID = "media_id";
         }
+
+        /**
+         * 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";
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 46bde37..5b4aedb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -34,7 +34,10 @@
 import android.database.Cursor;
 import android.database.SQLException;
 import android.net.Uri;
-import android.os.*;
+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;
@@ -1125,6 +1128,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>
@@ -2527,6 +2531,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,
@@ -3497,6 +3555,7 @@
             PARENTAL_CONTROL_REDIRECT_URL,
             USB_MASS_STORAGE_ENABLED,
             ACCESSIBILITY_ENABLED,
+            ACCESSIBILITY_SCRIPT_INJECTION,
             BACKUP_AUTO_RESTORE,
             ENABLED_ACCESSIBILITY_SERVICES,
             TTS_USE_DEFAULTS,
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index fde3769..ab79aaf 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -59,22 +59,14 @@
     // from remote device when Android is in Suspend state.
     private PowerManager.WakeLock mWakeLock;
 
-    private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
-    private static final int EVENT_RESTART_BLUETOOTH = 2;
-    private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3;
-    private static final int EVENT_AGENT_CANCEL = 4;
+    private static final int EVENT_RESTART_BLUETOOTH = 1;
+    private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 2;
+    private static final int EVENT_AGENT_CANCEL = 3;
 
     private static final int CREATE_DEVICE_ALREADY_EXISTS = 1;
     private static final int CREATE_DEVICE_SUCCESS = 0;
     private static final int CREATE_DEVICE_FAILED = -1;
 
-    // The time (in millisecs) to delay the pairing attempt after the first
-    // auto pairing attempt fails. We use an exponential delay with
-    // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
-    // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
-    private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
-    private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
-
     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
 
@@ -83,13 +75,6 @@
         public void handleMessage(Message msg) {
             String address = null;
             switch (msg.what) {
-            case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
-                address = (String)msg.obj;
-                if (address != null) {
-                    mBluetoothService.createBond(address);
-                    return;
-                }
-                break;
             case EVENT_RESTART_BLUETOOTH:
                 mBluetoothService.restart();
                 break;
@@ -102,8 +87,7 @@
             case EVENT_AGENT_CANCEL:
                 // Set the Bond State to BOND_NONE.
                 // We always have only 1 device in BONDING state.
-                String[] devices =
-                    mBluetoothService.getBondState().listInState(BluetoothDevice.BOND_BONDING);
+                String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING);
                 if (devices.length == 0) {
                     break;
                 } else if (devices.length > 1) {
@@ -111,7 +95,7 @@
                     break;
                 }
                 address = devices[0];
-                mBluetoothService.getBondState().setBondState(address,
+                mBluetoothService.setBondState(address,
                         BluetoothDevice.BOND_NONE,
                         BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED);
                 break;
@@ -122,7 +106,7 @@
     static { classInitNative(); }
     private static native void classInitNative();
 
-    /* pacakge */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
+    /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
             BluetoothService bluetoothService) {
         mBluetoothService = bluetoothService;
         mContext = context;
@@ -221,55 +205,7 @@
 
     private void onCreatePairedDeviceResult(String address, int result) {
         address = address.toUpperCase();
-        if (result == BluetoothDevice.BOND_SUCCESS) {
-            mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
-            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
-                mBluetoothService.getBondState().clearPinAttempts(address);
-            }
-        } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
-                mBluetoothService.getBondState().getAttempt(address) == 1) {
-            mBluetoothService.getBondState().addAutoPairingFailure(address);
-            pairingAttempt(address, result);
-        } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
-                mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
-            pairingAttempt(address, result);
-        } else {
-            mBluetoothService.getBondState().setBondState(address,
-                                                          BluetoothDevice.BOND_NONE, result);
-            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
-                mBluetoothService.getBondState().clearPinAttempts(address);
-            }
-        }
-    }
-
-    private void pairingAttempt(String address, int result) {
-        // This happens when our initial guess of "0000" as the pass key
-        // fails. Try to create the bond again and display the pin dialog
-        // to the user. Use back-off while posting the delayed
-        // message. The initial value is
-        // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
-        // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
-        // reached, display an error to the user.
-        int attempt = mBluetoothService.getBondState().getAttempt(address);
-        if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
-                    MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
-            mBluetoothService.getBondState().clearPinAttempts(address);
-            mBluetoothService.getBondState().setBondState(address,
-                    BluetoothDevice.BOND_NONE, result);
-            return;
-        }
-
-        Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
-        message.obj = address;
-        boolean postResult =  mHandler.sendMessageDelayed(message,
-                                        attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
-        if (!postResult) {
-            mBluetoothService.getBondState().clearPinAttempts(address);
-            mBluetoothService.getBondState().setBondState(address,
-                    BluetoothDevice.BOND_NONE, result);
-            return;
-        }
-        mBluetoothService.getBondState().attempt(address);
+        mBluetoothService.onCreatePairedDeviceResult(address, result);
     }
 
     private void onDeviceCreated(String deviceObjectPath) {
@@ -287,8 +223,8 @@
     private void onDeviceRemoved(String deviceObjectPath) {
         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
         if (address != null) {
-            mBluetoothService.getBondState().setBondState(address.toUpperCase(),
-                    BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED);
+            mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE,
+                BluetoothDevice.UNBOND_REASON_REMOVED);
             mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null);
         }
     }
@@ -420,13 +356,11 @@
                 // If locally initiated pairing, we will
                 // not go to BOND_BONDED state until we have received a
                 // successful return value in onCreatePairedDeviceResult
-                if (null == mBluetoothService.getBondState().getPendingOutgoingBonding()) {
-                    mBluetoothService.getBondState().setBondState(address,
-                            BluetoothDevice.BOND_BONDED);
+                if (null == mBluetoothService.getPendingOutgoingBonding()) {
+                    mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED);
                 }
             } else {
-                mBluetoothService.getBondState().setBondState(address,
-                        BluetoothDevice.BOND_NONE);
+                mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE);
                 mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
             }
         } else if (name.equals("Trusted")) {
@@ -463,9 +397,14 @@
         }
         BluetoothDevice device = mAdapter.getRemoteDevice(address);
         if (name.equals("Connected")) {
-            int state = propValues[1].equals("true") ? BluetoothInputDevice.STATE_CONNECTED :
-                BluetoothInputDevice.STATE_DISCONNECTED;
-            mBluetoothService.handlePanDeviceStateChange(device, state);
+            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);
         }
     }
 
@@ -489,8 +428,8 @@
         // Also set it only when the state is not already Bonded, we can sometimes
         // get an authorization request from the remote end if it doesn't have the link key
         // while we still have it.
-        if (mBluetoothService.getBondState().getBondState(address) != BluetoothDevice.BOND_BONDED)
-            mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDING);
+        if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED)
+            mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING);
         return address;
     }
 
@@ -504,7 +443,7 @@
          * so we may get this request many times. Also if we respond immediately,
          * the other end is unable to handle it. Delay sending the message.
          */
-        if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDED) {
+        if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) {
             Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT);
             message.obj = address;
             mHandler.sendMessageDelayed(message, 1500);
@@ -558,7 +497,7 @@
         if (address == null) return;
 
         String pendingOutgoingAddress =
-                mBluetoothService.getBondState().getPendingOutgoingBonding();
+                mBluetoothService.getPendingOutgoingBonding();
         if (address.equals(pendingOutgoingAddress)) {
             // we initiated the bonding
 
@@ -579,12 +518,7 @@
             case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
             case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
             case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
-                if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) &&
-                    !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) {
-                    mBluetoothService.getBondState().attempt(address);
-                    mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
-                    return;
-                }
+                if (mBluetoothService.attemptAutoPair(address)) return;
            }
         }
         // Acquire wakelock during PIN code request to bring up LCD display
@@ -614,6 +548,17 @@
         mWakeLock.release();
     }
 
+    private void onRequestOobData(String objectPath , int nativeData) {
+        String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
+        if (address == null) return;
+
+        Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
+        intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
+                BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT);
+        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+    }
+
     private boolean onAgentAuthorize(String objectPath, String deviceUuid) {
         if (!mBluetoothService.isEnabled()) return false;
 
@@ -651,7 +596,7 @@
                  Log.i(TAG, "Rejecting incoming HID connection from " + address);
              }
         } else if (BluetoothUuid.isBnep(uuid) || BluetoothUuid.isNap(uuid) &&
-                mBluetoothService.isTetheringOn()){
+                mBluetoothService.allowIncomingTethering()){
             authorized = true;
         } else {
             Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
@@ -672,6 +617,19 @@
         return false;
     }
 
+    private boolean onAgentOutOfBandDataAvailable(String objectPath) {
+        if (!mBluetoothService.isEnabled()) return false;
+
+        String address = mBluetoothService.getAddressFromObjectPath(objectPath);
+        if (address == null) return false;
+
+        if (mBluetoothService.getDeviceOutOfBandData(
+            mAdapter.getRemoteDevice(address)) != null) {
+            return true;
+        }
+        return false;
+    }
+
     private boolean isOtherSinkInNonDisconnectingState(String address) {
         BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
         Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks();
@@ -772,9 +730,9 @@
         mBluetoothService.handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED);
     }
 
-    private void onNetworkDeviceConnected(String address, int destUuid) {
+    private void onNetworkDeviceConnected(String address, String iface, int destUuid) {
         BluetoothDevice device = mAdapter.getRemoteDevice(address);
-        mBluetoothService.handlePanDeviceStateChange(device, BluetoothPan.STATE_CONNECTED);
+        mBluetoothService.handlePanDeviceStateChange(device, iface, BluetoothPan.STATE_CONNECTED);
     }
 
     private void onRestartRequired() {
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 7252736..7f160c4 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -42,6 +42,7 @@
 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;
@@ -55,6 +56,7 @@
 import android.os.SystemService;
 import android.provider.Settings;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.app.IBatteryStats;
 
@@ -111,9 +113,20 @@
     private static final int MESSAGE_FINISH_DISABLE = 2;
     private static final int MESSAGE_UUID_INTENT = 3;
     private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4;
+    private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 5;
+
+    // The time (in millisecs) to delay the pairing attempt after the first
+    // auto pairing attempt fails. We use an exponential delay with
+    // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
+    // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
+    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 static final String BLUETOOTH_NEAR_IFACE_ADDR_PREFIX= "192.168.44.";
+    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
@@ -143,7 +156,9 @@
 
     private BluetoothA2dpService mA2dpService;
     private final HashMap<BluetoothDevice, Integer> mInputDevices;
-    private final HashMap<BluetoothDevice, Integer> mPanDevices;
+    private final HashMap<BluetoothDevice, Pair<Integer, String>> mPanDevices;
+    private final HashMap<String, Pair<byte[], byte[]>> mDeviceOobData;
+
 
     private static String mDockAddress;
     private String mDockPin;
@@ -200,6 +215,7 @@
         mDeviceProperties = new HashMap<String, Map<String,String>>();
 
         mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
+        mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>();
         mUuidIntentTracker = new ArrayList<String>();
         mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
         mServiceRecordToPid = new HashMap<Integer, Integer>();
@@ -208,10 +224,12 @@
         mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
         mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID);
 
-        // Can tether to up to 7 devices
-        mBluetoothIfaceAddresses = new ArrayList<String>(BluetoothPan.MAX_CONNECTIONS);
-        for (int i=1; i <= BluetoothPan.MAX_CONNECTIONS; i++) {
-            mBluetoothIfaceAddresses.add(BLUETOOTH_NEAR_IFACE_ADDR_PREFIX + i);
+        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();
@@ -224,7 +242,7 @@
         filter.addAction(Intent.ACTION_DOCK_EVENT);
         mContext.registerReceiver(mReceiver, filter);
         mInputDevices = new HashMap<BluetoothDevice, Integer>();
-        mPanDevices = new HashMap<BluetoothDevice, Integer>();
+        mPanDevices = new HashMap<BluetoothDevice, Pair<Integer, String>>();
     }
 
     public static synchronized String readDockBluetoothAddress() {
@@ -526,6 +544,13 @@
                     setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, -1);
                 }
                 break;
+            case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
+                address = (String)msg.obj;
+                if (address != null) {
+                    createBond(address);
+                    return;
+                }
+                break;
             }
         }
     };
@@ -619,8 +644,68 @@
         Binder.restoreCallingIdentity(origCallerIdentityToken);
     }
 
-    /* package */ BondState getBondState() {
-        return mBondState;
+    /*package*/ synchronized boolean attemptAutoPair(String address) {
+        if (!mBondState.hasAutoPairingFailed(address) &&
+                !mBondState.isAutoPairingBlacklisted(address)) {
+            mBondState.attempt(address);
+            setPin(address, BluetoothDevice.convertPinToBytes("0000"));
+            return true;
+        }
+        return false;
+    }
+
+    /*package*/ synchronized void onCreatePairedDeviceResult(String address, int result) {
+        if (result == BluetoothDevice.BOND_SUCCESS) {
+            setBondState(address, BluetoothDevice.BOND_BONDED);
+            if (mBondState.isAutoPairingAttemptsInProgress(address)) {
+                mBondState.clearPinAttempts(address);
+            }
+        } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
+                mBondState.getAttempt(address) == 1) {
+            mBondState.addAutoPairingFailure(address);
+            pairingAttempt(address, result);
+        } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
+              mBondState.isAutoPairingAttemptsInProgress(address)) {
+            pairingAttempt(address, result);
+        } else {
+            setBondState(address, BluetoothDevice.BOND_NONE, result);
+            if (mBondState.isAutoPairingAttemptsInProgress(address)) {
+                mBondState.clearPinAttempts(address);
+            }
+        }
+    }
+
+    /*package*/ synchronized String getPendingOutgoingBonding() {
+        return mBondState.getPendingOutgoingBonding();
+    }
+
+    private void pairingAttempt(String address, int result) {
+        // This happens when our initial guess of "0000" as the pass key
+        // fails. Try to create the bond again and display the pin dialog
+        // to the user. Use back-off while posting the delayed
+        // message. The initial value is
+        // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
+        // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
+        // reached, display an error to the user.
+        int attempt = mBondState.getAttempt(address);
+        if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
+                    MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
+            mBondState.clearPinAttempts(address);
+            setBondState(address, BluetoothDevice.BOND_NONE, result);
+            return;
+        }
+
+        Message message = mHandler.obtainMessage(MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+        message.obj = address;
+        boolean postResult =  mHandler.sendMessageDelayed(message,
+                                        attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+        if (!postResult) {
+            mBondState.clearPinAttempts(address);
+            setBondState(address,
+                    BluetoothDevice.BOND_NONE, result);
+            return;
+        }
+        mBondState.attempt(address);
     }
 
     /** local cache of bonding state.
@@ -1155,7 +1240,7 @@
         mIsDiscovering = isDiscovering;
     }
 
-    public synchronized boolean createBond(String address) {
+    private boolean isBondingFeasible(String address) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                 "Need BLUETOOTH_ADMIN permission");
         if (!isEnabledInternal()) return false;
@@ -1185,8 +1270,13 @@
                 return false;
             }
         }
+        return true;
+    }
 
-        if (!createPairedDeviceNative(address, 60000 /* 1 minute */)) {
+    public synchronized boolean createBond(String address) {
+        if (!isBondingFeasible(address)) return false;
+
+        if (!createPairedDeviceNative(address, 60000  /*1 minute*/ )) {
             return false;
         }
 
@@ -1196,6 +1286,51 @@
         return true;
     }
 
+    public synchronized boolean createBondOutOfBand(String address, byte[] hash,
+                                                    byte[] randomizer) {
+        if (!isBondingFeasible(address)) return false;
+
+        if (!createPairedDeviceOutOfBandNative(address, 60000 /* 1 minute */)) {
+            return false;
+        }
+
+        setDeviceOutOfBandData(address, hash, randomizer);
+        mBondState.setPendingOutgoingBonding(address);
+        mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
+
+        return true;
+    }
+
+    public synchronized boolean setDeviceOutOfBandData(String address, byte[] hash,
+            byte[] randomizer) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (!isEnabledInternal()) return false;
+
+        Pair <byte[], byte[]> value = new Pair<byte[], byte[]>(hash, randomizer);
+
+        if (DBG) {
+            log("Setting out of band data for:" + address + ":" +
+              Arrays.toString(hash) + ":" + Arrays.toString(randomizer));
+        }
+
+        mDeviceOobData.put(address, value);
+        return true;
+    }
+
+    Pair<byte[], byte[]> getDeviceOutOfBandData(BluetoothDevice device) {
+        return mDeviceOobData.get(device.getAddress());
+    }
+
+
+    public synchronized byte[] readOutOfBandData() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                                "Need BLUETOOTH permission");
+        if (!isEnabledInternal()) return null;
+
+        return readAdapterOutOfBandDataNative();
+    }
+
     public synchronized boolean cancelBondProcess(String address) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                 "Need BLUETOOTH_ADMIN permission");
@@ -1241,6 +1376,10 @@
         return mBondState.listInState(BluetoothDevice.BOND_BONDED);
     }
 
+    /*package*/ synchronized String[] listInState(int state) {
+      return mBondState.listInState(state);
+    }
+
     public synchronized int getBondState(String address) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
@@ -1249,6 +1388,15 @@
         return mBondState.getBondState(address.toUpperCase());
     }
 
+    /*package*/ synchronized boolean setBondState(String address, int state) {
+        return setBondState(address, state, 0);
+    }
+
+    /*package*/ synchronized boolean setBondState(String address, int state, int reason) {
+        mBondState.setBondState(address.toUpperCase(), state);
+        return true;
+    }
+
     public synchronized boolean isBluetoothDock(String address) {
         SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
                 mContext.MODE_PRIVATE);
@@ -1260,6 +1408,12 @@
         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) {
@@ -1289,10 +1443,11 @@
     public synchronized int getPanDeviceState(BluetoothDevice device) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (mPanDevices.get(device) == null) {
+        Pair<Integer, String> panDevice = mPanDevices.get(device);
+        if (panDevice == null) {
             return BluetoothPan.STATE_DISCONNECTED;
         }
-        return mPanDevices.get(device);
+        return panDevice.first;
     }
 
     public synchronized boolean connectPanDevice(BluetoothDevice device) {
@@ -1306,8 +1461,8 @@
         }
 
         int connectedCount = 0;
-        for (BluetoothDevice BTdevice: mPanDevices.keySet()) {
-            if (getPanDeviceState(BTdevice) == BluetoothPan.STATE_CONNECTED) {
+        for (BluetoothDevice panDevice: mPanDevices.keySet()) {
+            if (getPanDeviceState(panDevice) == BluetoothPan.STATE_CONNECTED) {
                 connectedCount ++;
             }
         }
@@ -1366,21 +1521,33 @@
         return disconnectPanDeviceNative(objectPath);
     }
 
-    /*package*/ void handlePanDeviceStateChange(BluetoothDevice device, int state) {
+    /*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);
+            prevState = mPanDevices.get(device).first;
+            ifaceAddr = mPanDevices.get(device).second;
         }
         if (prevState == state) return;
 
-        mPanDevices.put(device, state);
-
         if (state == BluetoothPan.STATE_CONNECTED) {
-            updateTetherState(true);
+            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);
@@ -1388,12 +1555,35 @@
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
 
         if (DBG) log("Pan Device state : device: " + device + " State:" + prevState + "->" + state);
-
     }
 
-    // configured when we start tethering and unconfig'd on error or conclusion
-    private boolean updateTetherState(boolean enabled) {
-        Log.d(TAG, "configureBluetoothIface(" + enabled + ")");
+    /*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);
@@ -1402,64 +1592,58 @@
         String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
 
         // bring toggle the interfaces
-        String[] ifaces = new String[0];
+        String[] currentIfaces = new String[0];
         try {
-            ifaces = service.listInterfaces();
+            currentIfaces = service.listInterfaces();
         } catch (Exception e) {
             Log.e(TAG, "Error listing Interfaces :" + e);
-            return false;
+            return null;
         }
 
-        ArrayList<String> ifaceAddresses = (ArrayList<String>) mBluetoothIfaceAddresses.clone();
-        for (String iface : ifaces) {
-            for (String regex : bluetoothRegexs) {
-                if (iface.matches(regex)) {
-                    InterfaceConfiguration ifcg = null;
-                    try {
-                        ifcg = service.getInterfaceConfig(iface);
-                        if (ifcg != null) {
-                            if (enabled) {
-                                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 && !ifaceAddresses.isEmpty()) {
-                                    addr = ifaceAddresses.remove(0).split("\\.");
-                                    ifcg.ipAddr = (Integer.parseInt(addr[0]) << 24) +
-                                            (Integer.parseInt(addr[1]) << 16) +
-                                            (Integer.parseInt(addr[2]) << 8) +
-                                            (Integer.parseInt(addr[3]));
-                                } else {
-                                    String IfaceAddress =
-                                            String.valueOf(ifcg.ipAddr >>> 24) + "." +
-                                            String.valueOf((ifcg.ipAddr & 0x00FF0000) >>> 16) + "." +
-                                            String.valueOf((ifcg.ipAddr & 0x0000FF00) >>> 8) + "." +
-                                            String.valueOf(ifcg.ipAddr & 0x000000FF);
-                                    ifaceAddresses.remove(IfaceAddress);
-                                }
-                                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
-                            } else {
-                                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down");
-                            }
-                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", "");
-                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("  "," ");
-                            service.setInterfaceConfig(iface, ifcg);
-                            if (enabled) {
-                                if (cm.tether(iface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
-                                    Log.e(TAG, "Error tethering "+ifaces);
-                                }
-                            }
-                        }
-                    } catch (Exception e) {
-                        Log.e(TAG, "Error configuring interface " + iface + ", :" + e);
-                        return false;
-                    }
-                }
+        boolean found = false;
+        for (String currIface: currentIfaces) {
+            if (currIface.equals(iface)) {
+                found = true;
+                break;
             }
         }
 
-        return true;
+        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) {
@@ -1954,6 +2138,32 @@
         return setPairingConfirmationNative(address, confirm, data.intValue());
     }
 
+    public synchronized boolean setRemoteOutOfBandData(String address) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (!isEnabledInternal()) return false;
+        address = address.toUpperCase();
+        Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
+        if (data == null) {
+            Log.w(TAG, "setRemoteOobData(" + address + ") called but no native data available, " +
+                  "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
+                  " or by bluez.\n");
+            return false;
+        }
+
+        Pair<byte[], byte[]> val = mDeviceOobData.get(address);
+        byte[] hash, randomizer;
+        if (val == null) {
+            // TODO: check what should be passed in this case.
+            hash = new byte[16];
+            randomizer = new byte[16];
+        } else {
+            hash = val.first;
+            randomizer = val.second;
+        }
+        return setRemoteOutOfBandDataNative(address, hash, randomizer, data.intValue());
+    }
+
     public synchronized boolean cancelPairingUserInput(String address) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                 "Need BLUETOOTH_ADMIN permission");
@@ -2487,6 +2697,9 @@
     private native boolean stopDiscoveryNative();
 
     private native boolean createPairedDeviceNative(String address, int timeout_ms);
+    private native boolean createPairedDeviceOutOfBandNative(String address, int timeout_ms);
+    private native byte[] readAdapterOutOfBandDataNative();
+
     private native boolean cancelDeviceCreationNative(String address);
     private native boolean removeDeviceNative(String objectPath);
     private native int getDeviceServiceChannelNative(String objectPath, String uuid,
@@ -2497,6 +2710,9 @@
     private native boolean setPasskeyNative(String address, int passkey, int nativeData);
     private native boolean setPairingConfirmationNative(String address, boolean confirm,
             int nativeData);
+    private native boolean setRemoteOutOfBandDataNative(String address, byte[] hash,
+                                                        byte[] randomizer, int nativeData);
+
     private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
             int value);
     private native boolean createDeviceNative(String address);
diff --git a/core/java/android/util/CalendarUtils.java b/core/java/android/util/CalendarUtils.java
new file mode 100644
index 0000000..3d340d9
--- /dev/null
+++ b/core/java/android/util/CalendarUtils.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.provider.Calendar.CalendarCache;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+
+import java.util.Formatter;
+import java.util.HashSet;
+import java.util.Locale;
+
+/**
+ * A class containing utility methods related to Calendar apps.
+ *
+ * @hide
+ */
+public class CalendarUtils {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "CalendarUtils";
+
+    /**
+     * This class contains methods specific to reading and writing time zone
+     * values.
+     */
+    public static class TimeZoneUtils {
+        private static final String[] TIMEZONE_TYPE_ARGS = { CalendarCache.TIMEZONE_KEY_TYPE };
+        private static final String[] TIMEZONE_INSTANCES_ARGS =
+                { CalendarCache.TIMEZONE_KEY_INSTANCES };
+
+        private static StringBuilder mSB = new StringBuilder(50);
+        private static Formatter mF = new Formatter(mSB, Locale.getDefault());
+        private volatile static boolean mFirstTZRequest = true;
+        private volatile static boolean mTZQueryInProgress = false;
+
+        private volatile static boolean mUseHomeTZ = false;
+        private volatile static String mHomeTZ = Time.getCurrentTimezone();
+
+        private static HashSet<Runnable> mTZCallbacks = new HashSet<Runnable>();
+        private static int mToken = 1;
+        private static AsyncTZHandler mHandler;
+
+        // The name of the shared preferences file. This name must be maintained for historical
+        // reasons, as it's what PreferenceManager assigned the first time the file was created.
+        private final String mPrefsName;
+
+        /**
+         * This is the key used for writing whether or not a home time zone should
+         * be used in the Calendar app to the Calendar Preferences.
+         */
+        public static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
+        /**
+         * This is the key used for writing the time zone that should be used if
+         * home time zones are enabled for the Calendar app.
+         */
+        public static final String KEY_HOME_TZ = "preferences_home_tz";
+
+        /**
+         * This is a helper class for handling the async queries and updates for the
+         * time zone settings in Calendar.
+         */
+        private class AsyncTZHandler extends AsyncQueryHandler {
+            public AsyncTZHandler(ContentResolver cr) {
+                super(cr);
+            }
+
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                synchronized (mTZCallbacks) {
+                    boolean writePrefs = false;
+                    // Check the values in the db
+                    int keyColumn = cursor.getColumnIndexOrThrow(CalendarCache.KEY);
+                    int valueColumn = cursor.getColumnIndexOrThrow(CalendarCache.VALUE);
+                    while(cursor.moveToNext()) {
+                        String key = cursor.getString(keyColumn);
+                        String value = cursor.getString(valueColumn);
+                        if (TextUtils.equals(key, CalendarCache.TIMEZONE_KEY_TYPE)) {
+                            boolean useHomeTZ = !TextUtils.equals(
+                                    value, CalendarCache.TIMEZONE_TYPE_AUTO);
+                            if (useHomeTZ != mUseHomeTZ) {
+                                writePrefs = true;
+                                mUseHomeTZ = useHomeTZ;
+                            }
+                        } else if (TextUtils.equals(
+                                key, CalendarCache.TIMEZONE_KEY_INSTANCES_PREVIOUS)) {
+                            if (!TextUtils.isEmpty(value) && !TextUtils.equals(mHomeTZ, value)) {
+                                writePrefs = true;
+                                mHomeTZ = value;
+                            }
+                        }
+                    }
+                    cursor.close();
+                    if (writePrefs) {
+                        SharedPreferences prefs = getSharedPreferences((Context)cookie, mPrefsName);
+                        // Write the prefs
+                        setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
+                        setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
+                    }
+
+                    mTZQueryInProgress = false;
+                    for (Runnable callback : mTZCallbacks) {
+                        if (callback != null) {
+                            callback.run();
+                        }
+                    }
+                    mTZCallbacks.clear();
+                }
+            }
+        }
+
+        /**
+         * The name of the file where the shared prefs for Calendar are stored
+         * must be provided. All activities within an app should provide the
+         * same preferences name or behavior may become erratic.
+         *
+         * @param prefsName
+         */
+        public TimeZoneUtils(String prefsName) {
+            mPrefsName = prefsName;
+        }
+
+        /**
+         * Formats a date or a time range according to the local conventions.
+         *
+         * This formats a date/time range using Calendar's time zone and the
+         * local conventions for the region of the device.
+         *
+         * @param context the context is required only if the time is shown
+         * @param startMillis the start time in UTC milliseconds
+         * @param endMillis the end time in UTC milliseconds
+         * @param flags a bit mask of options See
+         * {@link DateUtils#formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
+         * @return a string containing the formatted date/time range.
+         */
+        public String formatDateRange(Context context, long startMillis,
+                long endMillis, int flags) {
+            String date;
+            synchronized (mSB) {
+                mSB.setLength(0);
+                date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
+                        getTimeZone(context, null)).toString();
+            }
+            return date;
+        }
+
+        /**
+         * Writes a new home time zone to the db.
+         *
+         * Updates the home time zone in the db asynchronously and updates
+         * the local cache. Sending a time zone of
+         * {@link CalendarCache#TIMEZONE_TYPE_AUTO} will cause it to be set
+         * to the device's time zone. null or empty tz will be ignored.
+         *
+         * @param context The calling activity
+         * @param timeZone The time zone to set Calendar to, or
+         * {@link CalendarCache#TIMEZONE_TYPE_AUTO}
+         */
+        public void setTimeZone(Context context, String timeZone) {
+            if (TextUtils.isEmpty(timeZone)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Empty time zone, nothing to be done.");
+                }
+                return;
+            }
+            boolean updatePrefs = false;
+            synchronized (mTZCallbacks) {
+                if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timeZone)) {
+                    if (mUseHomeTZ) {
+                        updatePrefs = true;
+                    }
+                    mUseHomeTZ = false;
+                } else {
+                    if (!mUseHomeTZ || !TextUtils.equals(mHomeTZ, timeZone)) {
+                        updatePrefs = true;
+                    }
+                    mUseHomeTZ = true;
+                    mHomeTZ = timeZone;
+                }
+            }
+            if (updatePrefs) {
+                // Write the prefs
+                SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
+                setSharedPreference(prefs, KEY_HOME_TZ_ENABLED, mUseHomeTZ);
+                setSharedPreference(prefs, KEY_HOME_TZ, mHomeTZ);
+
+                // Update the db
+                ContentValues values = new ContentValues();
+                if (mHandler != null) {
+                    mHandler.cancelOperation(mToken);
+                }
+
+                mHandler = new AsyncTZHandler(context.getContentResolver());
+
+                // skip 0 so query can use it
+                if (++mToken == 0) {
+                    mToken = 1;
+                }
+
+                // Write the use home tz setting
+                values.put(CalendarCache.VALUE, mUseHomeTZ ? CalendarCache.TIMEZONE_TYPE_HOME
+                        : CalendarCache.TIMEZONE_TYPE_AUTO);
+                mHandler.startUpdate(mToken, null, CalendarCache.URI, values, CalendarCache.WHERE,
+                        TIMEZONE_TYPE_ARGS);
+
+                // If using a home tz write it to the db
+                if (mUseHomeTZ) {
+                    ContentValues values2 = new ContentValues();
+                    values2.put(CalendarCache.VALUE, mHomeTZ);
+                    mHandler.startUpdate(mToken, null, CalendarCache.URI, values2,
+                            CalendarCache.WHERE, TIMEZONE_INSTANCES_ARGS);
+                }
+            }
+        }
+
+        /**
+         * Gets the time zone that Calendar should be displayed in
+         *
+         * This is a helper method to get the appropriate time zone for Calendar. If this
+         * is the first time this method has been called it will initiate an asynchronous
+         * query to verify that the data in preferences is correct. The callback supplied
+         * will only be called if this query returns a value other than what is stored in
+         * preferences and should cause the calling activity to refresh anything that
+         * depends on calling this method.
+         *
+         * @param context The calling activity
+         * @param callback The runnable that should execute if a query returns new values
+         * @return The string value representing the time zone Calendar should display
+         */
+        public String getTimeZone(Context context, Runnable callback) {
+            synchronized (mTZCallbacks){
+                if (mFirstTZRequest) {
+                    mTZQueryInProgress = true;
+                    mFirstTZRequest = false;
+
+                    SharedPreferences prefs = getSharedPreferences(context, mPrefsName);
+                    mUseHomeTZ = prefs.getBoolean(KEY_HOME_TZ_ENABLED, false);
+                    mHomeTZ = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
+
+                    // When the async query returns it should synchronize on
+                    // mTZCallbacks, update mUseHomeTZ, mHomeTZ, and the
+                    // preferences, set mTZQueryInProgress to false, and call all
+                    // the runnables in mTZCallbacks.
+                    if (mHandler == null) {
+                        mHandler = new AsyncTZHandler(context.getContentResolver());
+                    }
+                    mHandler.startQuery(0, context, CalendarCache.URI, CalendarCache.POJECTION,
+                            null, null, null);
+                }
+                if (mTZQueryInProgress) {
+                    mTZCallbacks.add(callback);
+                }
+            }
+            return mUseHomeTZ ? mHomeTZ : Time.getCurrentTimezone();
+        }
+
+        /**
+         * Forces a query of the database to check for changes to the time zone.
+         * This should be called if another app may have modified the db. If a
+         * query is already in progress the callback will be added to the list
+         * of callbacks to be called when it returns.
+         *
+         * @param context The calling activity
+         * @param callback The runnable that should execute if a query returns
+         *            new values
+         */
+        public void forceDBRequery(Context context, Runnable callback) {
+            synchronized (mTZCallbacks){
+                if (mTZQueryInProgress) {
+                    mTZCallbacks.add(callback);
+                    return;
+                }
+                mFirstTZRequest = true;
+                getTimeZone(context, callback);
+            }
+        }
+    }
+
+        /**
+         * A helper method for writing a String value to the preferences
+         * asynchronously.
+         *
+         * @param context A context with access to the correct preferences
+         * @param key The preference to write to
+         * @param value The value to write
+         */
+        public static void setSharedPreference(SharedPreferences prefs, String key, String value) {
+//            SharedPreferences prefs = getSharedPreferences(context);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.putString(key, value);
+            editor.apply();
+        }
+
+        /**
+         * A helper method for writing a boolean value to the preferences
+         * asynchronously.
+         *
+         * @param context A context with access to the correct preferences
+         * @param key The preference to write to
+         * @param value The value to write
+         */
+        public static void setSharedPreference(SharedPreferences prefs, String key, boolean value) {
+//            SharedPreferences prefs = getSharedPreferences(context, prefsName);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.putBoolean(key, value);
+            editor.apply();
+        }
+
+        /** Return a properly configured SharedPreferences instance */
+        public static SharedPreferences getSharedPreferences(Context context, String prefsName) {
+            return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
+        }
+}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 11a5d69..f917001 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -148,9 +148,15 @@
     void onPreDraw() {
         nPrepare(mRenderer);
     }
-    
+
     private native void nPrepare(int renderer);
 
+    void onPostDraw() {
+        nFinish(mRenderer);
+    }
+    
+    private native void nFinish(int renderer);
+
     @Override
     public boolean acquireContext() {
         if (!mContextLocked) {
@@ -531,17 +537,24 @@
         mLine[1] = startY;
         mLine[2] = stopX;
         mLine[3] = stopY;
-        drawLines(mLine, 0, 1, paint);
+        drawLines(mLine, 0, 4, paint);
     }
 
     @Override
     public void drawLines(float[] pts, int offset, int count, Paint paint) {
-        // TODO: Implement
+        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 / 4, paint);
+        drawLines(pts, 0, pts.length, paint);
     }
 
     @Override
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 5d85076a..3796994 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -210,7 +210,7 @@
          * is invoked and the requested flag is turned off. The error code is
          * also logged as a warning.
          */
-        void checkErrors() {
+        void checkEglErrors() {
             if (isEnabled()) {
                 int error = sEgl.eglGetError();
                 if (error != EGL10.EGL_SUCCESS) {
@@ -221,7 +221,7 @@
                         // we'll try again if it was context lost
                         setRequested(false);
                     }
-                    Log.w(LOG_TAG, "OpenGL error: " + error);
+                    Log.w(LOG_TAG, "EGL error: " + Integer.toHexString(error));
                 }
             }
         }
@@ -348,7 +348,7 @@
         void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
                 SurfaceHolder holder) {
             if (isRequested()) {
-                checkErrors();
+                checkEglErrors();
                 super.initializeIfNeeded(width, height, attachInfo, holder);
             }
         }
@@ -386,6 +386,9 @@
         void onPreDraw() {
         }
 
+        void onPostDraw() {
+        }
+        
         /**
          * Defines the EGL configuration for this renderer. The default configuration
          * is RGBX, no depth, no stencil.
@@ -418,10 +421,12 @@
                     canvas.restoreToCount(saveCount);
                 }
 
+                onPostDraw();
+
                 attachInfo.mIgnoreDirtyState = false;
 
                 sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
-                checkErrors();
+                checkEglErrors();
             }
         }
 
@@ -570,6 +575,11 @@
             mGlCanvas.onPreDraw();
         }
 
+        @Override
+        void onPostDraw() {
+            mGlCanvas.onPostDraw();
+        }
+
         static HardwareRenderer create(boolean translucent) {
             if (GLES20Canvas.isAvailable()) {
                 return new Gl20Renderer(translucent);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 7468579..dd04975 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -28,8 +28,7 @@
  * keyboard may compose the capabilities of a standard keyboard together with a track pad mouse
  * or other pointing device.
  * </p><p>
- * Some input devices present multiple distinguishable sources of input.  For example, a
- * game pad may have two analog joysticks, a directional pad and a full complement of buttons.
+ * Some input devices present multiple distinguishable sources of input.
  * Applications can query the framework about the characteristics of each distinct source.
  * </p><p>
  * As a further wrinkle, different kinds of input sources uses different coordinate systems
@@ -55,7 +54,7 @@
     
     /**
      * The input source has buttons or keys.
-     * Examples: {@link #SOURCE_KEYBOARD}, {@link #SOURCE_GAMEPAD}, {@link #SOURCE_DPAD}.
+     * Examples: {@link #SOURCE_KEYBOARD}, {@link #SOURCE_DPAD}.
      * 
      * A {@link KeyEvent} should be interpreted as a button or key press.
      * 
@@ -101,18 +100,6 @@
     public static final int SOURCE_CLASS_POSITION = 0x00000008;
     
     /**
-     * The input source is a joystick.
-     * 
-     * A {@link KeyEvent} should be interpreted as a joystick button press.
-     * 
-     * A {@link MotionEvent} should be interpreted in absolute coordinates as a joystick
-     * position in normalized device-specific units nominally between -1.0 and 1.0.
-     * 
-     * Use {@link #getMotionRange} to query the range and precision of motion.
-     */
-    public static final int SOURCE_CLASS_JOYSTICK = 0x00000010;
-    
-    /**
      * The input source is unknown.
      */
     public static final int SOURCE_UNKNOWN = 0x00000000;
@@ -132,13 +119,6 @@
     public static final int SOURCE_DPAD = 0x00000200 | SOURCE_CLASS_BUTTON;
     
     /**
-     * The input source is a gamepad.
-     * 
-     * @see #SOURCE_CLASS_BUTTON
-     */
-    public static final int SOURCE_GAMEPAD = 0x00000400 | SOURCE_CLASS_BUTTON;
-    
-    /**
      * The input source is a touch screen pointing device.
      * 
      * @see #SOURCE_CLASS_POINTER
@@ -168,20 +148,6 @@
      * @see #SOURCE_CLASS_POSITION
      */
     public static final int SOURCE_TOUCHPAD = 0x00100000 | SOURCE_CLASS_POSITION;
-
-    /**
-     * The input source is a joystick mounted on the left or is a standalone joystick.
-     * 
-     * @see #SOURCE_CLASS_JOYSTICK
-     */
-    public static final int SOURCE_JOYSTICK_LEFT = 0x01000000 | SOURCE_CLASS_JOYSTICK;
-    
-    /**
-     * The input source is a joystick mounted on the right.
-     * 
-     * @see #SOURCE_CLASS_JOYSTICK
-     */
-    public static final int SOURCE_JOYSTICK_RIGHT = 0x02000000 | SOURCE_CLASS_JOYSTICK;
     
     /**
      * A special input source constant that is used when filtering input devices
@@ -411,7 +377,7 @@
         /**
          * Gets the extent of the center flat position with respect to this coordinate.
          * For example, a flat value of 8 means that the center position is between -8 and +8.
-         * This value is mainly useful for calibrating joysticks.
+         * This value is mainly useful for calibrating self-centering devices.
          * @return The extent of the center flat position.
          */
         public float getFlat() {
@@ -506,13 +472,10 @@
         description.append("  Sources:");
         appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
         appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
-        appendSourceDescriptionIfApplicable(description, SOURCE_GAMEPAD, "gamepad");
         appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHSCREEN, "touchscreen");
         appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE, "mouse");
         appendSourceDescriptionIfApplicable(description, SOURCE_TRACKBALL, "trackball");
         appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad");
-        appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK_LEFT, "joystick_left");
-        appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK_RIGHT, "joystick_right");
         description.append("\n");
         
         appendRangeDescriptionIfApplicable(description, MOTION_RANGE_X, "x");
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 9afd16e..184e0fc 100755
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -23,7 +23,9 @@
  * Common base class for input events.
  */
 public abstract class InputEvent implements Parcelable {
+    /** @hide */
     protected int mDeviceId;
+    /** @hide */
     protected int mSource;
     
     /** @hide */
@@ -76,7 +78,7 @@
         mSource = source;
     }
     
-    public final int describeContents() {
+    public int describeContents() {
         return 0;
     }
     
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index ed10e41..9e7eedf 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -24,120 +24,305 @@
 import android.view.KeyCharacterMap.KeyData;
 
 /**
- * Contains constants for key events.
+ * Object used to report key and button events.
+ * <p>
+ * Each key press is described by a sequence of key events.  A key press
+ * starts with a key event with {@link #ACTION_DOWN}.  If the key is held
+ * sufficiently long that it repeats, then the initial down is followed
+ * additional key events with {@link #ACTION_DOWN} and a non-zero value for
+ * {@link #getRepeatCount()}.  The last key event is a {@link #ACTION_UP}
+ * for the key up.  If the key press is canceled, the key up event will have the
+ * {@link #FLAG_CANCELED} flag set.
+ * </p><p>
+ * Key events are generally accompanied by a key code ({@link #getKeyCode()}),
+ * scan code ({@link #getScanCode()}) and meta state ({@link #getMetaState()}).
+ * Key code constants are defined in this class.  Scan code constants are raw
+ * device-specific codes obtained from the OS and so are not generally meaningful
+ * to applications unless interpreted using the {@link KeyCharacterMap}.
+ * Meta states describe the pressed state of key modifiers
+ * such as {@link #META_SHIFT_ON} or {@link #META_ALT_ON}.
+ * </p><p>
+ * When interacting with an IME, the framework may deliver key events
+ * with the special action {@link #ACTION_MULTIPLE} that either specifies
+ * that single repeated key code or a sequence of characters to insert.
+ * </p><p>
+ * In general, the framework makes no guarantees that the key events delivered
+ * to a view constitute a complete key press.  In particular, there is no
+ * guarantee that a view will always receive a key event with {@link #ACTION_UP}
+ * for each {@link #ACTION_DOWN} that was delivered.
+ * </p><p>
+ * Refer to {@link InputDevice} for more information about how different kinds of
+ * input devices and sources represent keys and buttons.
+ * </p>
  */
 public class KeyEvent extends InputEvent implements Parcelable {
-    // key codes
+    /** Key code constant: Unknown key code. */
     public static final int KEYCODE_UNKNOWN         = 0;
+    /** Key code constant: Soft Left key.
+     * Usually situated below the display on phones and used as a multi-function
+     * feature key for selecting a software defined function shown on the bottom left
+     * of the display. */
     public static final int KEYCODE_SOFT_LEFT       = 1;
+    /** Key code constant: Soft Right key.
+     * Usually situated below the display on phones and used as a multi-function
+     * feature key for selecting a software defined function shown on the bottom right
+     * of the display. */
     public static final int KEYCODE_SOFT_RIGHT      = 2;
+    /** Key code constant: Home key.
+     * This key is handled by the framework and is never delivered to applications. */
     public static final int KEYCODE_HOME            = 3;
+    /** Key code constant: Back key. */
     public static final int KEYCODE_BACK            = 4;
+    /** Key code constant: Call key. */
     public static final int KEYCODE_CALL            = 5;
+    /** Key code constant: End Call key. */
     public static final int KEYCODE_ENDCALL         = 6;
+    /** Key code constant: '0' key. */
     public static final int KEYCODE_0               = 7;
+    /** Key code constant: '1' key. */
     public static final int KEYCODE_1               = 8;
+    /** Key code constant: '2' key. */
     public static final int KEYCODE_2               = 9;
+    /** Key code constant: '3' key. */
     public static final int KEYCODE_3               = 10;
+    /** Key code constant: '4' key. */
     public static final int KEYCODE_4               = 11;
+    /** Key code constant: '5' key. */
     public static final int KEYCODE_5               = 12;
+    /** Key code constant: '6' key. */
     public static final int KEYCODE_6               = 13;
+    /** Key code constant: '7' key. */
     public static final int KEYCODE_7               = 14;
+    /** Key code constant: '8' key. */
     public static final int KEYCODE_8               = 15;
+    /** Key code constant: '9' key. */
     public static final int KEYCODE_9               = 16;
+    /** Key code constant: '*' key. */
     public static final int KEYCODE_STAR            = 17;
+    /** Key code constant: '#' key. */
     public static final int KEYCODE_POUND           = 18;
+    /** Key code constant: Directional Pad Up key.
+     * May also be synthesized from trackball motions. */
     public static final int KEYCODE_DPAD_UP         = 19;
+    /** Key code constant: Directional Pad Down key.
+     * May also be synthesized from trackball motions. */
     public static final int KEYCODE_DPAD_DOWN       = 20;
+    /** Key code constant: Directional Pad Left key.
+     * May also be synthesized from trackball motions. */
     public static final int KEYCODE_DPAD_LEFT       = 21;
+    /** Key code constant: Directional Pad Right key.
+     * May also be synthesized from trackball motions. */
     public static final int KEYCODE_DPAD_RIGHT      = 22;
+    /** Key code constant: Directional Pad Center key.
+     * May also be synthesized from trackball motions. */
     public static final int KEYCODE_DPAD_CENTER     = 23;
+    /** Key code constant: Volume Up key. */
     public static final int KEYCODE_VOLUME_UP       = 24;
+    /** Key code constant: Volume Down key. */
     public static final int KEYCODE_VOLUME_DOWN     = 25;
+    /** Key code constant: Power key. */
     public static final int KEYCODE_POWER           = 26;
+    /** Key code constant: Camera key.
+     * Used to launch a camera application or take pictures. */
     public static final int KEYCODE_CAMERA          = 27;
+    /** Key code constant: Clear key. */
     public static final int KEYCODE_CLEAR           = 28;
+    /** Key code constant: 'A' key. */
     public static final int KEYCODE_A               = 29;
+    /** Key code constant: 'B' key. */
     public static final int KEYCODE_B               = 30;
+    /** Key code constant: 'C' key. */
     public static final int KEYCODE_C               = 31;
+    /** Key code constant: 'D' key. */
     public static final int KEYCODE_D               = 32;
+    /** Key code constant: 'E' key. */
     public static final int KEYCODE_E               = 33;
+    /** Key code constant: 'F' key. */
     public static final int KEYCODE_F               = 34;
+    /** Key code constant: 'G' key. */
     public static final int KEYCODE_G               = 35;
+    /** Key code constant: 'H' key. */
     public static final int KEYCODE_H               = 36;
+    /** Key code constant: 'I' key. */
     public static final int KEYCODE_I               = 37;
+    /** Key code constant: 'J' key. */
     public static final int KEYCODE_J               = 38;
+    /** Key code constant: 'K' key. */
     public static final int KEYCODE_K               = 39;
+    /** Key code constant: 'L' key. */
     public static final int KEYCODE_L               = 40;
+    /** Key code constant: 'M' key. */
     public static final int KEYCODE_M               = 41;
+    /** Key code constant: 'N' key. */
     public static final int KEYCODE_N               = 42;
+    /** Key code constant: 'O' key. */
     public static final int KEYCODE_O               = 43;
+    /** Key code constant: 'P' key. */
     public static final int KEYCODE_P               = 44;
+    /** Key code constant: 'Q' key. */
     public static final int KEYCODE_Q               = 45;
+    /** Key code constant: 'R' key. */
     public static final int KEYCODE_R               = 46;
+    /** Key code constant: 'S' key. */
     public static final int KEYCODE_S               = 47;
+    /** Key code constant: 'T' key. */
     public static final int KEYCODE_T               = 48;
+    /** Key code constant: 'U' key. */
     public static final int KEYCODE_U               = 49;
+    /** Key code constant: 'V' key. */
     public static final int KEYCODE_V               = 50;
+    /** Key code constant: 'W' key. */
     public static final int KEYCODE_W               = 51;
+    /** Key code constant: 'X' key. */
     public static final int KEYCODE_X               = 52;
+    /** Key code constant: 'Y' key. */
     public static final int KEYCODE_Y               = 53;
+    /** Key code constant: 'Z' key. */
     public static final int KEYCODE_Z               = 54;
+    /** Key code constant: ',' key. */
     public static final int KEYCODE_COMMA           = 55;
+    /** Key code constant: '.' key. */
     public static final int KEYCODE_PERIOD          = 56;
+    /** Key code constant: Left Alt modifier key. */
     public static final int KEYCODE_ALT_LEFT        = 57;
+    /** Key code constant: Right Alt modifier key. */
     public static final int KEYCODE_ALT_RIGHT       = 58;
+    /** Key code constant: Left Shift modifier key. */
     public static final int KEYCODE_SHIFT_LEFT      = 59;
+    /** Key code constant: Right Shift modifier key. */
     public static final int KEYCODE_SHIFT_RIGHT     = 60;
+    /** Key code constant: Tab key. */
     public static final int KEYCODE_TAB             = 61;
+    /** Key code constant: Space key. */
     public static final int KEYCODE_SPACE           = 62;
+    /** Key code constant: Symbol modifier key. */
     public static final int KEYCODE_SYM             = 63;
+    /** Key code constant: Explorer special function key.
+     * Used to launch a browser application. */
     public static final int KEYCODE_EXPLORER        = 64;
+    /** Key code constant: Envelope special function key.
+     * Used to launch a mail application. */
     public static final int KEYCODE_ENVELOPE        = 65;
+    /** Key code constant: Enter key. */
     public static final int KEYCODE_ENTER           = 66;
+    /** Key code constant: Delete key. */
     public static final int KEYCODE_DEL             = 67;
+    /** Key code constant: '`' (backtick) key. */
     public static final int KEYCODE_GRAVE           = 68;
+    /** Key code constant: '-'. */
     public static final int KEYCODE_MINUS           = 69;
+    /** Key code constant: '=' key. */
     public static final int KEYCODE_EQUALS          = 70;
+    /** Key code constant: '[' key. */
     public static final int KEYCODE_LEFT_BRACKET    = 71;
+    /** Key code constant: ']' key. */
     public static final int KEYCODE_RIGHT_BRACKET   = 72;
+    /** Key code constant: '\' key. */
     public static final int KEYCODE_BACKSLASH       = 73;
+    /** Key code constant: ';' key. */
     public static final int KEYCODE_SEMICOLON       = 74;
+    /** Key code constant: ''' (apostrophe) key. */
     public static final int KEYCODE_APOSTROPHE      = 75;
+    /** Key code constant: '/' key. */
     public static final int KEYCODE_SLASH           = 76;
+    /** Key code constant: '@' key. */
     public static final int KEYCODE_AT              = 77;
+    /** Key code constant: Number Lock modifier key. */
     public static final int KEYCODE_NUM             = 78;
+    /** Key code constant: Headset Hook key.
+     * Used to hang up calls and stop media. */
     public static final int KEYCODE_HEADSETHOOK     = 79;
+    /** Key code constant: Camera Focus key.
+     * Used to focus the camera. */
     public static final int KEYCODE_FOCUS           = 80;   // *Camera* focus
+    /** Key code constant: '+' key. */
     public static final int KEYCODE_PLUS            = 81;
+    /** Key code constant: Menu key. */
     public static final int KEYCODE_MENU            = 82;
+    /** Key code constant: Notification key. */
     public static final int KEYCODE_NOTIFICATION    = 83;
+    /** Key code constant: Search key. */
     public static final int KEYCODE_SEARCH          = 84;
+    /** Key code constant: Play/Pause media key. */
     public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
+    /** Key code constant: Stop media key. */
     public static final int KEYCODE_MEDIA_STOP      = 86;
+    /** Key code constant: Play Next media key. */
     public static final int KEYCODE_MEDIA_NEXT      = 87;
+    /** Key code constant: Play Previous media key. */
     public static final int KEYCODE_MEDIA_PREVIOUS  = 88;
+    /** Key code constant: Rewind media key. */
     public static final int KEYCODE_MEDIA_REWIND    = 89;
+    /** Key code constant: Fast Forward media key. */
     public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
+    /** Key code constant: Mute key. */
     public static final int KEYCODE_MUTE            = 91;
+    /** Key code constant: Page Up key. */
     public static final int KEYCODE_PAGE_UP         = 92;
+    /** Key code constant: Page Down key. */
     public static final int KEYCODE_PAGE_DOWN       = 93;
+    /** Key code constant: Picture Symbols modifier key.
+     * Used to switch symbol sets (Emoji, Kao-moji). */
     public static final int KEYCODE_PICTSYMBOLS     = 94;   // switch symbol-sets (Emoji,Kao-moji)
+    /** Key code constant: Switch Charset modifier key.
+     * Used to switch character sets (Kanji, Katakana). */
     public static final int KEYCODE_SWITCH_CHARSET  = 95;   // switch char-sets (Kanji,Katakana)
+    /** Key code constant: A Button key.
+     * On a game controller, the A button should be either the button labeled A
+     * or the first button on the upper row of controller buttons. */
     public static final int KEYCODE_BUTTON_A        = 96;
+    /** Key code constant: B Button key.
+     * On a game controller, the B button should be either the button labeled B
+     * or the second button on the upper row of controller buttons. */
     public static final int KEYCODE_BUTTON_B        = 97;
+    /** Key code constant: C Button key.
+     * On a game controller, the C button should be either the button labeled C
+     * or the third button on the upper row of controller buttons. */
     public static final int KEYCODE_BUTTON_C        = 98;
+    /** Key code constant: X Button key.
+     * On a game controller, the X button should be either the button labeled X
+     * or the first button on the lower row of controller buttons. */
     public static final int KEYCODE_BUTTON_X        = 99;
+    /** Key code constant: Y Button key.
+     * On a game controller, the Y button should be either the button labeled Y
+     * or the second button on the lower row of controller buttons. */
     public static final int KEYCODE_BUTTON_Y        = 100;
+    /** Key code constant: Z Button key.
+     * On a game controller, the Z button should be either the button labeled Z
+     * or the third button on the lower row of controller buttons. */
     public static final int KEYCODE_BUTTON_Z        = 101;
+    /** Key code constant: L1 Button key.
+     * On a game controller, the L1 button should be either the button labeled L1 (or L)
+     * or the top left trigger button. */
     public static final int KEYCODE_BUTTON_L1       = 102;
+    /** Key code constant: R1 Button key.
+     * On a game controller, the R1 button should be either the button labeled R1 (or R)
+     * or the top right trigger button. */
     public static final int KEYCODE_BUTTON_R1       = 103;
+    /** Key code constant: L2 Button key.
+     * On a game controller, the L2 button should be either the button labeled L2
+     * or the bottom left trigger button. */
     public static final int KEYCODE_BUTTON_L2       = 104;
+    /** Key code constant: R2 Button key.
+     * On a game controller, the R2 button should be either the button labeled R2
+     * or the bottom right trigger button. */
     public static final int KEYCODE_BUTTON_R2       = 105;
+    /** Key code constant: Left Thumb Button key.
+     * On a game controller, the left thumb button indicates that the left (or only)
+     * joystick is pressed. */
     public static final int KEYCODE_BUTTON_THUMBL   = 106;
+    /** Key code constant: Right Thumb Button key.
+     * On a game controller, the right thumb button indicates that the right
+     * joystick is pressed. */
     public static final int KEYCODE_BUTTON_THUMBR   = 107;
+    /** Key code constant: Start Button key.
+     * On a game controller, the button labeled Start. */
     public static final int KEYCODE_BUTTON_START    = 108;
+    /** Key code constant: Select Button key.
+     * On a game controller, the button labeled Select. */
     public static final int KEYCODE_BUTTON_SELECT   = 109;
+    /** Key code constant: Mode Button key.
+     * On a game controller, the button labeled Mode. */
     public static final int KEYCODE_BUTTON_MODE     = 110;
 
     // NOTE: If you add a new keycode here you must also add it to:
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index d0985d9..7d5dcd8 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -16,8 +16,7 @@
 
 package android.view;
 
-import java.io.IOException;
-import java.lang.reflect.Method;
+import com.android.internal.view.menu.MenuItemImpl;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -29,7 +28,9 @@
 import android.util.AttributeSet;
 import android.util.Xml;
 
-import com.android.internal.view.menu.MenuItemImpl;
+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.
@@ -52,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;
     
     /**
@@ -249,6 +252,9 @@
          * - -1: Safe sentinel for "no value".
          */
         private int itemShowAsAction;
+
+        private int itemActionViewLayout;
+        private String itemActionViewClassName;
         
         private String itemListenerMethodName;
         
@@ -325,6 +331,8 @@
             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();
             
@@ -368,6 +376,19 @@
                     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));
+            }
         }
         
         public void addItem() {
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 2604cf9..8b9d659 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -405,6 +405,29 @@
      * {@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 78b9b5d..6705596 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -24,9 +24,71 @@
  * Object used to report movement (mouse, pen, finger, trackball) events.  This
  * class may hold either absolute or relative movements, depending on what
  * it is being used for.
- * 
- * Refer to {@link InputDevice} for information about how different kinds of
+ * <p>
+ * On pointing devices such as touch screens, pointer coordinates specify absolute
+ * positions such as view X/Y coordinates.  Each complete gesture is represented
+ * by a sequence of motion events with actions that describe pointer state transitions
+ * and movements.  A gesture starts with a motion event with {@link #ACTION_DOWN}
+ * that provides the location of the first pointer down.  As each additional
+ * pointer that goes down or up, the framework will generate a motion event with
+ * {@link #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP} accordingly.
+ * Pointer movements are described by motion events with {@link #ACTION_MOVE}.
+ * Finally, a gesture end either when the final pointer goes up as represented
+ * by a motion event with {@link #ACTION_UP} or when gesture is canceled
+ * with {@link #ACTION_CANCEL}.
+ * </p><p>
+ * On trackballs, the pointer coordinates specify relative movements as X/Y deltas.
+ * A trackball gesture consists of a sequence of movements described by motion
+ * events with {@link #ACTION_MOVE} interspersed with occasional {@link #ACTION_DOWN}
+ * or {@link #ACTION_UP} motion events when the trackball button is pressed or released.
+ * </p><p>
+ * Motion events always report movements for all pointers at once.  The number
+ * of pointers only ever changes by one as individual pointers go up and down,
+ * except when the gesture is canceled.
+ * </p><p>
+ * The order in which individual pointers appear within a motion event can change
+ * from one event to the next. Use the {@link #getPointerId(int)} method to obtain a
+ * pointer id to track pointers across motion events in a gesture.  Then for
+ * successive motion events, use the {@link #findPointerIndex(int)} method to obtain
+ * the pointer index for a given pointer id in that motion event.
+ * </p><p>
+ * For efficiency, motion events with {@link #ACTION_MOVE} may batch together
+ * multiple movement samples within a single object.  The most current
+ * pointer coordinates are available using {@link #getX(int)} and {@link #getY(int)}.
+ * Earlier coordinates within the batch are accessed using {@link #getHistoricalX(int, int)}
+ * and {@link #getHistoricalY(int, int)}.  The coordinates are "historical" only
+ * insofar as they are older than the current coordinates in the batch; however,
+ * they are still distinct from any other coordinates reported in prior motion events.
+ * To process all coordinates in the batch in time order, first consume the historical
+ * coordinates then consume the current coordinates.
+ * </p><p>
+ * Example: Consuming all samples for all pointers in a motion event in time order.
+ * </p><p><pre><code>
+ * void printSamples(MotionEvent ev) {
+ *     final int historySize = ev.getHistorySize();
+ *     final int pointerCount = ev.getPointerCount();
+ *     for (int h = 0; h &lt; historySize; h++) {
+ *         System.out.printf("At time %d:", ev.getHistoricalEventTime(h));
+ *         for (int p = 0; p &lt; pointerCount; p++) {
+ *             System.out.printf("  pointer %d: (%f,%f)",
+ *                 ev.getPointerId(p), ev.getHistoricalX(p, h), ev.getHistoricalY(p, h));
+ *         }
+ *     }
+ *     System.out.printf("At time %d:", ev.getEventTime());
+ *     for (int p = 0; p &lt; pointerCount; p++) {
+ *         System.out.printf("  pointer %d: (%f,%f)",
+ *             ev.getPointerId(p), ev.getX(p), ev.getY(p));
+ *     }
+ * }
+ * </code></pre></p><p>
+ * In general, the framework makes no guarantees that the motion events delivered
+ * to a view constitute a complete gesture.  In particular, there is no
+ * guarantee that a view will always receive a motion event with {@link #ACTION_UP}
+ * for each {@link #ACTION_DOWN} that was delivered.
+ * </p><p>
+ * Refer to {@link InputDevice} for more information about how different kinds of
  * input devices and sources represent pointer coordinates.
+ * </p>
  */
 public final class MotionEvent extends InputEvent implements Parcelable {
     private static final long MS_PER_NS = 1000000;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b5987c3..ac65e09 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -986,7 +986,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
@@ -995,7 +995,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
@@ -1004,7 +1004,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
@@ -1013,7 +1013,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
@@ -1023,7 +1023,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
@@ -1032,8 +1032,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.
@@ -1041,48 +1040,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.
@@ -1091,8 +1084,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.
      *
@@ -1100,8 +1092,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.
      *
@@ -1109,8 +1100,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.
      *
@@ -1118,8 +1108,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.
@@ -1129,28 +1118,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.
      *
@@ -1158,18 +1140,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.
      *
@@ -1177,9 +1155,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.
      *
@@ -1187,9 +1163,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.
      *
@@ -1198,18 +1172,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.
      *
@@ -1217,9 +1187,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.
      *
@@ -1227,9 +1195,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.
@@ -1239,9 +1205,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.
      *
@@ -1249,9 +1213,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.
@@ -1261,9 +1223,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.
      *
@@ -1272,9 +1232,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.
@@ -1285,47 +1243,137 @@
      * @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<<0;
+    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) {
+                    if (false) {
+                        Log.i("View", "Index #" + i + " @ ordered #" + j
+                                + " resid=0x" + Integer.toHexString(orderedIds[j])
+                                + " mask " + orderedIds[j+1]);
+                    }
+                    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.
@@ -1572,6 +1620,9 @@
      */
     private static final int PIVOT_EXPLICITLY_SET = 0x10000000;
 
+    /** {@hide} */
+    static final int ACTIVATED                    = 0x20000000;
+
     /**
      * The parent this view is attached to.
      * {@hide}
@@ -5235,7 +5286,8 @@
      * The amount that the view is scaled in x around the pivot point, as a proportion of
      * the view's unscaled width. A value of 1, the default, means that no scaling is applied.
      *
-     * @default 1.0f
+     * <p>By default, this is 1.0f.
+     *
      * @see #getPivotX()
      * @see #getPivotY()
      * @return The scaling factor.
@@ -5267,7 +5319,8 @@
      * The amount that the view is scaled in y around the pivot point, as a proportion of
      * the view's unscaled height. A value of 1, the default, means that no scaling is applied.
      *
-     * @default 1.0f
+     * <p>By default, this is 1.0f.
+     *
      * @see #getPivotX()
      * @see #getPivotY()
      * @return The scaling factor.
@@ -5376,7 +5429,7 @@
      * The opacity of the view. This is a value from 0 to 1, where 0 means the view is
      * completely transparent and 1 means the view is completely opaque.
      *
-     * @default 1.0f
+     * <p>By default this is 1.0f.
      * @return The opacity of the view.
      */
     public float getAlpha() {
@@ -7690,6 +7743,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,
@@ -8324,18 +8396,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_PRESSED;
+        if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
+        if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
 
         drawableState = VIEW_STATE_SETS[viewStateIndex];
 
@@ -8347,7 +8414,7 @@
                     + " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
                     + " fo=" + hasFocus()
                     + " sl=" + ((privateFlags & SELECTED) != 0)
-                    + " wf=" + hasWindowFocus
+                    + " wf=" + hasWindowFocus()
                     + ": " + Arrays.toString(drawableState));
         }
 
@@ -8661,6 +8728,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.
@@ -10057,6 +10166,8 @@
         IBinder mPanelParentWindowToken;
         Surface mSurface;
 
+        boolean mHardwareAccelerated;        
+        
         /**
          * Scale factor used by the compatibility mode
          */
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 3db05b6..a18a977 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -877,7 +877,7 @@
                 mSplitMotionTargets = new SplitMotionTargets();
             }
             return dispatchSplitTouchEvent(ev);
-	}
+        }
 
         final int action = ev.getAction();
         final float xf = ev.getX();
@@ -1222,13 +1222,7 @@
                     uniqueTargetCount--;
                 }
 
-                final boolean childHandled = target.dispatchTouchEvent(targetEvent);
-                handled |= childHandled;
-                if (!childHandled) {
-                    // Child doesn't want these events anymore, but we're still dispatching
-                    // other split events to children.
-                    targets.removeView(target);
-                }
+                handled |= target.dispatchTouchEvent(targetEvent);
             } finally {
                 targetEvent.recycle();
             }
@@ -2072,6 +2066,19 @@
         }
     }
 
+    /**
+     * {@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;
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index e5fc859..7b20b8b 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -455,6 +455,7 @@
                     mHwRenderer.destroy(true);
                 }                
                 mHwRenderer = HardwareRenderer.createGlRenderer(2, translucent);
+                mAttachInfo.mHardwareAccelerated = true;
             }
         }
     }
@@ -1255,6 +1256,11 @@
             dirty.setEmpty();
             return;
         }
+
+        if (fullRedrawNeeded) {
+            mAttachInfo.mIgnoreDirtyState = true;
+            dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
+        }
         
         if (mHwRenderer != null && mHwRenderer.isEnabled()) {
             if (!dirty.isEmpty()) {
@@ -1269,11 +1275,6 @@
             return;
         }
 
-        if (fullRedrawNeeded) {
-            mAttachInfo.mIgnoreDirtyState = true;
-            dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
-        }
-
         if (DEBUG_ORIENTATION || DEBUG_DRAW) {
             Log.v(TAG, "Draw " + mView + "/"
                     + mWindowAttributes.getTitle()
@@ -1586,6 +1587,7 @@
         if (mHwRenderer != null) {
             mHwRenderer.destroy(true);
             mHwRenderer = null;
+            mAttachInfo.mHardwareAccelerated = false;
         }
 
         mSurface.release();
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
index 49ddc19..ba16c8a 100644
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -16,27 +16,95 @@
 
 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";
 
-    // Handle to the WebView this injector is associated with.
+    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 VwebView.
+     * Creates a new injector associated with a given {@link WebView}.
      *
      * @param webView The associated WebView.
      */
     public AccessibilityInjector(WebView webView) {
         mWebView = webView;
+        ensureWebContentKeyBindings();
     }
 
     /**
@@ -45,55 +113,372 @@
      * @return True if the event was processed.
      */
     public boolean onKeyEvent(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_UP) {
+            return mLastDownEventHandled;
+        }
 
-        // as a proof of concept let us do the simplest example
+        mLastDownEventHandled = false;
 
-        if (event.getAction() != KeyEvent.ACTION_UP) {
+        int key = event.getMetaState() << AccessibilityWebContentKeyBinding.OFFSET_META_STATE |
+            event.getKeyCode() << AccessibilityWebContentKeyBinding.OFFSET_KEY_CODE;
+
+        AccessibilityWebContentKeyBinding binding = sBindings.get(key);
+        if (binding == null) {
             return false;
         }
 
-        int keyCode = event.getKeyCode();
-
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_N:
-                modifySelection("extend", "forward", "sentence");
-                break;
-            case KeyEvent.KEYCODE_P:
-                modifySelection("extend", "backward", "sentence");
-                break;
+        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 false;
+        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) {
-        // put the selection string in an AccessibilityEvent and send it
-        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-        event.getText().add(selectionString);
-        mWebView.sendAccessibilityEventUnchecked(event);
+        mIsLastSelectionStringNull = (selectionString == null);
+        AccessibilityEvent event = mScheduledEventStack.pop();
+        if (event != null) {
+            event.getText().add(selectionString);
+            sendAccessibilityEvent(event);
+        }
     }
 
     /**
-     * Modifies the current selection.
+     * Sends an {@link AccessibilityEvent}.
      *
-     * @param alter Specifies how to alter the selection.
-     * @param direction The direction in which to alter the selection.
-     * @param granularity The granularity of the selection modification.
+     * @param event The event to send.
      */
-    private void modifySelection(String alter, String direction, String granularity) {
-        WebViewCore webViewCore = mWebView.getWebViewCore();
+    private void sendAccessibilityEvent(AccessibilityEvent event) {
+        if (DEBUG) {
+            Log.d(LOG_TAG, "Dispatching: " + event);
+        }
+        AccessibilityManager.getInstance(mWebView.getContext()).sendAccessibilityEvent(event);
+    }
 
-        if (webViewCore == null) {
+    /**
+     * @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;
         }
 
-        WebViewCore.ModifySelectionData data = new WebViewCore.ModifySelectionData();
-        data.mAlter = alter;
-        data.mDirection = direction;
-        data.mGranularity = granularity;
-        webViewCore.sendMessage(EventHub.MODIFY_SELECTION, data);
+        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/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 1b5651b..b00f88c 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -256,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;
 
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index 2299d4a..27043e0 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -44,6 +44,7 @@
     private boolean mMatchesFound;
     private int mNumberOfMatches;
     private View mTitleBar;
+    private ActionMode mActionMode;
 
     FindActionModeCallback(Context context) {
         mCustomView = LayoutInflater.from(context).inflate(
@@ -63,6 +64,10 @@
 
     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.
@@ -156,6 +161,7 @@
         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);
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index d81d7f2..122c268 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -178,6 +178,7 @@
     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
@@ -324,8 +325,9 @@
 
         // Detect tablet device for fixed viewport mode.
         final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
-        mUseFixedViewport = (metrics.density == 1.0f 
-            && (metrics.widthPixels >= 800 ||metrics.heightPixels >= 800));
+        final int landscapeWidth = Math.max(metrics.widthPixels, metrics.heightPixels);
+        mUseFixedViewport = (metrics.density == 1.0f && landscapeWidth >= 800);
+        mMaxFixedViewportWidth = (int) (landscapeWidth * 1.25);
 
         if (sLockForLocaleSettings == null) {
             sLockForLocaleSettings = new Object();
@@ -1516,6 +1518,13 @@
     }
 
     /**
+     * Returns maximum fixed viewport width.
+     */
+    /* package */ int getMaxFixedViewportWidth() {
+        return mMaxFixedViewportWidth;
+    }
+
+    /**
      * Returns whether private browsing is enabled.
      */
     /* package */ boolean isPrivateBrowsingEnabled() {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index a3a4c43..c62894c 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -49,6 +49,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.speech.tts.TextToSpeech;
 import android.text.Selection;
 import android.text.Spannable;
@@ -101,6 +102,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+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
@@ -535,6 +538,10 @@
     // JavaScript or ones for which no accessibility script exists
     private AccessibilityInjector mAccessibilityInjector;
 
+    // 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
@@ -660,7 +667,7 @@
 
     // 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 = 1040;
+    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
@@ -694,6 +701,11 @@
     private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
     private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
 
+    // 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;
+
     // the alias via which accessibility JavaScript interface is exposed
     private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
 
@@ -707,6 +719,14 @@
         "    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;
 
@@ -1244,12 +1264,35 @@
     }
 
     /**
+     * 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();
+        clearActionModes();
+    }
+
+    /**
      * Destroy the internal state of the WebView. This method should be called
      * after the WebView has been removed from the view system. No other
      * methods may be called on a WebView after destroy.
      */
     public void destroy() {
-        clearTextEntry();
+        clearHelpers();
+
         if (mWebViewCore != null) {
             // Set the handlers to null before destroying WebViewCore so no
             // more messages will be posted.
@@ -1568,7 +1611,7 @@
         arg.mUrl = url;
         arg.mExtraHeaders = extraHeaders;
         mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
-        clearTextEntry();
+        clearHelpers();
     }
 
     /**
@@ -1597,7 +1640,7 @@
             arg.mUrl = url;
             arg.mPostData = postData;
             mWebViewCore.sendMessage(EventHub.POST_URL, arg);
-            clearTextEntry();
+            clearHelpers();
         } else {
             loadUrl(url);
         }
@@ -1653,7 +1696,7 @@
         arg.mEncoding = encoding;
         arg.mHistoryUrl = historyUrl;
         mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
-        clearTextEntry();
+        clearHelpers();
     }
 
     /**
@@ -1709,7 +1752,7 @@
      * Reload the current url.
      */
     public void reload() {
-        clearTextEntry();
+        clearHelpers();
         switchOutDrawHistory();
         mWebViewCore.sendMessage(EventHub.RELOAD);
     }
@@ -1789,7 +1832,7 @@
 
     private void goBackOrForward(int steps, boolean ignoreSnapshot) {
         if (steps != 0) {
-            clearTextEntry();
+            clearHelpers();
             mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
                     ignoreSnapshot ? 1 : 0);
         }
@@ -1966,7 +2009,7 @@
             Log.w(LOGTAG, "This WebView doesn't support zoom.");
             return;
         }
-        clearTextEntry();
+        clearHelpers();
         mZoomManager.invokeZoomPicker();
     }
 
@@ -2136,7 +2179,8 @@
             // it when its ActionMode ends, remove it.
             if (mSelectCallback != null) {
                 mSelectCallback.setTitleBar(null);
-            } else if (mFindCallback != null) {
+            }
+            if (mFindCallback != null) {
                 mFindCallback.setTitleBar(null);
             }
         }
@@ -2947,6 +2991,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.
      */
@@ -2971,23 +3032,93 @@
      * 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) {
-        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
-            if (getSettings().getJavaScriptEnabled()) {
-                loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);
-            } else if (mAccessibilityInjector == null) {
-                mAccessibilityInjector = new AccessibilityInjector(this);
-            }
-        } else {
+        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;
+    }
+
+    /**
      * The URL of a page that sent a message to scroll the title bar off screen.
      *
      * Many mobile sites tell the page to scroll to (0,1) in order to scroll the
@@ -3681,7 +3812,7 @@
         InputMethodManager imm = (InputMethodManager)
                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
 
-        // bring it back to the reading level scale so that user can enter text
+        // bring it back to the default level scale so that user can enter text
         boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale();
         if (zoom) {
             mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
@@ -3964,7 +4095,7 @@
 
         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
                 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
-            if (nativePageShouldHandleShiftAndArrows()) {
+            if (pageShouldHandleShiftAndArrows()) {
                 mShiftIsPressed = true;
             } else if (!nativeCursorWantsKeyEvents() && !mSelectingText) {
                 setUpSelect();
@@ -3984,7 +4115,7 @@
         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
             switchOutDrawHistory();
-            if (nativePageShouldHandleShiftAndArrows()) {
+            if (pageShouldHandleShiftAndArrows()) {
                 letPageHandleNavKey(keyCode, event.getEventTime(), true);
                 return true;
             }
@@ -4118,7 +4249,7 @@
 
         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
                 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
-            if (nativePageShouldHandleShiftAndArrows()) {
+            if (pageShouldHandleShiftAndArrows()) {
                 mShiftIsPressed = false;
             } else if (copySelection()) {
                 selectionDone();
@@ -4128,7 +4259,7 @@
 
         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
-            if (nativePageShouldHandleShiftAndArrows()) {
+            if (pageShouldHandleShiftAndArrows()) {
                 letPageHandleNavKey(keyCode, event.getEventTime(), false);
                 return true;
             }
@@ -4299,7 +4430,7 @@
 
     @Override
     protected void onDetachedFromWindow() {
-        clearTextEntry();
+        clearHelpers();
         mZoomManager.dismissZoomPicker();
         if (hasWindowFocus()) setActive(false);
         super.onDetachedFromWindow();
@@ -5550,7 +5681,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;
         }
@@ -7288,6 +7420,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
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 60ddf08..acea163 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -572,13 +572,12 @@
     /**
      * Modifies the current selection.
      *
-     * @param alter Specifies how to alter the selection.
      * @param direction The direction in which to alter the selection.
      * @param granularity The granularity of the selection modification.
      *
      * @return The selection string.
      */
-    private native String nativeModifySelection(String alter, String direction, String granularity);
+    private native String nativeModifySelection(int direction, int granularity);
 
     // EventHub for processing messages
     private final EventHub mEventHub;
@@ -724,12 +723,6 @@
         boolean mRemember;
     }
 
-    static class ModifySelectionData {
-        String mAlter;
-        String mDirection;
-        String mGranularity;
-    }
-
         static final String[] HandlerDebugString = {
             "REVEAL_SELECTION", // 96
             "REQUEST_LABEL", // 97
@@ -1270,16 +1263,9 @@
                             break;
 
                         case MODIFY_SELECTION:
-                            ModifySelectionData modifySelectionData =
-                                (ModifySelectionData) msg.obj;
-                            String selectionString = nativeModifySelection(
-                                    modifySelectionData.mAlter,
-                                    modifySelectionData.mDirection,
-                                    modifySelectionData.mGranularity);
-
-                            mWebView.mPrivateHandler.obtainMessage(
-                                    WebView.SELECTION_STRING_CHANGED, selectionString)
-                                    .sendToTarget();
+                            String selectionString = nativeModifySelection(msg.arg1, msg.arg2);
+                            mWebView.mPrivateHandler.obtainMessage(WebView.SELECTION_STRING_CHANGED,
+                                    selectionString).sendToTarget();
                             break;
 
                         case LISTBOX_CHOICES:
@@ -1719,7 +1705,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;
             }
@@ -1794,6 +1785,7 @@
         int mScrollX;
         int mScrollY;
         boolean mMobileSite;
+        int mViewportWidth;
     }
 
     static class DrawData {
@@ -1835,6 +1827,13 @@
             }
             if (mInitialViewState != null) {
                 draw.mViewState = mInitialViewState;
+                if (mViewportWidth == -1 && mSettings.getUseFixedViewport() &&
+                    mSettings.getUseWideViewPort()) {
+                    // Use website's initial preferred width as the fixed viewport width.
+                    mViewportWidth = Math.min(mSettings.getMaxFixedViewportWidth(),
+                        draw.mMinPrefWidth);
+                    draw.mViewState.mViewportWidth = mViewportWidth;
+                }
                 mInitialViewState = null;
             }
             if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
@@ -2168,6 +2167,7 @@
         mInitialViewState.mScrollX = mRestoredX;
         mInitialViewState.mScrollY = mRestoredY;
         mInitialViewState.mMobileSite = (0 == mViewportWidth);
+        mInitialViewState.mViewportWidth = mViewportWidth;
         if (mRestoredScale > 0) {
             mInitialViewState.mViewScale = mRestoredScale / 100.0f;
             if (mRestoredTextWrapScale > 0) {
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index a760e91..0d704a0 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -770,6 +770,12 @@
         } else {
             mMaxZoomScale = viewState.mMaxScale;
         }
+        if (viewState.mViewportWidth > 0 &&
+            mWebView.getSettings().getUseFixedViewport() &&
+            mWebView.getSettings().getUseWideViewPort()) {
+            // Use website specified viewport width.
+            setZoomOverviewWidth(viewState.mViewportWidth);
+        }
     }
 
     /**
@@ -835,7 +841,7 @@
                 if (settings.getUseWideViewPort()
                     && (settings.getLoadWithOverviewMode() || settings.getUseFixedViewport())) {
                     mInitialZoomOverview = true;
-                    scale = (float) mWebView.getViewWidth() / WebView.DEFAULT_VIEWPORT_WIDTH;
+                    scale = (float) mWebView.getViewWidth() / mZoomOverviewWidth;
                 } else {
                     scale = viewState.mTextWrapScale;
                 }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index d87f55f..bb13f1d 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -16,11 +16,12 @@
 
 package android.widget;
 
+import com.android.internal.R;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.TransitionDrawable;
@@ -56,8 +57,6 @@
 import android.view.inputmethod.InputConnectionWrapper;
 import android.view.inputmethod.InputMethodManager;
 
-import com.android.internal.R;
-
 import java.util.ArrayList;
 import java.util.List;
 
@@ -3074,8 +3073,10 @@
 
             // Estimate how many screens we should travel
             final float screenTravelCount = (float) viewTravelCount / childCount;
-            mScrollDuration = (int) (SCROLL_DURATION / screenTravelCount);
+            mScrollDuration = screenTravelCount < 1 ? (int) (screenTravelCount * SCROLL_DURATION) :
+                    (int) (SCROLL_DURATION / screenTravelCount);
             mLastSeenPos = INVALID_POSITION;
+
             post(this);
         }
 
@@ -3213,9 +3214,15 @@
             }
 
             case MOVE_OFFSET: {
-                final int childCount = getChildCount();
+                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;
 
@@ -3228,7 +3235,9 @@
                 } else {
                     // On-screen, just scroll.
                     final int targetTop = getChildAt(position - firstPos).getTop();
-                    smoothScrollBy(targetTop - mOffsetFromTop, mScrollDuration);
+                    final int distance = targetTop - mOffsetFromTop;
+                    smoothScrollBy(distance,
+                            (int) (mScrollDuration * ((float) distance / getHeight())));
                 }
                 break;
             }
@@ -4356,6 +4365,14 @@
      * @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.
+        Intent.FilterComparison fc = new Intent.FilterComparison(intent);
+        if (mRemoteAdapter != null && fc.equals(mRemoteAdapter.getRemoteViewsServiceIntent())) {
+            return;
+        }
+
+        // Otherwise, create a new RemoteViewsAdapter for binding
         mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
     }
 
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 0310a31..c08adb2 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -779,6 +779,15 @@
      */
     @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.
+        Intent.FilterComparison fc = new Intent.FilterComparison(intent);
+        if (mRemoteViewsAdapter != null &&
+                fc.equals(mRemoteViewsAdapter.getRemoteViewsServiceIntent())) {
+            return;
+        }
+
+        // Otherwise, create a new RemoteViewsAdapter for binding
         mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
     }
 
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index dffe685..46c7d33 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1341,6 +1341,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));
             }
         }
 
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index b464fef..8b21612 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -163,7 +163,7 @@
      * @param attrs Attributes from inflating parent views used to style the popup.
      */
     public ListPopupWindow(Context context, AttributeSet attrs) {
-        this(context, attrs, 0, 0);
+        this(context, attrs, com.android.internal.R.attr.listPopupWindowStyle, 0);
     }
 
     /**
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index f9bdc43..e5a34e8 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1785,6 +1785,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));
             }
         }
 
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0ca71e1..5645101 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,6 +16,13 @@
 
 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;
@@ -39,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
@@ -76,6 +76,15 @@
     
     
     /**
+     * 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.
      */
@@ -147,6 +156,134 @@
         }
     }
 
+    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.");
+                return;
+            }
+        }
+
+        int viewId;
+        PendingIntent pendingIntentTemplate;
+
+        public final static int TAG = 8;
+    }
+
     /**
      * Equivalent to calling
      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
@@ -157,21 +294,29 @@
             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.");
+                // 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) {
@@ -647,6 +792,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);
@@ -671,6 +818,12 @@
                 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;
                 default:
                     throw new ActionException("Tag " + tag + " not found");
                 }
@@ -696,6 +849,17 @@
     }
 
     /**
+     * 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;
+    }
+
+    /**
      * Add an action to be executed on the remote side when apply is called.
      * 
      * @param a The action to add
@@ -758,7 +922,7 @@
     public void setViewVisibility(int viewId, int visibility) {
         setInt(viewId, "setVisibility", visibility);
     }
-    
+
     /**
      * Equivalent to calling TextView.setText
      * 
@@ -864,6 +1028,35 @@
     }
 
     /**
+     * When using collections (eg. ListView, 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, and individual items can
+     * differentiate their click behavior using {@link RemoteViews#setOnClickExtras(int, Bundle)}.
+     *
+     * @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));
+    }
+
+    /**
+     * When using collections (eg. ListView, 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 items click
+     * behaviour can be distinguished by setting extras.
+     *
+     * @param viewId The id of the
+     * @param extras
+     */
+    public void setOnClickExtras(int viewId, Bundle extras) {
+        addAction(new SetOnClickExtras(viewId, extras));
+    }
+
+    /**
      * @hide
      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
@@ -1179,6 +1372,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
index 70b9d59..91b4afa 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -669,6 +669,9 @@
     public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
         mContext = context;
         mIntent = intent;
+        if (mIntent == null) {
+            throw new IllegalArgumentException("Non-null Intent must be specified.");
+        }
 
         // initialize the worker thread
         mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
@@ -689,6 +692,10 @@
         unbindService();
     }
 
+    public Intent getRemoteViewsServiceIntent() {
+        return mIntent;
+    }
+
     public int getCount() {
         requestBindService();
         return mViewCache.getCount();
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index 584fa25..b9ded190 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -36,7 +36,7 @@
     private static final String LOG_TAG = "RemoteViewsService";
 
     // multimap implementation for reference counting
-    private HashMap<Intent, Pair<RemoteViewsFactory, Integer>> mRemoteViewFactories;
+    private HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>> mRemoteViewFactories;
     private final Object mLock = new Object();
 
     /**
@@ -89,7 +89,9 @@
             return mFactory.getCount();
         }
         public RemoteViews getViewAt(int position) {
-            return mFactory.getViewAt(position);
+            RemoteViews rv = mFactory.getViewAt(position);
+            rv.setIsWidgetCollectionChild(true);
+            return rv;
         }
         public RemoteViews getLoadingView() {
             return mFactory.getLoadingView();
@@ -108,26 +110,28 @@
     }
 
     public RemoteViewsService() {
-        mRemoteViewFactories = new HashMap<Intent, Pair<RemoteViewsFactory, Integer>>();
+        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(intent)) {
+            if (!mRemoteViewFactories.containsKey(fc)) {
                 factory = onGetViewFactory(intent);
                 factoryRef = new Pair<RemoteViewsFactory, Integer>(factory, 1);
-                mRemoteViewFactories.put(intent, factoryRef);
+                mRemoteViewFactories.put(fc, factoryRef);
                 factory.onCreate();
             } else {
-                Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(intent);
+                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(intent, factoryRef);
+                mRemoteViewFactories.put(fc, factoryRef);
             }
             return new RemoteViewsFactoryAdapter(factory);
         }
@@ -136,16 +140,19 @@
     @Override
     public boolean onUnbind(Intent intent) {
         synchronized (mLock) {
-            if (mRemoteViewFactories.containsKey(intent)) {
+            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(intent);
+                Pair<RemoteViewsFactory, Integer> oldFactoryRef =
+                        mRemoteViewFactories.get(fc);
                 int newRefCount = oldFactoryRef.second.intValue() - 1;
                 if (newRefCount <= 0) {
                     oldFactoryRef.first.onDestroy();
-                    mRemoteViewFactories.remove(intent);
+                    mRemoteViewFactories.remove(fc);
                 } else {
-                    Pair<RemoteViewsFactory, Integer> factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
-                    mRemoteViewFactories.put(intent, factoryRef);
+                    Pair<RemoteViewsFactory, Integer> factoryRef =
+                            new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
+                    mRemoteViewFactories.put(fc, factoryRef);
                 }
             }
         }
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index fe1c9da..09217af 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -57,6 +57,7 @@
     private OnCloseListener mOnCloseListener;
 
     private boolean mIconifiedByDefault;
+    private boolean mIconified;
     private CursorAdapter mSuggestionsAdapter;
     private View mSearchButton;
     private View mSubmitButton;
@@ -65,6 +66,7 @@
     private AutoCompleteTextView mQueryTextView;
     private boolean mSubmitButtonEnabled;
     private CharSequence mQueryHint;
+    private boolean mQueryRefinement;
 
     private SearchableInfo mSearchable;
 
@@ -139,8 +141,6 @@
         mQueryTextView.setOnItemClickListener(mOnItemClickListener);
         mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
 
-        mSubmitButtonEnabled = false;
-
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchView, 0, 0);
         setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, true));
         a.recycle();
@@ -175,6 +175,15 @@
     }
 
     /**
+     * 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
@@ -205,20 +214,54 @@
      * 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.
+     * is pressed. Changes to this property will take effect immediately.
      *
-     * @param iconified
+     * <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.
@@ -240,7 +283,34 @@
         return mSubmitButtonEnabled;
     }
 
-    public interface FilterableListAdapter extends ListAdapter, Filterable {
+    /**
+     * 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;
     }
 
     /**
@@ -263,11 +333,12 @@
         return mSuggestionsAdapter;
     }
 
-    private void updateViewsVisibility(boolean collapsed) {
+    private void updateViewsVisibility(final boolean collapsed) {
+        mIconified = collapsed;
         // Visibility of views that are visible when collapsed
-        int visCollapsed = collapsed? VISIBLE : GONE;
+        final int visCollapsed = collapsed ? VISIBLE : GONE;
         // Visibility of views that are visible when expanded
-        int visExpanded = collapsed? GONE : VISIBLE;
+        final int visExpanded = collapsed ? GONE : VISIBLE;
 
         mSearchButton.setVisibility(visCollapsed);
         mSubmitButton.setVisibility(mSubmitButtonEnabled ? visExpanded : GONE);
@@ -290,6 +361,14 @@
         }
     }
 
+    /**
+     * Called by the SuggestionsAdapter
+     * @hide
+     */
+    /* package */void onQueryRefine(CharSequence queryText) {
+        setQuery(queryText);
+    }
+
     private final OnClickListener mOnClickListener = new OnClickListener() {
 
         public void onClick(View v) {
@@ -322,7 +401,8 @@
         // 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());
+            launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText()
+                    .toString());
             return true;
         }
 
@@ -359,6 +439,9 @@
             mSuggestionsAdapter = new SuggestionsAdapter(getContext(),
                     this, mSearchable, mOutsideDrawablesCache);
             mQueryTextView.setAdapter(mSuggestionsAdapter);
+            ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
+                    mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
+                    : SuggestionsAdapter.REFINE_BY_ENTRY);
         }
     }
 
@@ -511,12 +594,14 @@
      * Sets the text in the query box, without updating the suggestions.
      */
     private void setQuery(CharSequence query) {
-        mQueryTextView.setText(query, false);
+        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, null, actionKey, actionMsg);
+        Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
         getContext().startActivity(intent);
     }
 
@@ -527,7 +612,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,
@@ -537,7 +621,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);
@@ -580,11 +664,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();
             }
@@ -606,14 +685,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
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 0f1acbe..56dc5cd 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -869,21 +869,19 @@
             View p = v;
             if (!(v.getParent() != null && v.getParent() instanceof View)) return;
 
-            View gp = (View) v.getParent();
             boolean firstPass = true;
             parentRect.set(0, 0, 0, 0);
             int depth = 0;
-            while (gp.getParent() != null && gp.getParent() instanceof View
+            while (p.getParent() != null && p.getParent() instanceof View
                     && !parentRect.contains(r)) {
                 if (!firstPass) {
-                    r.offset(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY());
+                    r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
                     depth++;
                 }
                 firstPass = false;
                 p = (View) p.getParent();
-                gp = (View) p.getParent();
-                parentRect.set(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY(),
-                        p.getRight() - gp.getScrollX(), p.getBottom() - gp.getScrollY());
+                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
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index 1b2449e..1ebe622 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -45,6 +45,7 @@
 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;
@@ -56,19 +57,23 @@
  *
  * @hide
  */
-class SuggestionsAdapter extends ResourceCursorAdapter {
+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 SparseArray<Drawable.ConstantState> mBackgroundsCache;
     private boolean mClosed = false;
+    private int mQueryRefinement = REFINE_BY_ENTRY;
 
     // URL color
     private ColorStateList mUrlColor;
@@ -79,7 +84,7 @@
     private int mText2UrlCol;
     private int mIconName1Col;
     private int mIconName2Col;
-    private int mBackgroundColorCol;
+    private int mFlagsCol;
 
     static final int NONE = -1;
 
@@ -102,14 +107,12 @@
         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;
-        mBackgroundsCache = new SparseArray<Drawable.ConstantState>();
-
+        
         mStartSpinnerRunnable = new Runnable() {
                 public void run() {
                 // mSearchView.setWorking(true); // TODO:
@@ -138,6 +141,27 @@
     }
 
     /**
+     * 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.
      */
@@ -242,8 +266,7 @@
                 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);
+                mFlagsCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FLAGS);
             }
         } catch (Exception e) {
             Log.e(LOG_TAG, "error changing cursor and caching columns", e);
@@ -269,12 +292,14 @@
         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);
         }
     }
 
@@ -282,13 +307,10 @@
     public void bindView(View view, Context context, Cursor cursor) {
         ChildViewCache views = (ChildViewCache) view.getTag();
 
-        int backgroundColor = 0;
-        if (mBackgroundColorCol != -1) {
-            backgroundColor = cursor.getInt(mBackgroundColorCol);
+        int flags = 0;
+        if (mFlagsCol != -1) {
+            flags = cursor.getInt(mFlagsCol);
         }
-        Drawable background = getItemBackground(backgroundColor);
-        view.setBackgroundDrawable(background);
-
         if (views.mText1 != null) {
             String text1 = getStringOrNull(cursor, mText1Col);
             setViewText(views.mText1, text1);
@@ -301,7 +323,7 @@
             } 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)) {
@@ -324,6 +346,22 @@
         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) {
@@ -341,33 +379,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);
@@ -600,21 +611,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/widget/TextView.java b/core/java/android/widget/TextView.java
index b8c9e72..2d550e4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3873,10 +3873,10 @@
             mCurrentAlpha = alpha;
             final Drawables dr = mDrawables;
             if (dr != null) {
-                if (dr.mDrawableLeft != null) dr.mDrawableLeft.setAlpha(alpha);
-                if (dr.mDrawableTop != null) dr.mDrawableTop.setAlpha(alpha);
-                if (dr.mDrawableRight != null) dr.mDrawableRight.setAlpha(alpha);
-                if (dr.mDrawableBottom != null) dr.mDrawableBottom.setAlpha(alpha);
+                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;
         }
@@ -5118,6 +5118,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) {
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 1620778..bd87a0d 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -18,6 +18,7 @@
 
 import com.android.internal.os.BatteryStatsImpl;
 
+import android.os.WorkSource;
 import android.telephony.SignalStrength;
 
 interface IBatteryStats {
@@ -33,6 +34,9 @@
        SensorService.cpp */
     void noteStopSensor(int uid, int sensor);
 
+    void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+    void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+
     void noteStartGps(int uid);
     void noteStopGps(int uid);
     void noteScreenOn();
@@ -57,6 +61,12 @@
     void noteScanWifiLockReleased(int uid);
     void noteWifiMulticastEnabled(int uid);
     void noteWifiMulticastDisabled(int uid);
+    void noteFullWifiLockAcquiredFromSource(in WorkSource ws);
+    void noteFullWifiLockReleasedFromSource(in WorkSource ws);
+    void noteScanWifiLockAcquiredFromSource(in WorkSource ws);
+    void noteScanWifiLockReleasedFromSource(in WorkSource ws);
+    void noteWifiMulticastEnabledFromSource(in WorkSource ws);
+    void noteWifiMulticastDisabledFromSource(in WorkSource ws);
     void setBatteryState(int status, int health, int plugType, int level, int temp, int volt);
     long getAwakeTimeBattery();
     long getAwakeTimePlugged();
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index 5c2d692..c8e3935 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.app;
 
+import com.android.internal.R;
+
 import android.app.Activity;
 import android.app.ActivityManagerNative;
 import android.app.IActivityManager;
@@ -30,8 +32,6 @@
 import android.widget.ArrayAdapter;
 import android.widget.ListView;
 
-import com.android.internal.R;
-
 import java.text.Collator;
 import java.util.Arrays;
 import java.util.Locale;
@@ -42,13 +42,14 @@
 
     public static interface LocaleSelectionListener {
         // You can add any argument if you really need it...
-        public void onLocaleSelected();
+        public void onLocaleSelected(Locale locale);
     }
 
     Loc[] mLocales;
     String[] mSpecialLocaleCodes;
     String[] mSpecialLocaleNames;
 
+    private Locale mNewLocale;
 
     LocaleSelectionListener mListener;  // default to null
 
@@ -181,14 +182,29 @@
         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();
 
-            Loc loc = mLocales[position];
-            config.locale = loc.locale;
+            config.locale = locale;
 
             // indicate this isn't some passing default - the user wants this remembered
             config.userSetLocale = true;
@@ -199,9 +215,5 @@
         } catch (RemoteException e) {
             // Intentionally left blank
         }
-
-        if (mListener != null) {
-            mListener.onLocaleSelected();
-        }
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 8b618c7..6e11cff 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -293,4 +293,34 @@
             inputStream.close();
         }
     }
+
+    // Remove the native binaries of a given package. This simply
+    // gets rid of the files in the 'lib' sub-directory.
+    public static void removeNativeBinariesLI(String nativeLibraryPath) {
+        if (DEBUG_NATIVE) {
+            Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryPath);
+        }
+
+        /*
+         * Just remove any file in the directory. Since the directory is owned
+         * by the 'system' UID, the application is not supposed to have written
+         * anything there.
+         */
+        File binaryDir = new File(nativeLibraryPath);
+        if (binaryDir.exists()) {
+            File[] binaries = binaryDir.listFiles();
+            if (binaries != null) {
+                for (int nn = 0; nn < binaries.length; nn++) {
+                    if (DEBUG_NATIVE) {
+                        Slog.d(TAG, "    Deleting " + binaries[nn].getName());
+                    }
+                    if (!binaries[nn].delete()) {
+                        Slog.w(TAG, "Could not delete native binary: " + binaries[nn].getPath());
+                    }
+                }
+            }
+            // Do not delete 'lib' directory itself, or this will prevent
+            // installation of future updates.
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index b9f0c61..4943531 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -29,6 +29,7 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
@@ -68,12 +69,12 @@
     private static final int VERSION = 50;
 
     // Maximum number of items we will record in the history.
-    private static final int MAX_HISTORY_ITEMS = 1000;
+    private static final int MAX_HISTORY_ITEMS = 2000;
     
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
     // in to one common name.
-    private static final int MAX_WAKELOCKS_PER_UID = 20;
+    private static final int MAX_WAKELOCKS_PER_UID = 30;
     
     private static final String BATCHED_WAKELOCK_NAME = "*overflow*";
     
@@ -1173,7 +1174,7 @@
         // If the current time is basically the same as the last time,
         // just collapse into one record.
         if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE
-                && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+100)) {
+                && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)) {
             // If the current is the same as the one before, then we no
             // longer need the entry.
             if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
@@ -1189,6 +1190,10 @@
             return;
         }
 
+        if (mNumHistoryItems == MAX_HISTORY_ITEMS) {
+            addHistoryRecordLocked(curTime, HistoryItem.CMD_OVERFLOW);
+        }
+
         if (mNumHistoryItems >= MAX_HISTORY_ITEMS) {
             // Once we've reached the maximum number of items, we only
             // record changes to the battery level.
@@ -1329,6 +1334,20 @@
         }
     }
 
+    public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteStartWakeLocked(ws.get(i), pid, name, type);
+        }
+    }
+
+    public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteStopWakeLocked(ws.get(i), pid, name, type);
+        }
+    }
+
     public int startAddingCpuLocked() {
         mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
 
@@ -1949,6 +1968,48 @@
         getUidStatsLocked(uid).noteWifiMulticastDisabledLocked();
     }
 
+    public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteFullWifiLockAcquiredLocked(ws.get(i));
+        }
+    }
+
+    public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteFullWifiLockReleasedLocked(ws.get(i));
+        }
+    }
+
+    public void noteScanWifiLockAcquiredFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteScanWifiLockAcquiredLocked(ws.get(i));
+        }
+    }
+
+    public void noteScanWifiLockReleasedFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteScanWifiLockReleasedLocked(ws.get(i));
+        }
+    }
+
+    public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiMulticastEnabledLocked(ws.get(i));
+        }
+    }
+
+    public void noteWifiMulticastDisabledFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiMulticastDisabledLocked(ws.get(i));
+        }
+    }
+
     @Override public long getScreenOnTime(long batteryRealtime, int which) {
         return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which);
     }
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java
index d38c03b..3030316 100644
--- a/core/java/com/android/internal/util/HierarchicalStateMachine.java
+++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java
@@ -137,7 +137,7 @@
     }
 
     class State1 extends HierarchicalState {
-        \@Override public boolean processMessage(Message message) {
+        &#64;Override public boolean processMessage(Message message) {
             Log.d(TAG, "Hello World");
             return HANDLED;
         }
@@ -257,10 +257,10 @@
     }
 
     class P1 extends HierarchicalState {
-        \@Override public void enter() {
+        &#64;Override public void enter() {
             Log.d(TAG, "mP1.enter");
         }
-        \@Override public boolean processMessage(Message message) {
+        &#64;Override public boolean processMessage(Message message) {
             boolean retVal;
             Log.d(TAG, "mP1.processMessage what=" + message.what);
             switch(message.what) {
@@ -278,16 +278,16 @@
             }
             return retVal;
         }
-        \@Override public void exit() {
+        &#64;Override public void exit() {
             Log.d(TAG, "mP1.exit");
         }
     }
 
     class S1 extends HierarchicalState {
-        \@Override public void enter() {
+        &#64;Override public void enter() {
             Log.d(TAG, "mS1.enter");
         }
-        \@Override public boolean processMessage(Message message) {
+        &#64;Override public boolean processMessage(Message message) {
             Log.d(TAG, "S1.processMessage what=" + message.what);
             if (message.what == CMD_1) {
                 // Transition to ourself to show that enter/exit is called
@@ -298,16 +298,16 @@
                 return NOT_HANDLED;
             }
         }
-        \@Override public void exit() {
+        &#64;Override public void exit() {
             Log.d(TAG, "mS1.exit");
         }
     }
 
     class S2 extends HierarchicalState {
-        \@Override public void enter() {
+        &#64;Override public void enter() {
             Log.d(TAG, "mS2.enter");
         }
-        \@Override public boolean processMessage(Message message) {
+        &#64;Override public boolean processMessage(Message message) {
             boolean retVal;
             Log.d(TAG, "mS2.processMessage what=" + message.what);
             switch(message.what) {
@@ -326,17 +326,17 @@
             }
             return retVal;
         }
-        \@Override public void exit() {
+        &#64;Override public void exit() {
             Log.d(TAG, "mS2.exit");
         }
     }
 
     class P2 extends HierarchicalState {
-        \@Override public void enter() {
+        &#64;Override public void enter() {
             Log.d(TAG, "mP2.enter");
             sendMessage(obtainMessage(CMD_5));
         }
-        \@Override public boolean processMessage(Message message) {
+        &#64;Override public boolean processMessage(Message message) {
             Log.d(TAG, "P2.processMessage what=" + message.what);
             switch(message.what) {
             case(CMD_3):
@@ -349,12 +349,12 @@
             }
             return HANDLED;
         }
-        \@Override public void exit() {
+        &#64;Override public void exit() {
             Log.d(TAG, "mP2.exit");
         }
     }
 
-    \@Override
+    &#64;Override
     void halting() {
         Log.d(TAG, "halting");
         synchronized (this) {
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index 035875a..d2851a9 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -19,9 +19,10 @@
 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.ContextMenu.ContextMenuInfo;
+import android.view.View;
 
 /**
  * @hide
@@ -222,4 +223,12 @@
     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/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index c4b6214..20939ab 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -21,6 +21,7 @@
 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;
@@ -158,8 +159,13 @@
         
         for (int i = 0; i < itemCount; i++) {
             final MenuItemImpl itemData = itemsToShow.get(i);
-            addItemView((ActionMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ACTION_BUTTON,
-                    this));
+            final View actionView = itemData.getActionView();
+            if (actionView != null) {
+                addView(actionView);
+            } else {
+                addItemView((ActionMenuItemView) itemData.getItemView(
+                        MenuBuilder.TYPE_ACTION_BUTTON, this));
+            }
         }
 
         if (reserveOverflow) {
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 749257a..8311c80 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -104,7 +104,7 @@
         com.android.internal.R.layout.list_menu_item_layout,
         com.android.internal.R.layout.list_menu_item_layout,
         com.android.internal.R.layout.action_menu_item_layout,
-        com.android.internal.R.layout.list_menu_item_layout,
+        com.android.internal.R.layout.popup_menu_item_layout,
     };
 
     private static final int[]  sCategoryToOrder = new int[] {
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 07a2a94..42f9771 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -78,6 +78,8 @@
 
     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;
 
@@ -666,4 +668,13 @@
         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
index a12a4d6..c9478240 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -72,9 +72,7 @@
     }
 
     public void show() {
-        // TODO Use a style from the theme here
-        mPopup = new ListPopupWindow(mContext, null, 0,
-                com.android.internal.R.style.Widget_Spinner);
+        mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
         mPopup.setOnItemClickListener(this);
         mPopup.setOnDismissListener(mDismissListener);
 
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 939f118..12cf853 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -84,6 +84,7 @@
     private boolean mCurDown;
     private int mCurNumPointers;
     private int mMaxNumPointers;
+    private int mActivePointerId;
     private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>();
     
     private final VelocityTracker mVelocity;
@@ -123,6 +124,7 @@
         
         PointerState ps = new PointerState();
         mPointers.add(ps);
+        mActivePointerId = 0;
         
         mVelocity = VelocityTracker.obtain();
         
@@ -183,14 +185,15 @@
             final int NP = mPointers.size();
             
             // Labels
-            if (NP > 0) {
-                final PointerState ps = mPointers.get(0);
+            if (mActivePointerId >= 0) {
+                final PointerState ps = mPointers.get(mActivePointerId);
+                
                 canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
                 canvas.drawText(mText.clear()
                         .append("P: ").append(mCurNumPointers)
                         .append(" / ").append(mMaxNumPointers)
                         .toString(), 1, base, mTextPaint);
-                
+
                 final int N = ps.mTraceCount;
                 if ((mCurDown && ps.mCurDown) || N == 0) {
                     canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
@@ -355,6 +358,11 @@
                     NP++;
                 }
                 
+                if (mActivePointerId < 0 ||
+                        ! mPointers.get(mActivePointerId).mCurDown) {
+                    mActivePointerId = id;
+                }
+                
                 final PointerState ps = mPointers.get(id);
                 ps.mCurDown = true;
                 if (mPrintCoords) {
@@ -396,6 +404,7 @@
             }
             
             if (action == MotionEvent.ACTION_UP
+                    || action == MotionEvent.ACTION_CANCEL
                     || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
                 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
                         >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
@@ -407,10 +416,14 @@
                     Log.i(TAG, mText.clear().append("Pointer ")
                             .append(id + 1).append(": UP").toString());
                 }
-
-                if (action == MotionEvent.ACTION_UP) {
+                
+                if (action == MotionEvent.ACTION_UP
+                        || action == MotionEvent.ACTION_CANCEL) {
                     mCurDown = false;
                 } else {
+                    if (mActivePointerId == id) {
+                        mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
+                    }
                     ps.addTrace(Float.NaN, Float.NaN);
                 }
             }
@@ -438,9 +451,9 @@
     
     // HACK
     // A quick and dirty string builder implementation optimized for GC.
-    // Using the basic StringBuilder implementation causes the application grind to a halt when
-    // more than a couple of pointers are down due to the number of temporary objects allocated
-    // while formatting strings for drawing or logging.
+    // Using String.format causes the application grind to a halt when
+    // more than a couple of pointers are down due to the number of
+    // temporary objects allocated while formatting strings for drawing or logging.
     private static final class FasterStringBuilder {
         private char[] mChars;
         private int mLength;
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c9d7e7f..4da73d7 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -703,42 +703,6 @@
         }
     }
 
-    /* enable poisoning of memory of freed objects */
-    property_get("dalvik.vm.gc.overwritefree", propBuf, "false");
-    if (strcmp(propBuf, "true") == 0) {
-        opt.optionString = "-Xgc:overwritefree";
-        mOptions.add(opt);
-    } else if (strcmp(propBuf, "false") != 0) {
-        LOGW("dalvik.vm.gc.overwritefree should be 'true' or 'false'");
-    }
-
-    /* enable heap verification before each gc */
-    property_get("dalvik.vm.gc.preverify", propBuf, "false");
-    if (strcmp(propBuf, "true") == 0) {
-        opt.optionString = "-Xgc:preverify";
-        mOptions.add(opt);
-    } else if (strcmp(propBuf, "false") != 0) {
-        LOGW("dalvik.vm.gc.preverify should be 'true' or 'false'");
-    }
-
-    /* enable heap verification after each gc */
-    property_get("dalvik.vm.gc.postverify", propBuf, "false");
-    if (strcmp(propBuf, "true") == 0) {
-        opt.optionString = "-Xgc:postverify";
-        mOptions.add(opt);
-    } else if (strcmp(propBuf, "false") != 0) {
-        LOGW("dalvik.vm.gc.postverify should be 'true' or 'false'");
-    }
-
-    /* enable card table verification for partial gc */
-    property_get("dalvik.vm.gc.verifycardtable", propBuf, "false");
-    if (strcmp(propBuf, "true") == 0) {
-        opt.optionString = "-Xgc:verifycardtable";
-        mOptions.add(opt);
-    } else if (strcmp(propBuf, "false") != 0) {
-        LOGW("dalvik.vm.gc.verifycardtable should be 'true' or 'false'");
-    }
-
     /* enable debugging; set suspend=y to pause during VM init */
 #ifdef HAVE_ANDROID_OS
     /* use android ADB transport */
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 2517a8a..6aa77f6 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -28,7 +28,7 @@
 #include <surfaceflinger/Surface.h>
 #include <ui/egl/android_natives.h>
 #include <ui/InputTransport.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 
 #include "JNIHelp.h"
 #include "android_os_MessageQueue.h"
@@ -128,17 +128,17 @@
 }
 
 void AInputQueue::attachLooper(ALooper* looper, int ident,
-        ALooper_callbackFunc* callback, void* data) {
-    mPollLoop = static_cast<android::PollLoop*>(looper);
-    mPollLoop->setLooperCallback(mConsumer.getChannel()->getReceivePipeFd(),
-            ident, POLLIN, callback, data);
-    mPollLoop->setLooperCallback(mDispatchKeyRead,
-            ident, POLLIN, callback, data);
+        ALooper_callbackFunc callback, void* data) {
+    mLooper = static_cast<android::Looper*>(looper);
+    mLooper->addFd(mConsumer.getChannel()->getReceivePipeFd(),
+            ident, ALOOPER_EVENT_INPUT, callback, data);
+    mLooper->addFd(mDispatchKeyRead,
+            ident, ALOOPER_EVENT_INPUT, callback, data);
 }
 
 void AInputQueue::detachLooper() {
-    mPollLoop->removeCallback(mConsumer.getChannel()->getReceivePipeFd());
-    mPollLoop->removeCallback(mDispatchKeyRead);
+    mLooper->removeFd(mConsumer.getChannel()->getReceivePipeFd());
+    mLooper->removeFd(mDispatchKeyRead);
 }
 
 int32_t AInputQueue::hasEvents() {
@@ -440,8 +440,8 @@
         if (env != NULL && clazz != NULL) {
             env->DeleteGlobalRef(clazz);
         }
-        if (pollLoop != NULL && mainWorkRead >= 0) {
-            pollLoop->removeCallback(mainWorkRead);
+        if (looper != NULL && mainWorkRead >= 0) {
+            looper->removeFd(mainWorkRead);
         }
         if (nativeInputQueue != NULL) {
             nativeInputQueue->mWorkWrite = -1;
@@ -509,7 +509,7 @@
     // These are used to wake up the main thread to process work.
     int mainWorkRead;
     int mainWorkWrite;
-    sp<PollLoop> pollLoop;
+    sp<Looper> looper;
 };
 
 void android_NativeActivity_setWindowFormat(
@@ -541,15 +541,15 @@
 /*
  * Callback for handling native events on the application's main thread.
  */
-static bool mainWorkCallback(int fd, int events, void* data) {
+static int mainWorkCallback(int fd, int events, void* data) {
     NativeCode* code = (NativeCode*)data;
     if ((events & POLLIN) == 0) {
-        return true;
+        return 1;
     }
     
     ActivityWork work;
     if (!read_work(code->mainWorkRead, &work)) {
-        return true;
+        return 1;
     }
 
     LOG_TRACE("mainWorkCallback: cmd=%d", work.cmd);
@@ -593,7 +593,7 @@
             break;
     }
     
-    return true;
+    return 1;
 }
 
 // ------------------------------------------------------------------------
@@ -621,9 +621,9 @@
             return 0;
         }
         
-        code->pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueue);
-        if (code->pollLoop == NULL) {
-            LOGW("Unable to retrieve MessageQueue's PollLoop");
+        code->looper = android_os_MessageQueue_getLooper(env, messageQueue);
+        if (code->looper == NULL) {
+            LOGW("Unable to retrieve MessageQueue's Looper");
             delete code;
             return 0;
         }
@@ -642,7 +642,7 @@
         result = fcntl(code->mainWorkWrite, F_SETFL, O_NONBLOCK);
         SLOGW_IF(result != 0, "Could not make main work write pipe "
                 "non-blocking: %s", strerror(errno));
-        code->pollLoop->setCallback(code->mainWorkRead, POLLIN, mainWorkCallback, code);
+        code->looper->addFd(code->mainWorkRead, 0, ALOOPER_EVENT_INPUT, mainWorkCallback, code);
         
         code->ANativeActivity::callbacks = &code->callbacks;
         if (env->GetJavaVM(&code->vm) < 0) {
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
index 36e9089..d930065 100644
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -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 {
@@ -433,7 +435,7 @@
     sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
     char const *nameStr = env->GetStringUTFChars(name, NULL);
     jobject ref = env->NewGlobalRef(function);
-    LOGD("native_addCustomFunction %s ref: %p", nameStr, ref);
+    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);
@@ -450,7 +452,7 @@
 
 static void native_releaseCustomFunction(JNIEnv* env, jobject object, jint ref)
 {
-    LOGD("native_releaseCustomFunction %d", ref);
+    LOGD_IF(DEBUG_JNI, "native_releaseCustomFunction %d", ref);
     env->DeleteGlobalRef((jobject)ref);
 }
 
diff --git a/core/jni/android_database_SQLiteQuery.cpp b/core/jni/android_database_SQLiteQuery.cpp
index d18cfd8..9e42189 100644
--- a/core/jni/android_database_SQLiteQuery.cpp
+++ b/core/jni/android_database_SQLiteQuery.cpp
@@ -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;
 }
 
@@ -117,6 +117,7 @@
     int boundParams;
     CursorWindow * window;
     bool gotAllRows = true;
+    bool gotException = false;
     
     if (statement == NULL) {
         LOGE("Invalid statement in fillWindow()");
@@ -167,7 +168,7 @@
             LOGE("startPos %d > actual rows %d", startPos, num);
             return num;
         }
-    } 
+    }
     
     while(startPos != 0 || numRows < maxRead) {
         err = sqlite3_step(statement);
@@ -181,7 +182,7 @@
             {
                 field_slot_t * fieldDir = window->allocRow();
                 if (!fieldDir) {
-                    LOGE("Failed allocating fieldDir at startPos %d row %d", startPos, numRows);
+                    LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d", startPos, numRows);
                     gotAllRows = false;
                     goto return_count;
                 }
@@ -206,7 +207,7 @@
                     int offset = window->alloc(size);
                     if (!offset) {
                         window->freeLastRow();
-                        LOGD("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);
                         gotAllRows = false;
                         goto return_count;
@@ -227,7 +228,7 @@
                     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);
+                        LOG_WINDOW("Failed allocating space for a long in column %d", i);
                         gotAllRows = false;
                         goto return_count;
                     }
@@ -237,7 +238,7 @@
                     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);
+                        LOG_WINDOW("Failed allocating space for a double in column %d", i);
                         gotAllRows = false;
                         goto return_count;
                     }
@@ -249,7 +250,7 @@
                     int offset = window->alloc(size);
                     if (!offset) {
                         window->freeLastRow();
-                        LOGD("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);
                         gotAllRows = false;
                         goto return_count;
@@ -274,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;
                 }
             }
@@ -295,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;
             }
 
@@ -305,6 +309,7 @@
             continue;
         } else {
             throw_sqlite3_exception(env, GET_HANDLE(env, object));
+            gotException = true;
             break;
         }
     }
@@ -326,6 +331,8 @@
         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);
diff --git a/core/jni/android_database_SQLiteStatement.cpp b/core/jni/android_database_SQLiteStatement.cpp
index 0f3114b..97e0483 100644
--- a/core/jni/android_database_SQLiteStatement.cpp
+++ b/core/jni/android_database_SQLiteStatement.cpp
@@ -239,6 +239,17 @@
     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 */
@@ -247,6 +258,7 @@
     {"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_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index c784974..10fe583 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -688,8 +688,8 @@
     field fields_to_find[] = {
         { "android/hardware/Camera", "mNativeContext",   "I", &fields.context },
         { "android/view/Surface",    ANDROID_VIEW_SURFACE_JNI_ID, "I", &fields.surface },
-        { "android/hardware/Camera$CameraInfo", "mFacing",   "I", &fields.facing },
-        { "android/hardware/Camera$CameraInfo", "mOrientation",   "I", &fields.orientation },
+        { "android/hardware/Camera$CameraInfo", "facing",   "I", &fields.facing },
+        { "android/hardware/Camera$CameraInfo", "orientation",   "I", &fields.orientation },
     };
 
     if (find_fields(env, fields_to_find, NELEM(fields_to_find)) < 0)
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index e29495c..10ceb7b 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -60,7 +60,7 @@
 
     Sensor const* const* sensorList;
     size_t count = mgr.getSensorList(&sensorList);
-    if (next >= count)
+    if (size_t(next) >= count)
         return -1;
     
     Sensor const* const list = sensorList[next];
@@ -78,7 +78,7 @@
     env->SetIntField(sensor, sensorOffsets.minDelay,     list->getMinDelay());
     
     next++;
-    return next<count ? next : 0;
+    return size_t(next) < count ? next : 0;
 }
 
 //----------------------------------------------------------------------------
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index 847b5a5..1b203ca 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -18,7 +18,7 @@
 
 #include "JNIHelp.h"
 
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 #include <utils/Log.h>
 #include "android_os_MessageQueue.h"
 
@@ -39,22 +39,22 @@
     NativeMessageQueue();
     ~NativeMessageQueue();
 
-    inline sp<PollLoop> getPollLoop() { return mPollLoop; }
+    inline sp<Looper> getLooper() { return mLooper; }
 
     bool pollOnce(int timeoutMillis);
     void wake();
 
 private:
-    sp<PollLoop> mPollLoop;
+    sp<Looper> mLooper;
 };
 
 // ----------------------------------------------------------------------------
 
 NativeMessageQueue::NativeMessageQueue() {
-    mPollLoop = PollLoop::getForThread();
-    if (mPollLoop == NULL) {
-        mPollLoop = new PollLoop(false);
-        PollLoop::setForThread(mPollLoop);
+    mLooper = Looper::getForThread();
+    if (mLooper == NULL) {
+        mLooper = new Looper(false);
+        Looper::setForThread(mLooper);
     }
 }
 
@@ -62,11 +62,11 @@
 }
 
 bool NativeMessageQueue::pollOnce(int timeoutMillis) {
-    return mPollLoop->pollOnce(timeoutMillis) != PollLoop::POLL_TIMEOUT;
+    return mLooper->pollOnce(timeoutMillis) != ALOOPER_POLL_TIMEOUT;
 }
 
 void NativeMessageQueue::wake() {
-    mPollLoop->wake();
+    mLooper->wake();
 }
 
 // ----------------------------------------------------------------------------
@@ -83,10 +83,10 @@
              reinterpret_cast<jint>(nativeMessageQueue));
 }
 
-sp<PollLoop> android_os_MessageQueue_getPollLoop(JNIEnv* env, jobject messageQueueObj) {
+sp<Looper> android_os_MessageQueue_getLooper(JNIEnv* env, jobject messageQueueObj) {
     NativeMessageQueue* nativeMessageQueue =
             android_os_MessageQueue_getNativeMessageQueue(env, messageQueueObj);
-    return nativeMessageQueue != NULL ? nativeMessageQueue->getPollLoop() : NULL;
+    return nativeMessageQueue != NULL ? nativeMessageQueue->getLooper() : NULL;
 }
 
 static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
diff --git a/core/jni/android_os_MessageQueue.h b/core/jni/android_os_MessageQueue.h
index 5c742e2..f961d8f 100644
--- a/core/jni/android_os_MessageQueue.h
+++ b/core/jni/android_os_MessageQueue.h
@@ -21,9 +21,9 @@
 
 namespace android {
 
-class PollLoop;
+class Looper;
 
-extern sp<PollLoop> android_os_MessageQueue_getPollLoop(JNIEnv* env, jobject messageQueueObj);
+extern sp<Looper> android_os_MessageQueue_getLooper(JNIEnv* env, jobject messageQueueObj);
 
 } // namespace android
 
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index 1307ec3..a9b91b04 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -63,6 +63,8 @@
 static jmethodID method_onRequestPasskeyConfirmation;
 static jmethodID method_onRequestPairingConsent;
 static jmethodID method_onDisplayPasskey;
+static jmethodID method_onRequestOobData;
+static jmethodID method_onAgentOutOfBandDataAvailable;
 static jmethodID method_onAgentAuthorize;
 static jmethodID method_onAgentCancel;
 
@@ -103,7 +105,7 @@
     method_onDeviceDisconnectRequested = env->GetMethodID(clazz, "onDeviceDisconnectRequested",
                                                         "(Ljava/lang/String;)V");
     method_onNetworkDeviceConnected = env->GetMethodID(clazz, "onNetworkDeviceConnected",
-                                                              "(Ljava/lang/String;I)V");
+                                                     "(Ljava/lang/String;Ljava/lang/String;I)V");
     method_onNetworkDeviceDisconnected = env->GetMethodID(clazz, "onNetworkDeviceDisconnected",
                                                               "(Ljava/lang/String;)V");
 
@@ -116,6 +118,8 @@
 
     method_onAgentAuthorize = env->GetMethodID(clazz, "onAgentAuthorize",
                                                "(Ljava/lang/String;Ljava/lang/String;)Z");
+    method_onAgentOutOfBandDataAvailable = env->GetMethodID(clazz, "onAgentOutOfBandDataAvailable",
+                                               "(Ljava/lang/String;)Z");
     method_onAgentCancel = env->GetMethodID(clazz, "onAgentCancel", "()V");
     method_onRequestPinCode = env->GetMethodID(clazz, "onRequestPinCode",
                                                "(Ljava/lang/String;I)V");
@@ -135,6 +139,8 @@
                                                "(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");
 
     field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I");
 #endif
@@ -345,6 +351,7 @@
 {
     DBusMessage *msg, *reply;
     DBusError err;
+    bool oob = TRUE;
 
     if (!dbus_connection_register_object_path(nat->conn, agent_path,
             &agent_vtable, nat)) {
@@ -366,6 +373,7 @@
     }
     dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &agent_path,
                              DBUS_TYPE_STRING, &capabilities,
+                             DBUS_TYPE_BOOLEAN, &oob,
                              DBUS_TYPE_INVALID);
 
     dbus_error_init(&err);
@@ -961,15 +969,18 @@
                                      "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);
@@ -1056,6 +1067,43 @@
         }
         goto success;
     } else if (dbus_message_is_method_call(msg,
+            "org.bluez.Agent", "OutOfBandAvailable")) {
+        char *object_path;
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_OBJECT_PATH, &object_path,
+                                   DBUS_TYPE_INVALID)) {
+            LOGE("%s: Invalid arguments for OutOfBandData available() method", __FUNCTION__);
+            goto failure;
+        }
+
+        LOGV("... object_path = %s", object_path);
+
+        bool available =
+            env->CallBooleanMethod(nat->me, method_onAgentOutOfBandDataAvailable,
+                env->NewStringUTF(object_path));
+
+
+        // reply
+        if (available) {
+            DBusMessage *reply = dbus_message_new_method_return(msg);
+            if (!reply) {
+                LOGE("%s: Cannot create message reply\n", __FUNCTION__);
+                goto failure;
+            }
+            dbus_connection_send(nat->conn, reply, NULL);
+            dbus_message_unref(reply);
+        } else {
+            DBusMessage *reply = dbus_message_new_error(msg,
+                    "org.bluez.Error.DoesNotExist", "OutofBand data not available");
+            if (!reply) {
+                LOGE("%s: Cannot create message reply\n", __FUNCTION__);
+                goto failure;
+            }
+            dbus_connection_send(nat->conn, reply, NULL);
+            dbus_message_unref(reply);
+        }
+        goto success;
+    } else if (dbus_message_is_method_call(msg,
             "org.bluez.Agent", "RequestPinCode")) {
         char *object_path;
         if (!dbus_message_get_args(msg, NULL,
@@ -1086,6 +1134,21 @@
                                        int(msg));
         goto success;
     } else if (dbus_message_is_method_call(msg,
+            "org.bluez.Agent", "RequestOobData")) {
+        char *object_path;
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_OBJECT_PATH, &object_path,
+                                   DBUS_TYPE_INVALID)) {
+            LOGE("%s: Invalid arguments for RequestOobData() method", __FUNCTION__);
+            goto failure;
+        }
+
+        dbus_message_ref(msg);  // increment refcount because we pass to java
+        env->CallVoidMethod(nat->me, method_onRequestOobData,
+                                       env->NewStringUTF(object_path),
+                                       int(msg));
+        goto success;
+    } else if (dbus_message_is_method_call(msg,
             "org.bluez.Agent", "DisplayPasskey")) {
         char *object_path;
         uint32_t passkey;
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index 74127cf..337dccc 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -296,6 +296,46 @@
 #endif
 }
 
+static jbyteArray readAdapterOutOfBandDataNative(JNIEnv *env, jobject object) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    DBusError err;
+    jbyte *hash, *randomizer;
+    jbyteArray byteArray = NULL;
+    int hash_len, r_len;
+    if (nat) {
+       DBusMessage *reply = dbus_func_args(env, nat->conn,
+                           get_adapter_path(env, object),
+                           DBUS_ADAPTER_IFACE, "ReadLocalOutOfBandData",
+                           DBUS_TYPE_INVALID);
+       if (!reply) return NULL;
+
+       dbus_error_init(&err);
+       if (dbus_message_get_args(reply, &err,
+                                DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &hash, &hash_len,
+                                DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &randomizer, &r_len,
+                                DBUS_TYPE_INVALID)) {
+          if (hash_len == 16 && r_len == 16) {
+               byteArray = env->NewByteArray(32);
+               if (byteArray) {
+                   env->SetByteArrayRegion(byteArray, 0, 16, hash);
+                   env->SetByteArrayRegion(byteArray, 16, 16, randomizer);
+               }
+           } else {
+               LOGE("readAdapterOutOfBandDataNative: Hash len = %d, R len = %d",
+                                                                  hash_len, r_len);
+           }
+       } else {
+          LOG_AND_FREE_DBUS_ERROR(&err);
+       }
+       dbus_message_unref(reply);
+       return byteArray;
+    }
+#endif
+    return NULL;
+}
+
 static jboolean createPairedDeviceNative(JNIEnv *env, jobject object,
                                          jstring address, jint timeout_ms) {
     LOGV(__FUNCTION__);
@@ -332,6 +372,41 @@
     return JNI_FALSE;
 }
 
+static jboolean createPairedDeviceOutOfBandNative(JNIEnv *env, jobject object,
+                                                jstring address, jint timeout_ms) {
+    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_address = env->GetStringUTFChars(address, NULL);
+        LOGV("... address = %s", c_address);
+        char *context_address = (char *)calloc(BTADDR_SIZE, sizeof(char));
+        const char *capabilities = "DisplayYesNo";
+        const char *agent_path = "/android/bluetooth/remote_device_agent";
+
+        strlcpy(context_address, c_address, BTADDR_SIZE);  // for callback
+        bool ret = dbus_func_args_async(env, nat->conn, (int)timeout_ms,
+                                        onCreatePairedDeviceResult, // callback
+                                        context_address,
+                                        eventLoopNat,
+                                        get_adapter_path(env, object),
+                                        DBUS_ADAPTER_IFACE,
+                                        "CreatePairedDeviceOutOfBand",
+                                        DBUS_TYPE_STRING, &c_address,
+                                        DBUS_TYPE_OBJECT_PATH, &agent_path,
+                                        DBUS_TYPE_STRING, &capabilities,
+                                        DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        return ret ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
 static jint getDeviceServiceChannelNative(JNIEnv *env, jobject object,
                                           jstring path,
                                           jstring pattern, jint attr_id) {
@@ -498,6 +573,40 @@
     return JNI_FALSE;
 }
 
+static jboolean setRemoteOutOfBandDataNative(JNIEnv *env, jobject object, jstring address,
+                         jbyteArray hash, jbyteArray randomizer, int nativeData) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *msg = (DBusMessage *)nativeData;
+        DBusMessage *reply = dbus_message_new_method_return(msg);
+        jbyte *h_ptr = env->GetByteArrayElements(hash, NULL);
+        jbyte *r_ptr = env->GetByteArrayElements(randomizer, NULL);
+        if (!reply) {
+            LOGE("%s: Cannot create message reply to return remote OOB data to "
+                 "D-Bus\n", __FUNCTION__);
+            dbus_message_unref(msg);
+            return JNI_FALSE;
+        }
+
+        dbus_message_append_args(reply,
+                                DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &h_ptr, 16,
+                                DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &r_ptr, 16,
+                                DBUS_TYPE_INVALID);
+
+        env->ReleaseByteArrayElements(hash, h_ptr, 0);
+        env->ReleaseByteArrayElements(randomizer, r_ptr, 0);
+
+        dbus_connection_send(nat->conn, reply, NULL);
+        dbus_message_unref(msg);
+        dbus_message_unref(reply);
+        return JNI_TRUE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
 static jboolean setPinNative(JNIEnv *env, jobject object, jstring address,
                          jstring pin, int nativeData) {
 #ifdef HAVE_BLUETOOTH
@@ -1069,7 +1178,10 @@
     {"startDiscoveryNative", "()Z", (void*)startDiscoveryNative},
     {"stopDiscoveryNative", "()Z", (void *)stopDiscoveryNative},
 
+    {"readAdapterOutOfBandDataNative", "()[B", (void *)readAdapterOutOfBandDataNative},
     {"createPairedDeviceNative", "(Ljava/lang/String;I)Z", (void *)createPairedDeviceNative},
+    {"createPairedDeviceOutOfBandNative", "(Ljava/lang/String;I)Z",
+                                    (void *)createPairedDeviceOutOfBandNative},
     {"cancelDeviceCreationNative", "(Ljava/lang/String;)Z", (void *)cancelDeviceCreationNative},
     {"removeDeviceNative", "(Ljava/lang/String;)Z", (void *)removeDeviceNative},
     {"getDeviceServiceChannelNative", "(Ljava/lang/String;Ljava/lang/String;I)I",
@@ -1078,6 +1190,7 @@
     {"setPairingConfirmationNative", "(Ljava/lang/String;ZI)Z",
             (void *)setPairingConfirmationNative},
     {"setPasskeyNative", "(Ljava/lang/String;II)Z", (void *)setPasskeyNative},
+    {"setRemoteOutOfBandDataNative", "(Ljava/lang/String;[B[BI)Z", (void *)setRemoteOutOfBandDataNative},
     {"setPinNative", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)setPinNative},
     {"cancelPairingUserInputNative", "(Ljava/lang/String;I)Z",
             (void *)cancelPairingUserInputNative},
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index aa71746..3a85bc1 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -84,6 +84,11 @@
     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();
@@ -260,6 +265,15 @@
     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
 // ----------------------------------------------------------------------------
@@ -384,6 +398,7 @@
     { "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 },
 
@@ -415,6 +430,7 @@
     { "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 },
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index 42f35d1..282e9ed 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -29,7 +29,7 @@
 
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 #include <utils/KeyedVector.h>
 #include <utils/threads.h>
 #include <ui/InputTransport.h>
@@ -77,7 +77,7 @@
         };
 
         Connection(uint16_t id,
-                const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop);
+                const sp<InputChannel>& inputChannel, const sp<Looper>& looper);
 
         inline const char* getInputChannelName() const { return inputChannel->getName().string(); }
 
@@ -88,7 +88,7 @@
 
         sp<InputChannel> inputChannel;
         InputConsumer inputConsumer;
-        sp<PollLoop> pollLoop;
+        sp<Looper> looper;
         jobject inputHandlerObjGlobal;
         PreallocatedInputEventFactory inputEventFactory;
 
@@ -110,7 +110,7 @@
     static void handleInputChannelDisposed(JNIEnv* env,
             jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data);
 
-    static bool handleReceiveCallback(int receiveFd, int events, void* data);
+    static int handleReceiveCallback(int receiveFd, int events, void* data);
 
     static jlong generateFinishedToken(int32_t receiveFd,
             uint16_t connectionId, uint16_t messageSeqNum);
@@ -141,7 +141,7 @@
     LOGD("channel '%s' - Registered", inputChannel->getName().string());
 #endif
 
-    sp<PollLoop> pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueueObj);
+    sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
 
     { // acquire lock
         AutoMutex _l(mLock);
@@ -153,7 +153,7 @@
         }
 
         uint16_t connectionId = mNextConnectionId++;
-        sp<Connection> connection = new Connection(connectionId, inputChannel, pollLoop);
+        sp<Connection> connection = new Connection(connectionId, inputChannel, looper);
         status_t result = connection->inputConsumer.initialize();
         if (result) {
             LOGW("Failed to initialize input consumer for input channel '%s', status=%d",
@@ -166,7 +166,7 @@
         int32_t receiveFd = inputChannel->getReceivePipeFd();
         mConnectionsByReceiveFd.add(receiveFd, connection);
 
-        pollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
+        looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
     } // release lock
 
     android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
@@ -201,7 +201,7 @@
 
         connection->status = Connection::STATUS_ZOMBIE;
 
-        connection->pollLoop->removeCallback(inputChannel->getReceivePipeFd());
+        connection->looper->removeFd(inputChannel->getReceivePipeFd());
 
         env->DeleteGlobalRef(connection->inputHandlerObjGlobal);
         connection->inputHandlerObjGlobal = NULL;
@@ -293,7 +293,7 @@
     q->unregisterInputChannel(env, inputChannelObj);
 }
 
-bool NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
+int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
     NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
@@ -308,33 +308,33 @@
         if (connectionIndex < 0) {
             LOGE("Received spurious receive callback for unknown input channel.  "
                     "fd=%d, events=0x%x", receiveFd, events);
-            return false; // remove the callback
+            return 0; // remove the callback
         }
 
         connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex);
-        if (events & (POLLERR | POLLHUP | POLLNVAL)) {
+        if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
             LOGE("channel '%s' ~ Publisher closed input channel or an error occurred.  "
                     "events=0x%x", connection->getInputChannelName(), events);
-            return false; // remove the callback
+            return 0; // remove the callback
         }
 
-        if (! (events & POLLIN)) {
+        if (! (events & ALOOPER_EVENT_INPUT)) {
             LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
                     "events=0x%x", connection->getInputChannelName(), events);
-            return true;
+            return 1;
         }
 
         status_t status = connection->inputConsumer.receiveDispatchSignal();
         if (status) {
             LOGE("channel '%s' ~ Failed to receive dispatch signal.  status=%d",
                     connection->getInputChannelName(), status);
-            return false; // remove the callback
+            return 0; // remove the callback
         }
 
         if (connection->messageInProgress) {
             LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.",
                     connection->getInputChannelName());
-            return true;
+            return 1;
         }
 
         status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);
@@ -342,7 +342,7 @@
             LOGW("channel '%s' ~ Failed to consume input event.  status=%d",
                     connection->getInputChannelName(), status);
             connection->inputConsumer.sendFinishedSignal();
-            return true;
+            return 1;
         }
 
         connection->messageInProgress = true;
@@ -394,7 +394,7 @@
                 connection->getInputChannelName());
         env->DeleteLocalRef(inputHandlerObjLocal);
         q->finished(env, finishedToken, false);
-        return true;
+        return 1;
     }
 
 #if DEBUG_DISPATCH_CYCLE
@@ -417,7 +417,7 @@
 
     env->DeleteLocalRef(inputEventObj);
     env->DeleteLocalRef(inputHandlerObjLocal);
-    return true;
+    return 1;
 }
 
 jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId,
@@ -435,9 +435,9 @@
 // ----------------------------------------------------------------------------
 
 NativeInputQueue::Connection::Connection(uint16_t id,
-        const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop) :
+        const sp<InputChannel>& inputChannel, const sp<Looper>& looper) :
     id(id), status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel),
-    pollLoop(pollLoop), inputHandlerObjGlobal(NULL),
+    looper(looper), inputHandlerObjGlobal(NULL),
     messageSeqNum(0), messageInProgress(false) {
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 89298ea..06c8423 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -478,7 +478,7 @@
          Does not include placing calls. -->
     <permission android:name="android.permission.MODIFY_PHONE_STATE"
         android:permissionGroup="android.permission-group.PHONE_CALLS"
-        android:protectionLevel="dangerous"
+        android:protectionLevel="signatureOrSystem"
         android:label="@string/permlab_modifyPhoneState"
         android:description="@string/permdesc_modifyPhoneState" />
 
@@ -599,8 +599,8 @@
     <!-- Allows an application to retrieve state dump information from system
          services. -->
     <permission android:name="android.permission.DUMP"
-        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
-        android:protectionLevel="dangerous"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
+        android:protectionLevel="signatureOrSystem"
         android:label="@string/permlab_dump"
         android:description="@string/permdesc_dump" />
 
@@ -849,11 +849,10 @@
         android:description="@string/permdesc_clearAppCache" />
 
     <!-- Allows an application to read the low-level system log files.
-         These can contain slightly private information about what is
-         happening on the device, but should never contain the user's
-         private information. -->
+         Log entries can contain the user's private information,
+         which is why this permission is 'dangerous'. -->
     <permission android:name="android.permission.READ_LOGS"
-        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:permissionGroup="android.permission-group.PERSONAL_INFO"
         android:protectionLevel="dangerous"
         android:label="@string/permlab_readLogs"
         android:description="@string/permdesc_readLogs" />
@@ -939,7 +938,7 @@
     <permission android:name="android.permission.UPDATE_DEVICE_STATS"
         android:label="@string/permlab_batteryStats"
         android:description="@string/permdesc_batteryStats"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signatureOrSystem" />
 
     <!-- Allows an application to open windows that are for use by parts
          of the system user interface.  Not for use by third party apps. -->
diff --git a/core/res/res/color/primary_text_dark.xml b/core/res/res/color/primary_text_dark.xml
index 39c9e22..3dd73e7 100644
--- a/core/res/res/color/primary_text_dark.xml
+++ b/core/res/res/color/primary_text_dark.xml
@@ -19,7 +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_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_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/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_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/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/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-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-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/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/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/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/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/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_list_item.xml b/core/res/res/layout/preference_header_item.xml
similarity index 93%
rename from core/res/res/layout/preference_list_item.xml
rename to core/res/res/layout/preference_header_item.xml
index 3b888b4..aba7b2ad 100644
--- a/core/res/res/layout/preference_list_item.xml
+++ b/core/res/res/layout/preference_header_item.xml
@@ -14,12 +14,12 @@
      limitations under the License.
 -->
 
-<!-- Layout for a preference category item, containing an icon and label. -->
+<!-- Layout of a header item in PreferenceActivity. -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+android:id/widget_frame"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
+    android:background="?android:attr/activatedBackgroundIndicator"
     android:gravity="center_vertical"
     android:paddingRight="?android:attr/scrollbarSize">
 
@@ -54,6 +54,7 @@
             android:layout_below="@android:id/title"
             android:layout_alignLeft="@android:id/title"
             android:textAppearance="?android:attr/textAppearanceSmall"
+            android:ellipsize="end"
             android:maxLines="2" />
 
     </RelativeLayout>
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
index fe950b2..9661c64 100644
--- a/core/res/res/layout/preference_list_content.xml
+++ b/core/res/res/layout/preference_list_content.xml
@@ -33,7 +33,7 @@
             android:orientation="vertical"
             android:layout_width="0px"
             android:layout_height="match_parent"
-            android:layout_weight="1">
+            android:layout_weight="10">
 
             <ListView android:id="@android:id/list"
                 android:layout_width="match_parent"
@@ -52,7 +52,7 @@
         <FrameLayout android:id="@+id/prefs"
                 android:layout_width="0px"
                 android:layout_height="match_parent"
-                android:layout_weight="3"
+                android:layout_weight="33"
                 android:visibility="gone" />
     </LinearLayout>
 
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/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 05e9faa..94cc393 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Umožňuje aplikaci načíst všechna data kontaktů (adresy) uložená ve vašem telefonu. Škodlivé aplikace poté mohou dalším lidem odeslat vaše data."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"zápis dat kontaktů"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Umožňuje aplikaci změnit kontaktní údaje (adresu) uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit kontaktní údaje."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"zápis informací o vlastníkovi"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Umožňuje aplikaci změnit informace o vlastníkovi telefonu uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit informace o vlastníkovi."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"čtení informací o vlastníkovi"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Umožňuje aplikaci číst informace o vlastníkovi telefonu uložená v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení načíst informace o vlastníkovi."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"Čtení událostí v kalendáři"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Umožňuje aplikaci načíst všechny události kalendáře uložené ve vašem telefonu. Škodlivé aplikace poté mohou dalším lidem odeslat události z vašeho kalendáře."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"Přidávání nebo úprava událostí v kalendáři a odesílání e-mailů hostům"</string>
@@ -713,13 +709,10 @@
     <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>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <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>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,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>
+    <!-- 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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index f5331e5..d64b548 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Tillader, at et program læser alle kontaktdata (adresser), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine data til andre mennesker."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"skriv kontaktdata"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Tillader, at et program ændrer kontaktdata (adresser), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre kontaktdata."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skriv ejerdata"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillader, at et program ændrer rtelefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre ejerdata."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"læs ejerdata"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillader, at et program læser telefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at læse ejerdata."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"læs kalenderbegivenheder"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillader, at et program læser alle kalenderbegivenheder, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine kalenderbegivenheder til andre mennesker."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"tilføj eller rediger kalenderbegivenheder, og send e-mail til gæster"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Kopier"</string>
     <string name="paste" msgid="5629880836805036433">"Indsæt"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopier webadresse"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"Marker tekst..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Tekstmarkering"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Inputmetode"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,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>
+    <!-- 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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 52ae3b4..1fe084c 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Ermöglicht einer Anwendung, alle auf Ihrem Telefon gespeicherten Kontaktdaten (Adressen) zu lesen. Schädliche Anwendungen können so Ihre Daten an andere Personen senden."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"Kontaktdaten schreiben"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Kontaktdaten (Adressen) zu ändern. Schädliche Anwendungen können so Ihre Kontaktdaten löschen oder verändern."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"Eigentümerdaten schreiben"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu ändern. Schädliche Anwendungen können so Eigentümerdaten löschen oder verändern."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"Eigentümerdaten lesen"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu lesen. Schädliche Anwendungen können so Eigentümerdaten lesen."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"Kalendereinträge lesen"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Ermöglicht einer Anwendung, alle auf Ihrem Telefon gespeicherten Kalenderereignisse zu lesen. Schädliche Anwendungen können so Ihre Kalenderereignisse an andere Personen senden."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"Kalendereinträge hinzufügen oder ändern und E-Mails an Gäste senden"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Kopieren"</string>
     <string name="paste" msgid="5629880836805036433">"Einfügen"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL kopieren"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"Text auswählen..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Textauswahl"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Eingabemethode"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"Schließen erzwingen"</string>
     <string name="report" msgid="4060218260984795706">"Bericht"</string>
     <string name="wait" msgid="7147118217226317732">"Warten"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index c21321d..2d54f44 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Επιτρέπει σε μια εφαρμογή την ανάγνωση όλων των δεδομένων επαφής (διεύθυνσης) που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να αποστείλουν τα δεδομένα σας σε τρίτους."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"εγγραφή δεδομένων επαφής"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Επιτρέπει σε μια εφαρμογή να τροποποιεί τα δεδομένα επαφής (διεύθυνσης) που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να διαγράψουν ή να τροποποιήσουν τα δεδομένα επαφών σας."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"εγγραφή δεδομένων κατόχου"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Επιτρέπει σε μια εφαρμογή να τροποποιήσει τα δεδομένα κατόχου τηλεφώνου στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να τροποποιήσουν τα δεδομένα κατόχου."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"ανάγνωση δεδομένων κατόχου"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Επιτρέπει σε μια εφαρμογή την ανάγνωση των δεδομένων κατόχου τηλεφώνου που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για την ανάγνωση δεδομένων κατόχου τηλεφώνου."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"ανάγνωση συμβάντων ημερολογίου"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Επιτρέπει σε μια εφαρμογή να αναγνώσει όλα τα συμβάντα ημερολογίου που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να αποστείλουν συμβάντα ημερολογίου σε άλλους χρήστες."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"προσθήκη ή τροποποίηση συμβάντων του ημερολογίου και αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου στους προσκεκλημένους"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Αντιγραφή"</string>
     <string name="paste" msgid="5629880836805036433">"Επικόλληση"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Αντιγραφή διεύθυνσης URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"Επιλογή κειμένου..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Επιλογή κειμένου"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Μέθοδος εισόδου"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"Αναγκαστικό κλείσιμο"</string>
     <string name="report" msgid="4060218260984795706">"Αναφορά"</string>
     <string name="wait" msgid="7147118217226317732">"Αναμονή"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 38ad404..efd2885 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Admite una aplicación que lee todos los datos de (direcciones) de contactos almacenados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para enviar tus eventos de calendario a otras personas."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"escribir datos de contacto"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Admite una aplicación que modifica los datos de (dirección de) contacto guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar los datos de contacto."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"escribir datos del propietario"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Admite una aplicación que modifica los datos del propietario del teléfono guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar los datos del propietario."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"leer datos del propietario"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Admite una aplicación que lee los datos del propietario del teléfono guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para leer los datos del propietario del teléfono."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"Leer eventos del calendario"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Admite que una aplicación lea todos los eventos de calendario almacenados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para enviar tus eventos de calendario a otras personas."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"Agregar o cambiar eventos del calendario y enviar un correo electrónico a los invitados"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Pegar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <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>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"Provocar acercamiento"</string>
     <string name="report" msgid="4060218260984795706">"Notificar"</string>
     <string name="wait" msgid="7147118217226317732">"Espera"</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>
@@ -876,4 +875,10 @@
     <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_message" msgid="4712369856601275146">"Toca para obtener más información acerca de la utilización de datos móviles."</string>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index bb2dfd8..b12f8be 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -144,7 +144,7 @@
     <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"El sonido está desactivado. Activar."</string>
     <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"El sonido está activado. Desactivar."</string>
     <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Modo avión"</string>
-    <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modo avión desactivado. Activar."</string>
+    <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modo avión activado. Desactivar."</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Modo avión desactivado. Activar."</string>
     <string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Permite que una aplicación lea todos los datos de contacto (direcciones) almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para enviar tus datos a otras personas."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"escribir datos de contacto"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permite que una aplicación modifique los datos de contacto (direcciones) almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar tus datos de contacto."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"escribir datos de propietario"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite que una aplicación modifique los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar los datos del propietario."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"leer datos del propietario"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite que una aplicación lea los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para leer los datos del propietario del teléfono."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"leer eventos de calendario"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite que una aplicación lea todos los eventos de calendario almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para enviar tus eventos de calendario a otras personas."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"añadir o modificar eventos de calendario y enviar mensajes de correo electrónico a los invitados"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Pegar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <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>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"Forzar cierre"</string>
     <string name="report" msgid="4060218260984795706">"Informe"</string>
     <string name="wait" msgid="7147118217226317732">"Esperar"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 343829a..5347a45 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Permet à une application de lire toutes les données des contacts (adresses) enregistrées sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour envoyer vos données à d\'autres personnes."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"Édition des données d\'un contact"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permet à une application de modifier toutes les données de contact (adresses) enregistrées sur le téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier vos données de contact."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"Édition les données du propriétaire"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permet à une application de modifier les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier ces données."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"Lecture des données du propriétaire"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permet à une application de lire les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour lire ces données."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"lire des événements de l\'agenda"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permet à une application de lire tous les événements de l\'agenda enregistrés sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour envoyer les événements de votre agenda à d\'autres personnes."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"ajouter ou modifier des événements d\'agenda et envoyer des e-mails aux invités"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Copier"</string>
     <string name="paste" msgid="5629880836805036433">"Coller"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copier l\'URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <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>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,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>
+    <!-- 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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 58a34eb..713c983 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Consente la lettura da parte di un\'applicazione di tutti i dati (gli indirizzi) di contatto memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per inviare i dati ad altre persone."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"scrittura dati di contatto"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Consente a un\'applicazione di modificare i dati (gli indirizzi) di contatto memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare i dati di contatto."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"scrittura dati proprietario"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Consente a un\'applicazione di modificare i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare tali dati."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"lettura dati proprietario"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Consente a un\'applicazione di leggere i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per leggere tali dati."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"leggere eventi di calendario"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Consente la lettura da parte di un\'applicazione di tutti gli eventi di calendario memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per inviare i tuoi eventi di calendario ad altre persone."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"aggiungere o modificare eventi di calendario e inviare email agli invitati"</string>
@@ -443,8 +439,8 @@
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Casa"</item>
     <item msgid="869923650527136615">"Cellulare"</item>
-    <item msgid="7897544654242874543">"Ufficio"</item>
-    <item msgid="1103601433382158155">"Fax ufficio"</item>
+    <item msgid="7897544654242874543">"Lavoro"</item>
+    <item msgid="1103601433382158155">"Fax lavoro"</item>
     <item msgid="1735177144948329370">"Fax casa"</item>
     <item msgid="603878674477207394">"Cercapersone"</item>
     <item msgid="1650824275177931637">"Altro"</item>
@@ -452,24 +448,24 @@
   </string-array>
   <string-array name="emailAddressTypes">
     <item msgid="8073994352956129127">"Casa"</item>
-    <item msgid="7084237356602625604">"Ufficio"</item>
+    <item msgid="7084237356602625604">"Lavoro"</item>
     <item msgid="1112044410659011023">"Altro"</item>
     <item msgid="2374913952870110618">"Personalizzato"</item>
   </string-array>
   <string-array name="postalAddressTypes">
     <item msgid="6880257626740047286">"Casa"</item>
-    <item msgid="5629153956045109251">"Ufficio"</item>
+    <item msgid="5629153956045109251">"Lavoro"</item>
     <item msgid="4966604264500343469">"Altro"</item>
     <item msgid="4932682847595299369">"Personalizzato"</item>
   </string-array>
   <string-array name="imAddressTypes">
     <item msgid="1738585194601476694">"Casa"</item>
-    <item msgid="1359644565647383708">"Uffico"</item>
+    <item msgid="1359644565647383708">"Lavoro"</item>
     <item msgid="7868549401053615677">"Altro"</item>
     <item msgid="3145118944639869809">"Personalizzato"</item>
   </string-array>
   <string-array name="organizationTypes">
-    <item msgid="7546335612189115615">"Ufficio"</item>
+    <item msgid="7546335612189115615">"Lavoro"</item>
     <item msgid="4378074129049520373">"Altro"</item>
     <item msgid="3455047468583965104">"Personalizzato"</item>
   </string-array>
@@ -486,8 +482,8 @@
     <string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizzato"</string>
     <string name="phoneTypeHome" msgid="2570923463033985887">"Casa"</string>
     <string name="phoneTypeMobile" msgid="6501463557754751037">"Cellulare"</string>
-    <string name="phoneTypeWork" msgid="8863939667059911633">"Ufficio"</string>
-    <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax ufficio"</string>
+    <string name="phoneTypeWork" msgid="8863939667059911633">"Lavoro"</string>
+    <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax lavoro"</string>
     <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax casa"</string>
     <string name="phoneTypePager" msgid="7582359955394921732">"Cercapersone"</string>
     <string name="phoneTypeOther" msgid="1544425847868765990">"Altro"</string>
@@ -501,7 +497,7 @@
     <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string>
     <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string>
     <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Cellulare lavoro"</string>
-    <string name="phoneTypeWorkPager" msgid="649938731231157056">"Cercapersone ufficio"</string>
+    <string name="phoneTypeWorkPager" msgid="649938731231157056">"Cercapersone lavoro"</string>
     <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistente"</string>
     <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
     <string name="eventTypeBirthday" msgid="2813379844211390740">"Compleanno"</string>
@@ -509,16 +505,16 @@
     <string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string>
     <string name="emailTypeCustom" msgid="8525960257804213846">"Personalizzato"</string>
     <string name="emailTypeHome" msgid="449227236140433919">"Casa"</string>
-    <string name="emailTypeWork" msgid="3548058059601149973">"Ufficio"</string>
+    <string name="emailTypeWork" msgid="3548058059601149973">"Lavoro"</string>
     <string name="emailTypeOther" msgid="2923008695272639549">"Altro"</string>
     <string name="emailTypeMobile" msgid="119919005321166205">"Cellulare"</string>
     <string name="postalTypeCustom" msgid="8903206903060479902">"Personalizzato"</string>
     <string name="postalTypeHome" msgid="8165756977184483097">"Casa"</string>
-    <string name="postalTypeWork" msgid="5268172772387694495">"Ufficio"</string>
+    <string name="postalTypeWork" msgid="5268172772387694495">"Lavoro"</string>
     <string name="postalTypeOther" msgid="2726111966623584341">"Altro"</string>
     <string name="imTypeCustom" msgid="2074028755527826046">"Personalizzato"</string>
     <string name="imTypeHome" msgid="6241181032954263892">"Casa"</string>
-    <string name="imTypeWork" msgid="1371489290242433090">"Ufficio"</string>
+    <string name="imTypeWork" msgid="1371489290242433090">"Lavoro"</string>
     <string name="imTypeOther" msgid="5377007495735915478">"Altro"</string>
     <string name="imProtocolCustom" msgid="6919453836618749992">"Personalizzato"</string>
     <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string>
@@ -530,7 +526,7 @@
     <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string>
     <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string>
     <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string>
-    <string name="orgTypeWork" msgid="29268870505363872">"Ufficio"</string>
+    <string name="orgTypeWork" msgid="29268870505363872">"Lavoro"</string>
     <string name="orgTypeOther" msgid="3951781131570124082">"Altro"</string>
     <string name="orgTypeCustom" msgid="225523415372088322">"Personalizzato"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Inserisci il PIN"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Copia"</string>
     <string name="paste" msgid="5629880836805036433">"Incolla"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copia URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"Seleziona testo..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selezione testo"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metodo inserimento"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"Termina"</string>
     <string name="report" msgid="4060218260984795706">"Segnala"</string>
     <string name="wait" msgid="7147118217226317732">"Attendi"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 7da30f7..ea9bf9f 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"端末に保存した連絡先(アドレス)データの読み取りをアプリケーションに許可します。悪意のあるアプリケーションがデータを他人に送信する恐れがあります。"</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"連絡先データの書き込み"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"端末に保存した連絡先(アドレス)データの変更をアプリケーションに許可します。悪意のあるアプリケーションが連絡先データを消去/変更する恐れがあります。"</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"所有者データの書き込み"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"端末に保存した所有者のデータの変更をアプリケーションに許可します。悪意のあるアプリケーションが所有者のデータを消去/変更する恐れがあります。"</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"所有者データの読み取り"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"携帯電話に保存した所有者データの読み取りをアプリケーションに許可します。悪意のあるアプリケーションが所有者データを読み取る恐れがあります。"</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"カレンダーの予定の読み取り"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"端末に保存したカレンダーの予定の読み取りをアプリケーションに許可します。悪意のあるアプリケーションがカレンダーの予定を他人に送信する恐れがあります。"</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"カレンダーの予定の追加や変更を行い、ゲストにメールを送信する"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"コピー"</string>
     <string name="paste" msgid="5629880836805036433">"貼り付け"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URLをコピー"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"テキストを選択..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"テキスト選択"</string>
     <string name="inputMethod" msgid="1653630062304567879">"入力方法"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"強制終了"</string>
     <string name="report" msgid="4060218260984795706">"レポート"</string>
     <string name="wait" msgid="7147118217226317732">"待機"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 144efa0..a2309da 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"애플리케이션이 휴대전화에 저장된 모든 연락처(주소) 데이터를 읽을 수 있도록 합니다. 이 경우 악성 애플리케이션이 데이터를 다른 사람에게 보낼 수 있습니다."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"연락처 데이터 작성"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"애플리케이션이 휴대전화에 저장된 연락처(주소) 데이터를 수정할 수 있도록 합니다. 이 경우 악성 애플리케이션이 연락처 데이터를 지우거나 수정할 수 있습니다."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"소유자 데이터 작성"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"애플리케이션이 휴대전화에 저장된 소유자 데이터를 수정할 수 있도록 합니다. 단, 악성 애플리케이션이 이 기능을 이용하여 소유자 데이터를 지우거나 수정할 수 있습니다."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"소유자 데이터 읽기"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"애플리케이션이 휴대전화에 저장된 휴대전화 소유자 데이터를 읽을 수 있도록 합니다. 이 경우 악성 애플리케이션이 휴대전화 소유자 데이터를 읽을 수 있습니다."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"캘린더 일정 읽기"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"애플리케이션이 휴대전화에 저장된 모든 캘린더 일정을 읽을 수 있도록 합니다. 이 경우 악성 애플리케이션이 캘린더 일정을 다른 사람에게 보낼 수 있습니다."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"캘린더 일정 추가/수정 및 참석자에게 이메일 전송"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"복사"</string>
     <string name="paste" msgid="5629880836805036433">"붙여넣기"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL 복사"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"텍스트 선택..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"텍스트 선택"</string>
     <string name="inputMethod" msgid="1653630062304567879">"입력 방법"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"닫기"</string>
     <string name="report" msgid="4060218260984795706">"신고"</string>
     <string name="wait" msgid="7147118217226317732">"대기"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index b4aa661..43e5597 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Lar applikasjonen lese all kontakt- og adresseinformasjon lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å sende personlige data til andre."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"skrive kontaktinformasjon"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Lar applikasjonen endre kontakt- og adresseinformasjon lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å redigere eller endre kontaktinformasjonen."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skrive eierinformasjon"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Lar applikasjonen endre dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å slette eller redigere telefonens eierdata."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"lese eierinformasjon"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Lar applikasjonen lese dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å lese telefonens eierdata."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"les kalenderaktiviteter"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Lar applikasjonen lese alle kalenderhendelser lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å sende kalenderhendelser til andre."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"legg til eller endre kalenderaktiviteter og send e-postmelding til gjestene"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Kopier"</string>
     <string name="paste" msgid="5629880836805036433">"Lim inn"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopier URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"Marker tekst"</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Merket tekst"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Inndatametode"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,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>
+    <!-- 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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index cc60101..7aded96 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Hiermee kan een toepassing alle contactgegevens (adresgegevens) zien die op uw telefoon zijn opgeslagen. Schadelijke toepassingen kunnen hiervan gebruik maken om uw gegevens te verzenden naar andere personen."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"contactgegevens schrijven"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Hiermee kan een toepassing de op uw telefoon opgeslagen contactgegevens (adresgegevens) wijzigen. Schadelijke toepassingen kunnen hiermee uw contactgegevens verwijderen of wijzigen."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"gegevens eigenaar schrijven"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar wijzigen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar verwijderen of wijzigen."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"gegevens eigenaar lezen"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar lezen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar lezen."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"agendagebeurtenissen lezen"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Hiermee kan een toepassing alle agendagebeurtenissen lezen die zijn opgeslagen op uw telefoon. Schadelijke toepassingen kunnen hiervan gebruik maken om uw agendagebeurtenissen te verzenden naar andere personen."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"agendagebeurtenissen toevoegen of aanpassen en e-mail verzenden naar gasten"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Kopiëren"</string>
     <string name="paste" msgid="5629880836805036433">"Plakken"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL kopiëren"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"Tekst selecteren..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Tekstselectie"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Invoermethode"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"Nu sluiten"</string>
     <string name="report" msgid="4060218260984795706">"Rapport"</string>
     <string name="wait" msgid="7147118217226317732">"Wachten"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index a965316..c6a0d9c 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Pozwala aplikacji na czytanie wszystkich danych kontaktowych (adresowych) zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby wysyłać dane użytkownika do innych ludzi."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"zapisywanie danych kontaktowych"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Pozwala aplikacji na zmianę danych kontaktowych (adresowych) zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby usunąć lub zmienić dane kontaktowe."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"zapisywanie danych właściciela"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Pozwala aplikacji na zmianę danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby wymazać lub zmienić dane właściciela."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"czytanie danych właściciela"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Pozwala aplikacji na czytanie danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do odczytania danych właściciela."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"odczytywanie wydarzeń w kalendarzu"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Pozwala aplikacji na odczytywanie wszystkich wydarzeń z kalendarza, zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do rozsyłania wydarzeń z kalendarza do innych ludzi."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"dodawanie i modyfikowanie wydarzeń w kalendarzu oraz wysyłanie wiadomości e-mail do gości"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Kopiuj"</string>
     <string name="paste" msgid="5629880836805036433">"Wklej"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopiuj adres URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"Zaznacz tekst"</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Zaznaczanie tekstu"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metoda wprowadzania"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,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>
+    <!-- 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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 360b911..638da34 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Permite a uma aplicação ler todos os dados de contactos (endereços) armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para enviar os seus dados a outras pessoas."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"escrever dados de contacto"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permite a uma aplicação modificar os dados de contacto (endereço) armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar estes dados para apagar ou modificar os dados dos seus contactos."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"escrever dados do proprietário"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite a uma aplicação modificar os dados do proprietário do telefone armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para apagar ou modificar dados do proprietário."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"ler dados do proprietário"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite a uma aplicação modificar os dados do proprietário do telefone armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para ler dados do proprietário do telefone."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"ler eventos da agenda"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite a uma aplicação ler todos os eventos do calendário armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para enviar os eventos do seu calendário a outras pessoas."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"adicionar ou alterar eventos da agenda e enviar e-mails para os convidados"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Colar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <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>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,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>
+    <!-- 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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index dc5ee14..1ec1346 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Permite que um aplicativo leia todos os dados de contato (endereço) armazenados no seu telefone. Aplicativos maliciosos podem usar isso para enviar os seus dados para outras pessoas."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"gravar dados de contato"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permite que um aplicativo modifique os dados de contato (endereço) armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar os seus dados de contato."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"gravar dados do proprietário"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite que um aplicativo modifique os dados de proprietário do telefone armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar dados do proprietário."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"ler dados do proprietário"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite que um aplicativo leia os dados de proprietário do telefone armazenados no seu telefone. Aplicativos maliciosos podem usar isso para ler os dados de proprietário do telefone."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"ler eventos da agenda"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite que um aplicativo leia todos os eventos da agenda armazenados no seu telefone. Aplicativos maliciosos podem usar isso para enviar eventos da sua agenda para outras pessoas."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"adicionar ou modificar eventos da agenda e enviar e-mail aos convidados"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Copiar"</string>
     <string name="paste" msgid="5629880836805036433">"Colar"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <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>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,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>
+    <!-- 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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 8405760..df2456c 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Позволяет приложению считывать все данные контактов (адресов), сохраненные в памяти телефона. Вредоносные приложения могут использовать эту возможность для передачи данных посторонним лицам."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"перезаписывать данные контакта"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Позволяет приложению изменять данные (адрес) контакта, сохраненные в памяти телефона. Вредоносные приложения могут использовать эту возможность для удаления или изменения данных контакта."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"перезаписывать данные о владельце"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Позволяет приложению изменять сведения о владельце, сохраненные на телефоне. Вредоносные приложения могут использовать эту возможность для удаления или изменения данных владельца."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"считывать данные о владельце"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Позволяет приложению считывать сведения о владельце, сохраненные в памяти телефона. Вредоносные приложения могут использовать эту возможность для считывания данных владельца."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"считывать мероприятия в календаре"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Позволяет приложению считывать все события календаря, сохраненные на телефоне. Вредоносные приложения могут использовать эту возможность для передачи ваших событий календаря посторонним лицам."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"добавлять и изменять мероприятия в календаре и отправлять письма гостям"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Копировать"</string>
     <string name="paste" msgid="5629880836805036433">"Вставить"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Копировать URL"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"Выбрать текст..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Выбор текста"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Способ ввода"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"Закрыть"</string>
     <string name="report" msgid="4060218260984795706">"Отчет"</string>
     <string name="wait" msgid="7147118217226317732">"Подождать"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 46806ab..4ba0e91 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Tillåter att ett program läser alla kontaktuppgifter (adresser) som har lagrats på din telefon. Skadliga program kan använda detta för att skicka dina data till andra personer."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"skriva kontaktuppgifter"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Tillåter att ett program ändrar kontaktuppgifter (adress) som har lagrats på din telefon. Skadliga program kan använda detta för att radera eller ändra kontaktuppgifter."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skriva ägarinformation"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillåter att ett program ändrar information om telefonens ägare som har lagrats på din telefon. Skadliga program kan använda detta för att radera eller ändra ägaruppgifter."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"läsa information om ägare"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillåter att ett program läser information om telefonens ägare som har lagrats på telefonen. Skadliga program kan använda detta för att läsa telefonens ägaruppgifter."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"läsa kalenderhändelser"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillåter att ett program läser alla händelser i kalendern som har lagrats på din telefon. Skadliga program kan använda detta för att skicka din kalender till andra personer."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"lägg till och ändra kalenderhändelser och skicka e-post till gäster"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"Kopiera"</string>
     <string name="paste" msgid="5629880836805036433">"Klistra in"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Kopiera webbadress"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"Markera text..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Textmarkering"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Indatametod"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,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>
+    <!-- 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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index fffbcd7..78a1a3e 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"Uygulamaların telefonunuzda depolanan tüm kişi (adres) verilerini okumasına izin verir. Kötü amaçlı uygulamalar bu işlevi verilerinizi başkalarına göndermek için kullanabilir."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"kişi verileri yaz"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"Uygulamaların telefonunuzda depolanan kişi (adres) verilerini değiştirmesine izin verir. Kötü amaçlı uygulamalar bu işlevi kişi verilerinizi silmek veya değiştirmek için kullanabilir."</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"sahip verilerini yaz"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Uygulamaların telefonunuzda depolanan telefon sahibi verilerini değiştirmesine izin verir. Kötü amaçlı uygulamalar bu işlevi kullanıcı verilerini silmek veya değiştirmek için kullanabilir."</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"sahip verilerini oku"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Uygulamaların telefonunuzda depolanan telefon sahibi verilerini okumasına izin verir. Kötü amaçlı uygulamalar bunu telefon sahibi verilerini okumak için kullanabilir."</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"takvim etkinliklerini oku"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"Uygulamaların telefonunuzda depolanan takvim etkinliklerinin tümünü okumasına izin verir. Kötü amaçlı uygulamalar bunu, takvim etkinliklerinizi başkalarına göndermek için kullanabilir."</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"takvim etkinlikleri ekle veya değiştir ve konuklara e-posta gönder"</string>
@@ -713,13 +709,10 @@
     <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>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <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>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"Kapanmaya zorla"</string>
     <string name="report" msgid="4060218260984795706">"Rapor"</string>
     <string name="wait" msgid="7147118217226317732">"Bekle"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index bff82b9..9ad8c3e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"允许应用程序读取您手机上存储的所有联系人(地址)数据。恶意应用程序可借此将您的数据发送给其他人。"</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"写入联系数据"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"允许应用程序修改您手机上存储的联系人(地址)数据。恶意应用程序可借此清除或修改您的联系人数据。"</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"写入所有者数据"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"允许应用程序修改您手机上存储的手机所有者数据。恶意应用程序可借此清除或修改所有者数据。"</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"读取所有者数据"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"允许应用程序读取您手机上存储的手机所有者数据。恶意应用程序可借此读取手机所有者数据。"</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"读取日历活动"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"允许应用程序读取您手机上存储的所有日历活动。恶意应用程序可借此将您的日历活动发送给其他人。"</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"添加或修改日历活动以及向邀请对象发送电子邮件"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"复制"</string>
     <string name="paste" msgid="5629880836805036433">"粘贴"</string>
     <string name="copyUrl" msgid="2538211579596067402">"复制网址"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"选择文字..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"文字选择"</string>
     <string name="inputMethod" msgid="1653630062304567879">"输入法"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"强行关闭"</string>
     <string name="report" msgid="4060218260984795706">"报告"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 300cf20..ead49a7 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -288,10 +288,6 @@
     <string name="permdesc_readContacts" msgid="3371591512896545975">"允許應用程式讀取手機上所有聯絡人 (地址)。請注意:惡意程式可能利用此功能將您的資料傳送給其他人。"</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"輸入聯絡人資料"</string>
     <string name="permdesc_writeContacts" msgid="3924383579108183601">"允許應用程式更改聯絡資訊 (地址)。請注意:惡意程式可能利用此功能,清除或修改聯絡資料。"</string>
-    <string name="permlab_writeOwnerData" msgid="4892555913849295393">"寫入持有者的資料"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"允許應用程式更改手機持有者的資料。請注意:惡意程式可能利用此功能,清除或修改持有者的資料。"</string>
-    <string name="permlab_readOwnerData" msgid="6668525984731523563">"讀取持有者的資料"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"允許應用程式讀取手機持有者資料。請注意:惡意程式可能利用此功能讀取持有者的資料。"</string>
     <string name="permlab_readCalendar" msgid="6898987798303840534">"讀取日曆活動"</string>
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"允許應用程式讀取手機上所有日曆資料。請注意:惡意程式可能利用此功能將您的日曆資料傳送給其他人。"</string>
     <string name="permlab_writeCalendar" msgid="3894879352594904361">"新增或修改日曆活動,並傳送電子郵件給他人"</string>
@@ -713,13 +709,10 @@
     <string name="copy" msgid="2681946229533511987">"複製"</string>
     <string name="paste" msgid="5629880836805036433">"貼上"</string>
     <string name="copyUrl" msgid="2538211579596067402">"複製網址"</string>
-    <!-- no translation found for selectTextMode (6738556348861347240) -->
-    <skip />
-    <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
-    <skip />
+    <string name="selectTextMode" msgid="6738556348861347240">"選取文字..."</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"選取文字"</string>
     <string name="inputMethod" msgid="1653630062304567879">"輸入方式"</string>
-    <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
-    <skip />
+    <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>
@@ -746,6 +739,12 @@
     <string name="force_close" msgid="3653416315450806396">"強制關閉"</string>
     <string name="report" msgid="4060218260984795706">"回報"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</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>
@@ -876,4 +875,10 @@
     <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>
+    <!-- no translation found for no_matches (8129421908915840737) -->
+    <skip />
+    <!-- no translation found for find_on_page (1946799233822820384) -->
+    <skip />
+    <!-- no translation found for matches_found:one (8167147081136579439) -->
+    <!-- no translation found for matches_found:other (4641872797067609177) -->
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 3bee089..2dec5c5 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -127,6 +127,12 @@
         <!-- 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. -->
@@ -151,6 +157,9 @@
         <!-- 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 -->
         <!-- ============= -->
@@ -457,6 +466,10 @@
         <attr name="quickContactBadgeStyleSmallWindowMedium" format="reference" />
         <!-- Default quickcontact badge style with large quickcontact window. -->
         <attr name="quickContactBadgeStyleSmallWindowLarge" format="reference" />
+        <!-- Default ListPopupWindow style. -->
+        <attr name="listPopupWindowStyle" format="reference" />
+        <!-- Default PopupMenu style. -->
+        <attr name="popupMenuStyle" format="reference" />
 
         <!-- =================== -->
         <!-- Action bar styles   -->
@@ -3104,6 +3117,7 @@
          <li>"state_last"
          <li>"state_only"
          <li>"state_pressed"
+         <li>"state_activated"
          <li>"state_error"
          <li>"state_circle"
          <li>"state_rect"
@@ -3111,23 +3125,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}.-->
@@ -3138,8 +3164,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" />
@@ -3147,6 +3171,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">
@@ -3502,6 +3527,16 @@
             <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>
 
     <!-- **************************************************************** -->
@@ -3520,10 +3555,16 @@
     <!-- 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. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ac5828f..c5486e3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -131,6 +131,10 @@
     <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>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 0b32362..fbee438 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1344,6 +1344,16 @@
   <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="anim" name="animator_fade_in" />
   <public type="anim" name="animator_fade_out" />
@@ -1352,12 +1362,6 @@
   <!-- Context menu ID for the "Select text..." menu item to switch to text
        selection context mode in text views. -->
   <public type="id" name="selectTextMode" />
-  
-  <public type="style" name="Theme.WithActionBar" />
-  <public type="style" name="Widget.Spinner.DropDown" />
-  <public type="style" name="Widget.ActionButton" />
-  <public type="style" name="Theme.Dialog.NoFrame" />
-  <public type="style" name="Theme.NoTitleBar.OverlayActionModes" />
 
   <!-- Standard content view for a {@link android.app.ListFragment}.
        If you are implementing a subclass of ListFragment with your
@@ -1366,6 +1370,15 @@
        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" />
+
+  <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" />
@@ -1373,9 +1386,14 @@
   <public type="style" name="Theme.Light.Holo.NoActionBar" />
   <public type="style" name="Theme.Light.Holo.NoActionBar.Fullscreen" />
 
-  <!-- A simple ListView item layout which can contain text and support (single or multiple) item selection. -->
-  <public type="layout" name="simple_selectable_list_item" id="0x01090022" />
+  <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" id="0x01040030" />
+  <public type="string" name="selectTextMode" />
 
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 433c005..95934d5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -710,12 +710,12 @@
     <string name="permdesc_movePackage">Allows an application to move application resources from internal to external media and vice versa.</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_readLogs">read system log files</string>
+    <string name="permlab_readLogs">read sensitive log data</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_readLogs">Allows an application to read from the
         system\'s various log files.  This allows it to discover general
-        information about what you are doing with the phone, but they should
-        not contain any personal or private information.</string>
+        information about what you are doing with the phone, potentially
+        including personal or private information.</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_diagnostic">read/write to resources owned by diag</string>
@@ -1390,7 +1390,7 @@
     <!-- Other IM address type -->
     <string name="imTypeOther">Other</string>
 
-    <!-- Custom IM address type -->
+    <!-- Custom IM protocol type -->
     <string name="imProtocolCustom">Custom</string>
     <!-- AIM IM protocol type -->
     <string name="imProtocolAim">AIM</string>
@@ -1418,6 +1418,15 @@
     <!-- Custom organization type -->
     <string name="orgTypeCustom">Custom</string>
 
+    <!-- Custom SIP address type -->
+    <string name="sipAddressTypeCustom">Custom</string>
+    <!-- Home SIP address type -->
+    <string name="sipAddressTypeHome">Home</string>
+    <!-- Work SIP address type -->
+    <string name="sipAddressTypeWork">Work</string>
+    <!-- Other SIP address type -->
+    <string name="sipAddressTypeOther">Other</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>
@@ -1602,7 +1611,7 @@
 
     <!-- Do not translate.  WebView User Agent string -->
     <string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>)
-        AppleWebKit/534.7 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.7</string>
+        AppleWebKit/534.8 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.8</string>
     <!-- Do not translate.  WebView User Agent targeted content -->
     <string name="web_user_agent_target_content" translatable="false">"Mobile "</string>
 
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 31aa49a..1db6f87 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -563,6 +563,11 @@
         <item name="android:popupBackground">@android:drawable/editbox_dropdown_background_dark</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>
@@ -937,4 +942,32 @@
         <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>
+
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 8a9ebf0..ba4cf9b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -77,6 +77,9 @@
         <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>
 
@@ -97,6 +100,8 @@
 
         <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>
@@ -196,6 +201,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>
@@ -272,6 +279,8 @@
         <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>
@@ -596,6 +605,7 @@
         <item name="editTextBackground">@android:drawable/edit_text_holo_dark</item>
         <item name="editTextColor">?android:attr/textColorPrimary</item>
         <item name="android:windowActionBar">true</item>
+        <item name="android:spinnerStyle">?android:attr/dropDownSpinnerStyle</item>
         <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio_holo_dark</item>
         <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check_holo_dark</item>
     </style>
@@ -608,6 +618,7 @@
     <style name="Theme.Light.Holo">
         <item name="editTextBackground">@android:drawable/edit_text_holo_light</item>
         <item name="android:windowActionBar">true</item>
+        <item name="android:spinnerStyle">?android:attr/dropDownSpinnerStyle</item>
         <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio_holo_light</item>
         <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check_holo_light</item>
     </style>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index f09421b..e449ddb 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1022,7 +1022,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/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/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/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/testing_eclipse.jd b/docs/html/guide/developing/testing/testing_eclipse.jd
index 3258af9..ba7eaba 100644
--- a/docs/html/guide/developing/testing/testing_eclipse.jd
+++ b/docs/html/guide/developing/testing/testing_eclipse.jd
@@ -304,6 +304,7 @@
                 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.
@@ -333,13 +334,33 @@
 </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 Android package name to which it applies.
+    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>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>
     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>testpackage</em></code> is the name of the test package you are
-    running, and <em>app_package</em> is the label for the application under test.
+    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>
 <ul>
     <li>
@@ -354,33 +375,57 @@
         If you have not already installed your test package, then you see
         the message:
         <p>
-            <code>Uploading <em>testpackage</em> onto device '<em>devicename</em>-<em>port</em>'
+            <code>Uploading <em>testfile</em> onto device '<em>devicename</em>-<em>port</em>'
             </code>
         </p>
         <p>
-            then the message <code>Installing <em>testpackage</em></code>.
+            then the message <code>Installing <em>testfile</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: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>app_package</em></code>
+        <code>Project dependency found, installing: <em>appfile</em></code>
         </p>
         <p>
-            then the message <code>Uploading <em>app_name</em>.apk</code> onto device
+            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>app_name</em>.apk</code>
+            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
@@ -412,6 +457,17 @@
     </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>
@@ -449,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/topics/resources/localization.jd b/docs/html/guide/topics/resources/localization.jd
index 3d630c9d..36e12f6 100755
--- a/docs/html/guide/topics/resources/localization.jd
+++ b/docs/html/guide/topics/resources/localization.jd
@@ -433,12 +433,12 @@
 href="{@docRoot}guide/developing/tools/emulator.html">Android Emulator</a>.</p>

 <h4>Creating and using a custom locale</h4>

 

-<p>A &quot;custom&quot; locale is a language/region combination that the

-Android system image does not explicitly support. (For a list of supported 

-locales, see the <a href="{@docRoot}sdk/android-{@sdkCurrentVersion}.html">Android

-Version Notes</a>.) You can test how your application will run in a custom

-locale by creating a custom locale in the emulator. There are two ways to do

-this:</p>

+<p>A &quot;custom&quot; locale is a language/region combination that the Android

+system image does not explicitly support. (For a list of supported locales in

+Android platforms see the Version Notes in the <a

+href="{@docRoot}sdk/index.html">SDK</a> tab). You can test

+how your application will run in a custom locale by creating a custom locale in

+the emulator. There are two ways to do this:</p>

 

 <ul>

   <li>Use the Custom Locale application, which is accessible from the

diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd
index d047b2d..f47a709 100644
--- a/docs/html/guide/topics/ui/dialogs.jd
+++ b/docs/html/guide/topics/ui/dialogs.jd
@@ -478,7 +478,7 @@
         }
     }
 
-    @Override
+    &#64;Override
     protected void onPrepareDialog(int id, Dialog dialog) {
         switch(id) {
         case PROGRESS_DIALOG:
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/sdk/adt_download.jd b/docs/html/sdk/adt_download.jd
index 126c052..5e642d7 100644
--- a/docs/html/sdk/adt_download.jd
+++ b/docs/html/sdk/adt_download.jd
@@ -24,8 +24,8 @@
   <tr>
      <td>0.9.8</td>
      <td><a href="http://dl-ssl.google.com/android/ADT-0.9.8.zip">ADT-0.9.8.zip</a></td>
-     <td><nobr>{@adtZipBytes} bytes</nobr></td>
-     <td>{@adtZipChecksum}</td>
+     <td><nobr>8703591 bytes</nobr></td>
+     <td>22070f8e52924605a3b3abf87c1ba39f</td>
      <td>Requires SDK Tools, Revision 7 <em><nobr>September 2010</nobr></em></td>
   </tr>
   <tr>
diff --git a/docs/html/sdk/android-1.5.jd b/docs/html/sdk/android-1.5.jd
index 0c16b60..9ed798c 100644
--- a/docs/html/sdk/android-1.5.jd
+++ b/docs/html/sdk/android-1.5.jd
@@ -2,8 +2,6 @@
 sdk.platform.version=1.5
 sdk.platform.apiLevel=3
 sdk.platform.majorMinor=major
-sdk.platform.releaseDate=April 2009
-sdk.platform.deployableDate=May 2009
 
 @jd:body
 
@@ -43,7 +41,7 @@
 <em>API Level:</em>&nbsp;<strong>{@sdkPlatformApiLevel}</strong></p>
 
 <p>Android {@sdkPlatformVersion} is a {@sdkPlatformMajorMinor} platform release
-deployable to Android-powered handsets starting in {@sdkPlatformDeployableDate}.
+deployable to Android-powered handsets starting in May 2009.
 The release includes new features for users and developers, as well as changes
 in the Android framework API. </p>
 
diff --git a/docs/html/sdk/android-1.6.jd b/docs/html/sdk/android-1.6.jd
index c4e08ff..a01a5f6 100644
--- a/docs/html/sdk/android-1.6.jd
+++ b/docs/html/sdk/android-1.6.jd
@@ -2,8 +2,6 @@
 sdk.platform.version=1.6
 sdk.platform.apiLevel=4
 sdk.platform.majorMinor=minor
-sdk.platform.releaseDate=December 2009
-sdk.platform.deployableDate=October 2009
 
 @jd:body
 
@@ -43,7 +41,7 @@
 <em>API Level:</em>&nbsp;<strong>{@sdkPlatformApiLevel}</strong></p>
 
 <p>Android {@sdkPlatformVersion} is a {@sdkPlatformMajorMinor} platform release
-deployable to Android-powered handsets since {@sdkPlatformDeployableDate}.
+deployable to Android-powered handsets since October 2009.
 The platform includes new features for users and developers, as well as changes
 in the Android framework API. </p>
 
diff --git a/docs/html/sdk/android-2.0.1.jd b/docs/html/sdk/android-2.0.1.jd
index cacb6bf..0c8afb6 100644
--- a/docs/html/sdk/android-2.0.1.jd
+++ b/docs/html/sdk/android-2.0.1.jd
@@ -2,8 +2,6 @@
 sdk.platform.version=2.0.1
 sdk.platform.apiLevel=6
 sdk.platform.majorMinor=minor
-sdk.platform.releaseDate=December 2009
-sdk.platform.deployableDate=December 2009
 
 @jd:body
 
@@ -44,7 +42,7 @@
 <em>API Level:</em>&nbsp;<strong>{@sdkPlatformApiLevel}</strong></p>
 
 <p>Android {@sdkPlatformVersion} is a {@sdkPlatformMajorMinor} platform release
-deployable to Android-powered handsets starting in {@sdkPlatformDeployableDate}.
+deployable to Android-powered handsets starting in December 2009.
 This release includes minor API
 changes, bug fixes and framework behavioral changes. For information on changes
 and fixes, see the <a href="#api">Framework API</a> section.</p>
diff --git a/docs/html/sdk/android-2.0.jd b/docs/html/sdk/android-2.0.jd
index a430f34..2c31923 100644
--- a/docs/html/sdk/android-2.0.jd
+++ b/docs/html/sdk/android-2.0.jd
@@ -2,8 +2,6 @@
 sdk.platform.version=2.0
 sdk.platform.apiLevel=5
 sdk.platform.majorMinor=major
-sdk.platform.releaseDate=October 2009
-sdk.platform.deployableDate=November 2009
 
 @jd:body
 
@@ -38,7 +36,7 @@
 <em>API Level:</em>&nbsp;<strong>{@sdkPlatformApiLevel}</strong></p>
 
 <p>Android {@sdkPlatformVersion} is a {@sdkPlatformMajorMinor} platform release
-deployable to Android-powered handsets starting in {@sdkPlatformDeployableDate}.
+deployable to Android-powered handsets starting in November 2009.
 The release includes new features for users and developers, as well as changes
 in the Android framework API. </p>
 
diff --git a/docs/html/sdk/android-2.1.jd b/docs/html/sdk/android-2.1.jd
index cd48a72..6eba6f09 100644
--- a/docs/html/sdk/android-2.1.jd
+++ b/docs/html/sdk/android-2.1.jd
@@ -2,7 +2,6 @@
 sdk.platform.version=2.1
 sdk.platform.apiLevel=7
 sdk.platform.majorMinor=minor
-sdk.platform.deployableDate=January 2010
 
 @jd:body
 
@@ -42,7 +41,7 @@
 <em>API Level:</em>&nbsp;<strong>{@sdkPlatformApiLevel}</strong></p>
 
 <p>Android {@sdkPlatformVersion} is a {@sdkPlatformMajorMinor} platform release
-deployable to Android-powered handsets starting in {@sdkPlatformDeployableDate}.
+deployable to Android-powered handsets starting in January 2010.
 This release includes new API
 changes and bug fixes. For information on changes, see the <a href="#api">Framework API</a>
 section.</p>
diff --git a/docs/html/sdk/eclipse-adt.jd b/docs/html/sdk/eclipse-adt.jd
index 4594bb5..a984d56 100644
--- a/docs/html/sdk/eclipse-adt.jd
+++ b/docs/html/sdk/eclipse-adt.jd
@@ -1,5 +1,9 @@
 page.title=ADT Plugin for Eclipse
 sdk.preview=0
+adt.zip.version=0.9.8
+adt.zip.download=ADT-0.9.8.zip
+adt.zip.bytes=8703591
+adt.zip.checksum=22070f8e52924605a3b3abf87c1ba39f
 
 @jd:body
 
@@ -428,7 +432,7 @@
 
 <h3 id="preparing">Configuring the ADT Plugin</h3>
 
-<p>Once you've successfully downnloaded ADT as described above, the next step
+<p>Once you've successfully downloaded ADT as described above, the next step
 is to modify your ADT preferences in Eclipse to point to the Android SDK directory:</p>
 
 <ol>
diff --git a/docs/knowntags.txt b/docs/knowntags.txt
new file mode 100644
index 0000000..5bebabb
--- /dev/null
+++ b/docs/knowntags.txt
@@ -0,0 +1,38 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# The grandfathered list.  We should get rid of these if possible.
+#
+@ToBeFixed
+@stable
+@com.intel.drl.spec_ref
+@ar.org.fitc.spec_ref
+
+# Something about CTS?
+@cts
+
+# Auto-generated info about the SDK
+@sdkCurrent
+@sdkCurrentVersion
+@sdkCurrentRelId
+@sdkPlatformVersion
+@sdkPlatformApiLevel
+@sdkPlatformMajorMinor
+@sdkPlatformReleaseDate
+@sdkPlatformDeployableDate
+@adtZipVersion
+@adtZipDownload
+@adtZipBytes
+@adtZipChecksum
diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java
index 9ee597c..7ec70da 100644
--- a/drm/java/android/drm/DrmManagerClient.java
+++ b/drm/java/android/drm/DrmManagerClient.java
@@ -213,12 +213,12 @@
      * Save DRM rights to specified rights path
      * and make association with content path.
      *
+     * <p class="note">In case of OMA or WM-DRM, rightsPath and contentPath could be null.</p>
+     *
      * @param drmRights DrmRights to be saved
      * @param rightsPath File path where rights to be saved
      * @param contentPath File path where content was saved
      * @throws IOException if failed to save rights information in the given path
-     *
-     * @note In case of OMA or WM-DRM, rightsPath and contentPath could be null
      */
     public void saveRights(
             DrmRights drmRights, String rightsPath, String contentPath) throws IOException {
@@ -256,9 +256,9 @@
      * @param path Path of the content to be handled
      * @param mimeType Mimetype of the object to be handled
      * @return
-     *        true - if the given mimeType or path can be handled
-     *        false - cannot be handled.
-     * @note false will be return in case the state is uninitialized
+     *        true - if the given mimeType or path can be handled.
+     *        false - cannot be handled.  false will be returned in case
+     *        the state is uninitialized
      */
     public boolean canHandle(String path, String mimeType) {
         if ((null == path || path.equals("")) && (null == mimeType || mimeType.equals(""))) {
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index b336995..66ed104a 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -422,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)
@@ -455,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)
      */
@@ -478,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/renderscript/ProgramFragment.java b/graphics/java/android/renderscript/ProgramFragment.java
index 04091a3..00c5cf1 100644
--- a/graphics/java/android/renderscript/ProgramFragment.java
+++ b/graphics/java/android/renderscript/ProgramFragment.java
@@ -62,9 +62,9 @@
         }
     }
 
-    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;
 
@@ -101,7 +101,72 @@
         }
         Slot[] mSlots;
 
+        private void buildShaderString() {
+            mShader  = "//rs_shader_internal\n";
+            mShader += "varying lowp vec4 varColor;\n";
+            mShader += "varying vec4 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;
@@ -126,22 +191,35 @@
             return this;
         }
 
+        @Override
         public ProgramFragment create() {
-            mRS.validate();
-            int[] tmp = new int[MAX_TEXTURE * 2 + 2];
-            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;
+            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;
-            tmp[5] = mVaryingColorEnable ? 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/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java
index b072433..119db69 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,29 +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 Builder(RenderScript rs) {
-            mRS = rs;
-        }
-
-        public Builder setTextureMatrixEnable(boolean enable) {
-            mTextureMatrixEnable = enable;
-            return this;
-        }
-
-        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);
@@ -93,6 +71,68 @@
         }
     }
 
+    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 vec4 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 * ATTRIB_texture0;\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_4(mRS), "texture0");
+            addInput(b.create());
+
+            return super.create();
+        }
+    }
+
 
 
     public static class MatrixAllocation {
@@ -105,9 +145,14 @@
         Matrix4f mTexture;
 
         public Allocation mAlloc;
+        private FieldPacker mIOBuffer;
 
         public MatrixAllocation(RenderScript rs) {
-            mAlloc = Allocation.createSized(rs, Element.createUser(rs, Element.DataType.FLOAT_32), 48);
+            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());
@@ -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 1f3e159..1312036 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -608,7 +608,7 @@
             while(mRun) {
                 rbuf[0] = 0;
                 int msg = mRS.nContextGetMessage(mRS.mContext, rbuf, true);
-                if (msg == 0) {
+                if ((msg == 0) && mRun) {
                     // Can happen for two reasons
                     if (rbuf[0] > 0) {
                         // 1: Buffer needs to be enlarged.
@@ -661,6 +661,10 @@
         validate();
         nContextDeinitToClient(mContext);
         mMessageThread.mRun = false;
+        try {
+            mMessageThread.join();
+        } catch(InterruptedException e) {
+        }
 
         nContextDestroy();
         mContext = 0;
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index f07dbfd8..f386537 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -784,14 +784,14 @@
 static void
 nScriptSetVarI(JNIEnv *_env, jobject _this, RsContext con, jint script, jint slot, jint val)
 {
-    LOG_API("nScriptSetVarI, con(%p), s(%p), slot(%i), val(%i), b(%f), a(%f)", con, (void *)script, slot, val);
+    LOG_API("nScriptSetVarI, con(%p), s(%p), slot(%i), val(%i)", con, (void *)script, slot, val);
     rsScriptSetVarI(con, (RsScript)script, slot, val);
 }
 
 static void
 nScriptSetVarF(JNIEnv *_env, jobject _this, RsContext con, jint script, jint slot, float val)
 {
-    LOG_API("nScriptSetVarI, con(%p), s(%p), slot(%i), val(%i), b(%f), a(%f)", con, (void *)script, slot, val);
+    LOG_API("nScriptSetVarI, con(%p), s(%p), slot(%i), val(%f)", con, (void *)script, slot, val);
     rsScriptSetVarF(con, (RsScript)script, slot, val);
 }
 
diff --git a/include/android_runtime/android_app_NativeActivity.h b/include/android_runtime/android_app_NativeActivity.h
index fdceb84..b49e02a 100644
--- a/include/android_runtime/android_app_NativeActivity.h
+++ b/include/android_runtime/android_app_NativeActivity.h
@@ -18,6 +18,7 @@
 #define _ANDROID_APP_NATIVEACTIVITY_H
 
 #include <ui/InputTransport.h>
+#include <utils/Looper.h>
 
 #include <android/native_activity.h>
 
@@ -69,7 +70,7 @@
     /* Destroys the consumer and releases its input channel. */
     ~AInputQueue();
 
-    void attachLooper(ALooper* looper, int ident, ALooper_callbackFunc* callback, void* data);
+    void attachLooper(ALooper* looper, int ident, ALooper_callbackFunc callback, void* data);
 
     void detachLooper();
 
@@ -103,7 +104,7 @@
     void wakeupDispatch();
 
     android::InputConsumer mConsumer;
-    android::sp<android::PollLoop> mPollLoop;
+    android::sp<android::Looper> mLooper;
 
     int mDispatchKeyRead;
     int mDispatchKeyWrite;
diff --git a/include/camera/Camera.h b/include/camera/Camera.h
index 75cf5ff..f7b3b42 100644
--- a/include/camera/Camera.h
+++ b/include/camera/Camera.h
@@ -82,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
diff --git a/include/camera/CameraParameters.h b/include/camera/CameraParameters.h
index 7c5371a..72d5756 100644
--- a/include/camera/CameraParameters.h
+++ b/include/camera/CameraParameters.h
@@ -354,7 +354,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/gui/SensorEventQueue.h b/include/gui/SensorEventQueue.h
index 6581ae3..97dd391 100644
--- a/include/gui/SensorEventQueue.h
+++ b/include/gui/SensorEventQueue.h
@@ -42,7 +42,7 @@
 
 class ISensorEventConnection;
 class Sensor;
-class PollLoop;
+class Looper;
 
 // ----------------------------------------------------------------------------
 
@@ -69,11 +69,11 @@
     status_t disableSensor(int32_t handle) const;
 
 private:
-    sp<PollLoop> getPollLoop() const;
+    sp<Looper> getLooper() const;
     sp<ISensorEventConnection> mSensorEventConnection;
     sp<SensorChannel> mSensorChannel;
     mutable Mutex mLock;
-    mutable sp<PollLoop> mPollLoop;
+    mutable sp<Looper> mLooper;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/media/MediaProfiles.h b/include/media/MediaProfiles.h
index 0ec7eec..aa97874 100644
--- a/include/media/MediaProfiles.h
+++ b/include/media/MediaProfiles.h
@@ -302,10 +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 *createDefaultCamcorderTimeLapseLowProfile();
-    static CamcorderProfile *createDefaultCamcorderTimeLapseHighProfile();
+
+    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/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/CameraSourceTimeLapse.h b/include/media/stagefright/CameraSourceTimeLapse.h
index e1cd0c8..3b303f8 100644
--- a/include/media/stagefright/CameraSourceTimeLapse.h
+++ b/include/media/stagefright/CameraSourceTimeLapse.h
@@ -43,6 +43,16 @@
 
     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.
@@ -82,14 +92,59 @@
     // 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().
-    bool mCameraIdle;
+    // 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();
@@ -122,6 +177,10 @@
     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.
diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h
index 25d5afb..d6b09dc 100644
--- a/include/ui/EventHub.h
+++ b/include/ui/EventHub.h
@@ -111,10 +111,10 @@
     /* The input device is a multi-touch touchscreen. */
     INPUT_DEVICE_CLASS_TOUCHSCREEN_MT= 0x00000010,
 
-    /* The input device is a directional pad. */
+    /* The input device is a directional pad (implies keyboard, has DPAD keys). */
     INPUT_DEVICE_CLASS_DPAD          = 0x00000020,
 
-    /* The input device is a gamepad (implies keyboard). */
+    /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
     INPUT_DEVICE_CLASS_GAMEPAD       = 0x00000040,
 
     /* The input device has switches. */
diff --git a/include/ui/FramebufferNativeWindow.h b/include/ui/FramebufferNativeWindow.h
index 0f4594f..c913355 100644
--- a/include/ui/FramebufferNativeWindow.h
+++ b/include/ui/FramebufferNativeWindow.h
@@ -56,6 +56,9 @@
     status_t setUpdateRectangle(const Rect& updateRect);
     status_t compositionComplete();
     
+    // for debugging only
+    int getCurrentBufferIndex() const;
+
 private:
     friend class LightRefBase<FramebufferNativeWindow>;    
     ~FramebufferNativeWindow(); // this class cannot be overloaded
@@ -77,6 +80,7 @@
     int32_t mNumBuffers;
     int32_t mNumFreeBuffers;
     int32_t mBufferHead;
+    int32_t mCurrentBufferIndex;
     bool mUpdateOnDemand;
 };
     
diff --git a/include/ui/GraphicLog.h b/include/ui/GraphicLog.h
new file mode 100644
index 0000000..ee1b09a
--- /dev/null
+++ b/include/ui/GraphicLog.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_GRAPHIC_LOG_H
+#define _UI_GRAPHIC_LOG_H
+
+#include <utils/Singleton.h>
+#include <cutils/compiler.h>
+
+namespace android {
+
+class GraphicLog : public Singleton<GraphicLog>
+{
+    int32_t mEnabled;
+    static void logImpl(int32_t tag, int32_t buffer);
+    static void logImpl(int32_t tag, int32_t identity, int32_t buffer);
+
+public:
+    enum {
+        SF_APP_DEQUEUE_BEFORE   = 60100,
+        SF_APP_DEQUEUE_AFTER    = 60101,
+        SF_APP_LOCK_BEFORE      = 60102,
+        SF_APP_LOCK_AFTER       = 60103,
+        SF_APP_QUEUE            = 60104,
+
+        SF_REPAINT              = 60105,
+        SF_COMPOSITION_COMPLETE = 60106,
+        SF_UNLOCK_CLIENTS       = 60107,
+        SF_SWAP_BUFFERS         = 60108,
+        SF_REPAINT_DONE         = 60109,
+
+        SF_FB_POST_BEFORE       = 60110,
+        SF_FB_POST_AFTER        = 60111,
+        SF_FB_DEQUEUE_BEFORE    = 60112,
+        SF_FB_DEQUEUE_AFTER     = 60113,
+        SF_FB_LOCK_BEFORE       = 60114,
+        SF_FB_LOCK_AFTER        = 60115,
+    };
+
+    inline void log(int32_t tag, int32_t buffer) {
+        if (CC_UNLIKELY(mEnabled))
+            logImpl(tag, buffer);
+    }
+    inline void log(int32_t tag, int32_t identity, int32_t buffer) {
+        if (CC_UNLIKELY(mEnabled))
+            logImpl(tag, identity, buffer);
+    }
+
+    GraphicLog();
+
+    void setEnabled(bool enable);
+};
+
+}
+
+#endif // _UI_GRAPHIC_LOG_H
+
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index f00f2db..96b4faed 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -25,11 +25,12 @@
 #include <utils/Timers.h>
 #include <utils/RefBase.h>
 #include <utils/String8.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 #include <utils/Pool.h>
 
 #include <stddef.h>
 #include <unistd.h>
+#include <limits.h>
 
 
 namespace android {
@@ -80,9 +81,8 @@
  */
 struct InputTarget {
     enum {
-        /* This flag indicates that subsequent event delivery should be held until the
-         * current event is delivered to this target or a timeout occurs. */
-        FLAG_SYNC = 0x01,
+        /* This flag indicates that the event is being delivered to a foreground application. */
+        FLAG_FOREGROUND = 0x01,
 
         /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
          * of the area of this target and so should instead be delivered as an
@@ -108,9 +108,6 @@
     // Flags for the input target.
     int32_t flags;
 
-    // The timeout for event delivery to this target in nanoseconds.  Or -1 if none.
-    nsecs_t timeout;
-
     // The x and y offset to add to a MotionEvent as it is delivered.
     // (ignored for KeyEvents)
     float xOffset, yOffset;
@@ -118,6 +115,125 @@
 
 
 /*
+ * An input window describes the bounds of a window that can receive input.
+ */
+struct InputWindow {
+    // Window flags from WindowManager.LayoutParams
+    enum {
+        FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001,
+        FLAG_DIM_BEHIND        = 0x00000002,
+        FLAG_BLUR_BEHIND        = 0x00000004,
+        FLAG_NOT_FOCUSABLE      = 0x00000008,
+        FLAG_NOT_TOUCHABLE      = 0x00000010,
+        FLAG_NOT_TOUCH_MODAL    = 0x00000020,
+        FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
+        FLAG_KEEP_SCREEN_ON     = 0x00000080,
+        FLAG_LAYOUT_IN_SCREEN   = 0x00000100,
+        FLAG_LAYOUT_NO_LIMITS   = 0x00000200,
+        FLAG_FULLSCREEN      = 0x00000400,
+        FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800,
+        FLAG_DITHER             = 0x00001000,
+        FLAG_SECURE             = 0x00002000,
+        FLAG_SCALED             = 0x00004000,
+        FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000,
+        FLAG_LAYOUT_INSET_DECOR = 0x00010000,
+        FLAG_ALT_FOCUSABLE_IM = 0x00020000,
+        FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
+        FLAG_SHOW_WHEN_LOCKED = 0x00080000,
+        FLAG_SHOW_WALLPAPER = 0x00100000,
+        FLAG_TURN_SCREEN_ON = 0x00200000,
+        FLAG_DISMISS_KEYGUARD = 0x00400000,
+        FLAG_IMMERSIVE = 0x00800000,
+        FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000,
+        FLAG_COMPATIBLE_WINDOW = 0x20000000,
+        FLAG_SYSTEM_ERROR = 0x40000000,
+    };
+
+    // Window types from WindowManager.LayoutParams
+    enum {
+        FIRST_APPLICATION_WINDOW = 1,
+        TYPE_BASE_APPLICATION   = 1,
+        TYPE_APPLICATION        = 2,
+        TYPE_APPLICATION_STARTING = 3,
+        LAST_APPLICATION_WINDOW = 99,
+        FIRST_SUB_WINDOW        = 1000,
+        TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW,
+        TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1,
+        TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2,
+        TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3,
+        TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4,
+        LAST_SUB_WINDOW         = 1999,
+        FIRST_SYSTEM_WINDOW     = 2000,
+        TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW,
+        TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1,
+        TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2,
+        TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3,
+        TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4,
+        TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5,
+        TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6,
+        TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7,
+        TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8,
+        TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9,
+        TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10,
+        TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11,
+        TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12,
+        TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13,
+        TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14,
+        LAST_SYSTEM_WINDOW      = 2999,
+    };
+
+    sp<InputChannel> inputChannel;
+    String8 name;
+    int32_t layoutParamsFlags;
+    int32_t layoutParamsType;
+    nsecs_t dispatchingTimeout;
+    int32_t frameLeft;
+    int32_t frameTop;
+    int32_t frameRight;
+    int32_t frameBottom;
+    int32_t visibleFrameLeft;
+    int32_t visibleFrameTop;
+    int32_t visibleFrameRight;
+    int32_t visibleFrameBottom;
+    int32_t touchableAreaLeft;
+    int32_t touchableAreaTop;
+    int32_t touchableAreaRight;
+    int32_t touchableAreaBottom;
+    bool visible;
+    bool canReceiveKeys;
+    bool hasFocus;
+    bool hasWallpaper;
+    bool paused;
+    int32_t layer;
+    int32_t ownerPid;
+    int32_t ownerUid;
+
+    bool visibleFrameIntersects(const InputWindow* other) const;
+    bool touchableAreaContainsPoint(int32_t x, int32_t y) const;
+};
+
+
+/*
+ * A private handle type used by the input manager to track the window.
+ */
+class InputApplicationHandle : public RefBase {
+protected:
+    InputApplicationHandle() { }
+    virtual ~InputApplicationHandle() { }
+};
+
+
+/*
+ * An input application describes properties of an application that can receive input.
+ */
+struct InputApplication {
+    String8 name;
+    nsecs_t dispatchingTimeout;
+    sp<InputApplicationHandle> handle;
+};
+
+
+/*
  * Input dispatcher policy interface.
  *
  * The input reader policy is used by the input reader to interact with the Window Manager
@@ -135,47 +251,41 @@
     /* Notifies the system that a configuration change has occurred. */
     virtual void notifyConfigurationChanged(nsecs_t when) = 0;
 
+    /* Notifies the system that an application is not responding.
+     * Returns a new timeout to continue waiting, or 0 to abort dispatch. */
+    virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+            const sp<InputChannel>& inputChannel) = 0;
+
     /* Notifies the system that an input channel is unrecoverably broken. */
     virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel) = 0;
 
-    /* Notifies the system that an input channel is not responding.
-     * Returns true and a new timeout value if the dispatcher should keep waiting.
-     * Otherwise returns false. */
-    virtual bool notifyInputChannelANR(const sp<InputChannel>& inputChannel,
-            nsecs_t& outNewTimeout) = 0;
-
-    /* Notifies the system that an input channel recovered from ANR. */
-    virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) = 0;
-
     /* Gets the key repeat initial timeout or -1 if automatic key repeating is disabled. */
     virtual nsecs_t getKeyRepeatTimeout() = 0;
 
     /* Gets the key repeat inter-key delay. */
     virtual nsecs_t getKeyRepeatDelay() = 0;
 
-    /* Waits for key event input targets to become available.
-     * If the event is being injected, injectorPid and injectorUid should specify the
-     * process id and used id of the injecting application, otherwise they should both
-     * be -1.
-     * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */
-    virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
-            int32_t injectorPid, int32_t injectorUid,
-            Vector<InputTarget>& outTargets) = 0;
-
-    /* Waits for motion event targets to become available.
-     * If the event is being injected, injectorPid and injectorUid should specify the
-     * process id and used id of the injecting application, otherwise they should both
-     * be -1.
-     * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */
-    virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
-            int32_t injectorPid, int32_t injectorUid,
-            Vector<InputTarget>& outTargets) = 0;
-
     /* Gets the maximum suggested event delivery rate per second.
      * This value is used to throttle motion event movement actions on a per-device
      * basis.  It is not intended to be a hard limit.
      */
     virtual int32_t getMaxEventsPerSecond() = 0;
+
+    /* Allows the policy a chance to intercept a key before dispatching. */
+    virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
+            const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+
+    /* Poke user activity for an event dispatched to a window. */
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType) = 0;
+
+    /* Checks whether a given application pid/uid has permission to inject input events
+     * into other applications.
+     *
+     * This method is special in that its implementation promises to be non-reentrant and
+     * is safe to call while holding other locks.  (Most other methods make no such guarantees!)
+     */
+    virtual bool checkInjectEventsPermissionNonReentrant(
+            int32_t injectorPid, int32_t injectorUid) = 0;
 };
 
 
@@ -187,6 +297,11 @@
     virtual ~InputDispatcherInterface() { }
 
 public:
+    /* Dumps the state of the input dispatcher.
+     *
+     * This method may be called on any thread (usually by the input manager). */
+    virtual void dump(String8& dump) = 0;
+
     /* Runs a single iteration of the dispatch loop.
      * Nominally processes one queued event, a timeout, or a response from an input consumer.
      *
@@ -199,7 +314,6 @@
      * These methods should only be called on the input reader thread.
      */
     virtual void notifyConfigurationChanged(nsecs_t eventTime) = 0;
-    virtual void notifyAppSwitchComing(nsecs_t eventTime) = 0;
     virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
             uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
             int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
@@ -219,21 +333,30 @@
     virtual int32_t injectInputEvent(const InputEvent* event,
             int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0;
 
-    /* Preempts input dispatch in progress by making pending synchronous
-     * dispatches asynchronous instead.  This method is generally called during a focus
-     * transition from one application to the next so as to enable the new application
-     * to start receiving input as soon as possible without having to wait for the
-     * old application to finish up.
+    /* Sets the list of input windows.
      *
      * This method may be called on any thread (usually by the input manager).
      */
-    virtual void preemptInputDispatch() = 0;
+    virtual void setInputWindows(const Vector<InputWindow>& inputWindows) = 0;
+
+    /* Sets the focused application.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void setFocusedApplication(const InputApplication* inputApplication) = 0;
+
+    /* Sets the input dispatching mode.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void setInputDispatchMode(bool enabled, bool frozen) = 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.
      *
      * These methods may be called on any thread (usually by the input manager).
      */
-    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0;
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) = 0;
     virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
 };
 
@@ -261,10 +384,11 @@
 public:
     explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
 
+    virtual void dump(String8& dump);
+
     virtual void dispatchOnce();
 
     virtual void notifyConfigurationChanged(nsecs_t eventTime);
-    virtual void notifyAppSwitchComing(nsecs_t eventTime);
     virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
             uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
             int32_t scanCode, int32_t metaState, nsecs_t downTime);
@@ -277,9 +401,11 @@
     virtual int32_t injectInputEvent(const InputEvent* event,
             int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
 
-    virtual void preemptInputDispatch();
+    virtual void setInputWindows(const Vector<InputWindow>& inputWindows);
+    virtual void setFocusedApplication(const InputApplication* inputApplication);
+    virtual void setInputDispatchMode(bool enabled, bool frozen);
 
-    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel);
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor);
     virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
 
 private:
@@ -307,9 +433,11 @@
         int32_t injectorUid;      // -1 if not injected
 
         bool dispatchInProgress; // initially false, set to true while dispatching
-        int32_t pendingSyncDispatches; // the number of synchronous dispatches in progress
+        int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
 
         inline bool isInjected() { return injectorPid >= 0; }
+
+        void recycle();
     };
 
     struct ConfigurationChangedEntry : EventEntry {
@@ -326,6 +454,17 @@
         int32_t metaState;
         int32_t repeatCount;
         nsecs_t downTime;
+
+        bool syntheticRepeat; // set to true for synthetic key repeats
+
+        enum InterceptKeyResult {
+            INTERCEPT_KEY_RESULT_UNKNOWN,
+            INTERCEPT_KEY_RESULT_SKIP,
+            INTERCEPT_KEY_RESULT_CONTINUE,
+        };
+        InterceptKeyResult interceptKeyResult; // set based on the interception result
+
+        void recycle();
     };
 
     struct MotionSample {
@@ -362,7 +501,6 @@
         int32_t targetFlags;
         float xOffset;
         float yOffset;
-        nsecs_t timeout;
 
         // True if dispatch has started.
         bool inProgress;
@@ -380,8 +518,8 @@
         //   will be set to NULL.
         MotionSample* tailMotionSample;
 
-        inline bool isSyncTarget() {
-            return targetFlags & InputTarget::FLAG_SYNC;
+        inline bool hasForegroundTarget() const {
+            return targetFlags & InputTarget::FLAG_FOREGROUND;
         }
     };
 
@@ -413,37 +551,43 @@
 
         // parameters for the command (usage varies by command)
         sp<Connection> connection;
+        nsecs_t eventTime;
+        KeyEntry* keyEntry;
+        sp<InputChannel> inputChannel;
+        sp<InputApplicationHandle> inputApplicationHandle;
+        int32_t windowType;
+        int32_t userActivityEventType;
     };
 
     // Generic queue implementation.
     template <typename T>
     struct Queue {
-        T head;
-        T tail;
+        T headSentinel;
+        T tailSentinel;
 
         inline Queue() {
-            head.prev = NULL;
-            head.next = & tail;
-            tail.prev = & head;
-            tail.next = NULL;
+            headSentinel.prev = NULL;
+            headSentinel.next = & tailSentinel;
+            tailSentinel.prev = & headSentinel;
+            tailSentinel.next = NULL;
         }
 
-        inline bool isEmpty() {
-            return head.next == & tail;
+        inline bool isEmpty() const {
+            return headSentinel.next == & tailSentinel;
         }
 
         inline void enqueueAtTail(T* entry) {
-            T* last = tail.prev;
+            T* last = tailSentinel.prev;
             last->next = entry;
             entry->prev = last;
-            entry->next = & tail;
-            tail.prev = entry;
+            entry->next = & tailSentinel;
+            tailSentinel.prev = entry;
         }
 
         inline void enqueueAtHead(T* entry) {
-            T* first = head.next;
-            head.next = entry;
-            entry->prev = & head;
+            T* first = headSentinel.next;
+            headSentinel.next = entry;
+            entry->prev = & headSentinel;
             entry->next = first;
             first->prev = entry;
         }
@@ -454,10 +598,12 @@
         }
 
         inline T* dequeueAtHead() {
-            T* first = head.next;
+            T* first = headSentinel.next;
             dequeue(first);
             return first;
         }
+
+        uint32_t count() const;
     };
 
     /* Allocates queue entries and performs reference counting as needed. */
@@ -476,7 +622,8 @@
                 float xPrecision, float yPrecision,
                 nsecs_t downTime, uint32_t pointerCount,
                 const int32_t* pointerIds, const PointerCoords* pointerCoords);
-        DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry);
+        DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry,
+                int32_t targetFlags, float xOffset, float yOffset);
         CommandEntry* obtainCommandEntry(Command command);
 
         void releaseEventEntry(EventEntry* entry);
@@ -500,6 +647,85 @@
         void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime);
     };
 
+    /* Tracks dispatched key and motion event state so that cancelation events can be
+     * synthesized when events are dropped. */
+    class InputState {
+    public:
+        // Specifies whether a given event will violate input state consistency.
+        enum Consistency {
+            // The event is consistent with the current input state.
+            CONSISTENT,
+            // The event is inconsistent with the current input state but applications
+            // will tolerate it.  eg. Down followed by another down.
+            TOLERABLE,
+            // The event is inconsistent with the current input state and will probably
+            // cause applications to crash.  eg. Up without prior down, move with
+            // unexpected number of pointers.
+            BROKEN
+        };
+
+        InputState();
+        ~InputState();
+
+        // Returns true if there is no state to be canceled.
+        bool isNeutral() const;
+
+        // Returns true if the input state believes it is out of sync.
+        bool isOutOfSync() const;
+
+        // Sets the input state to be out of sync if it is not neutral.
+        void setOutOfSync();
+
+        // Resets the input state out of sync flag.
+        void resetOutOfSync();
+
+        // Records tracking information for an event that has just been published.
+        // Returns whether the event is consistent with the current input state.
+        Consistency trackEvent(const EventEntry* entry);
+
+        // Records tracking information for a key event that has just been published.
+        // Returns whether the event is consistent with the current input state.
+        Consistency trackKey(const KeyEntry* entry);
+
+        // Records tracking information for a motion event that has just been published.
+        // Returns whether the event is consistent with the current input state.
+        Consistency trackMotion(const MotionEntry* entry);
+
+        // Synthesizes cancelation events for the current state.
+        void synthesizeCancelationEvents(Allocator* allocator,
+                Vector<EventEntry*>& outEvents) const;
+
+        // Clears the current state.
+        void clear();
+
+    private:
+        bool mIsOutOfSync;
+
+        struct KeyMemento {
+            int32_t deviceId;
+            int32_t source;
+            int32_t keyCode;
+            int32_t scanCode;
+            nsecs_t downTime;
+        };
+
+        struct MotionMemento {
+            int32_t deviceId;
+            int32_t source;
+            float xPrecision;
+            float yPrecision;
+            nsecs_t downTime;
+            uint32_t pointerCount;
+            int32_t pointerIds[MAX_POINTERS];
+            PointerCoords pointerCoords[MAX_POINTERS];
+
+            void setPointers(const MotionEntry* entry);
+        };
+
+        Vector<KeyMemento> mKeyMementos;
+        Vector<MotionMemento> mMotionMementos;
+    };
+
     /* Manages the dispatch state associated with a single input channel. */
     class Connection : public RefBase {
     protected:
@@ -511,8 +737,6 @@
             STATUS_NORMAL,
             // An unrecoverable communication error has occurred.
             STATUS_BROKEN,
-            // The client is not responding.
-            STATUS_NOT_RESPONDING,
             // The input channel has been unregistered.
             STATUS_ZOMBIE
         };
@@ -520,12 +744,11 @@
         Status status;
         sp<InputChannel> inputChannel;
         InputPublisher inputPublisher;
+        InputState inputState;
         Queue<DispatchEntry> outboundQueue;
-        nsecs_t nextTimeoutTime; // next timeout time (LONG_LONG_MAX if none)
 
         nsecs_t lastEventTime; // the time when the event was originally captured
         nsecs_t lastDispatchTime; // the time when the last event was dispatched
-        nsecs_t lastANRTime; // the time when the last ANR was recorded
 
         explicit Connection(const sp<InputChannel>& inputChannel);
 
@@ -537,31 +760,17 @@
         // Returns NULL if not found.
         DispatchEntry* findQueuedDispatchEntryForEvent(const EventEntry* eventEntry) const;
 
-        // Determine whether this connection has a pending synchronous dispatch target.
-        // Since there can only ever be at most one such target at a time, if there is one,
-        // it must be at the tail because nothing else can be enqueued after it.
-        inline bool hasPendingSyncTarget() {
-            return ! outboundQueue.isEmpty() && outboundQueue.tail.prev->isSyncTarget();
-        }
-
         // Gets the time since the current event was originally obtained from the input driver.
-        inline double getEventLatencyMillis(nsecs_t currentTime) {
+        inline double getEventLatencyMillis(nsecs_t currentTime) const {
             return (currentTime - lastEventTime) / 1000000.0;
         }
 
         // Gets the time since the current event entered the outbound dispatch queue.
-        inline double getDispatchLatencyMillis(nsecs_t currentTime) {
+        inline double getDispatchLatencyMillis(nsecs_t currentTime) const {
             return (currentTime - lastDispatchTime) / 1000000.0;
         }
 
-        // Gets the time since the current event ANR was declared, if applicable.
-        inline double getANRLatencyMillis(nsecs_t currentTime) {
-            return (currentTime - lastANRTime) / 1000000.0;
-        }
-
         status_t initialize();
-
-        void setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout);
     };
 
     sp<InputDispatcherPolicyInterface> mPolicy;
@@ -569,15 +778,32 @@
     Mutex mLock;
 
     Allocator mAllocator;
-    sp<PollLoop> mPollLoop;
+    sp<Looper> mLooper;
 
+    EventEntry* mPendingEvent;
     Queue<EventEntry> mInboundQueue;
     Queue<CommandEntry> mCommandQueue;
 
+    Vector<EventEntry*> mTempCancelationEvents;
+
+    void dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, nsecs_t keyRepeatDelay,
+            nsecs_t* nextWakeupTime);
+
+    // Enqueues an inbound event.  Returns true if mLooper->wake() should be called.
+    bool enqueueInboundEventLocked(EventEntry* entry);
+
+    // App switch latency optimization.
+    nsecs_t mAppSwitchDueTime;
+
+    static bool isAppSwitchKey(int32_t keyCode);
+    bool isAppSwitchPendingLocked();
+    bool detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry);
+    void resetPendingAppSwitchLocked(bool handled);
+
     // All registered connections mapped by receive pipe file descriptor.
     KeyedVector<int, sp<Connection> > mConnectionsByReceiveFd;
 
-    ssize_t getConnectionIndex(const sp<InputChannel>& inputChannel);
+    ssize_t getConnectionIndexLocked(const sp<InputChannel>& inputChannel);
 
     // Active connections are connections that have a non-empty outbound queue.
     // We don't use a ref-counted pointer here because we explicitly abort connections
@@ -585,30 +811,19 @@
     // and the connection itself to be deactivated.
     Vector<Connection*> mActiveConnections;
 
-    // List of connections that have timed out.  Only used by dispatchOnce()
-    // We don't use a ref-counted pointer here because it is not possible for a connection
-    // to be unregistered while processing timed out connections since we hold the lock for
-    // the duration.
-    Vector<Connection*> mTimedOutConnections;
+    // Input channels that will receive a copy of all input events.
+    Vector<sp<InputChannel> > mMonitoringChannels;
 
-    // Preallocated key and motion event objects used only to ask the input dispatcher policy
-    // for the targets of an event that is to be dispatched.
+    // Preallocated key event object used for policy inquiries.
     KeyEvent mReusableKeyEvent;
-    MotionEvent mReusableMotionEvent;
-
-    // The input targets that were most recently identified for dispatch.
-    // If there is a synchronous event dispatch in progress, the current input targets will
-    // remain unchanged until the dispatch has completed or been aborted.
-    Vector<InputTarget> mCurrentInputTargets;
-    bool mCurrentInputTargetsValid; // false while targets are being recomputed
 
     // Event injection and synchronization.
     Condition mInjectionResultAvailableCondition;
-    EventEntry* createEntryFromInputEventLocked(const InputEvent* event);
+    EventEntry* createEntryFromInjectedInputEventLocked(const InputEvent* event);
     void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
 
     Condition mInjectionSyncFinishedCondition;
-    void decrementPendingSyncDispatchesLocked(EventEntry* entry);
+    void decrementPendingForegroundDispatchesLocked(EventEntry* entry);
 
     // Throttling state.
     struct ThrottleState {
@@ -622,36 +837,109 @@
     } mThrottleState;
 
     // Key repeat tracking.
-    // XXX Move this up to the input reader instead.
     struct KeyRepeatState {
         KeyEntry* lastKeyEntry; // or null if no repeat
         nsecs_t nextRepeatTime;
     } mKeyRepeatState;
 
     void resetKeyRepeatLocked();
+    KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime, nsecs_t keyRepeatTimeout);
 
     // Deferred command processing.
     bool runCommandsLockedInterruptible();
     CommandEntry* postCommandLocked(Command command);
 
-    // Process events that have just been dequeued from the head of the input queue.
-    void processConfigurationChangedLockedInterruptible(
-            nsecs_t currentTime, ConfigurationChangedEntry* entry);
-    void processKeyLockedInterruptible(
-            nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout);
-    void processKeyRepeatLockedInterruptible(
-            nsecs_t currentTime, nsecs_t keyRepeatTimeout);
-    void processMotionLockedInterruptible(
-            nsecs_t currentTime, MotionEntry* entry);
+    // Inbound event processing.
+    void drainInboundQueueLocked();
+    void releasePendingEventLocked();
+    void releaseInboundEventLocked(EventEntry* entry);
+    bool isEventFromReliableSourceLocked(EventEntry* entry);
 
-    // Identify input targets for an event and dispatch to them.
-    void identifyInputTargetsAndDispatchKeyLockedInterruptible(
-            nsecs_t currentTime, KeyEntry* entry);
-    void identifyInputTargetsAndDispatchMotionLockedInterruptible(
-            nsecs_t currentTime, MotionEntry* entry);
+    // Dispatch state.
+    bool mDispatchEnabled;
+    bool mDispatchFrozen;
+    Vector<InputWindow> mWindows;
+    Vector<InputWindow*> mWallpaperWindows;
+
+    // Focus tracking for keys, trackball, etc.
+    InputWindow* mFocusedWindow;
+
+    // Focus tracking for touch.
+    bool mTouchDown;
+    InputWindow* mTouchedWindow;                   // primary target for current down
+    bool mTouchedWindowIsObscured;                 // true if other windows may obscure the target
+    Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets
+    struct OutsideTarget {
+        InputWindow* window;
+        bool obscured;
+    };
+    Vector<OutsideTarget> mTempTouchedOutsideTargets; // temporary outside touch targets
+    Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets
+
+    // Focused application.
+    InputApplication* mFocusedApplication;
+    InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication
+    void releaseFocusedApplicationLocked();
+
+    // Dispatch inbound events.
+    bool dispatchConfigurationChangedLocked(
+            nsecs_t currentTime, ConfigurationChangedEntry* entry);
+    bool dispatchKeyLocked(
+            nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
+            bool dropEvent, nsecs_t* nextWakeupTime);
+    bool dispatchMotionLocked(
+            nsecs_t currentTime, MotionEntry* entry,
+            bool dropEvent, nsecs_t* nextWakeupTime);
     void dispatchEventToCurrentInputTargetsLocked(
             nsecs_t currentTime, EventEntry* entry, bool resumeWithAppendedMotionSample);
 
+    void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry);
+    void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry);
+
+    // The input targets that were most recently identified for dispatch.
+    bool mCurrentInputTargetsValid; // false while targets are being recomputed
+    Vector<InputTarget> mCurrentInputTargets;
+    int32_t mCurrentInputWindowType;
+    sp<InputChannel> mCurrentInputChannel;
+
+    enum InputTargetWaitCause {
+        INPUT_TARGET_WAIT_CAUSE_NONE,
+        INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY,
+        INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,
+    };
+
+    InputTargetWaitCause mInputTargetWaitCause;
+    nsecs_t mInputTargetWaitStartTime;
+    nsecs_t mInputTargetWaitTimeoutTime;
+    bool mInputTargetWaitTimeoutExpired;
+
+    // Finding targets for input events.
+    void resetTargetsLocked();
+    void commitTargetsLocked(const InputWindow* window);
+    int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry,
+            const InputApplication* application, const InputWindow* window,
+            nsecs_t* nextWakeupTime);
+    void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+            const sp<InputChannel>& inputChannel);
+    nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime);
+    void resetANRTimeoutsLocked();
+
+    int32_t findFocusedWindowLocked(nsecs_t currentTime, const EventEntry* entry,
+            nsecs_t* nextWakeupTime, InputWindow** outWindow);
+    int32_t findTouchedWindowLocked(nsecs_t currentTime, const MotionEntry* entry,
+            nsecs_t* nextWakeupTime, InputWindow** outWindow);
+
+    void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags);
+    void addMonitoringTargetsLocked();
+    void pokeUserActivityLocked(nsecs_t eventTime, int32_t windowType, int32_t eventType);
+    bool checkInjectionPermission(const InputWindow* window,
+            int32_t injectorPid, int32_t injectorUid);
+    bool isWindowObscuredLocked(const InputWindow* window);
+    bool isWindowFinishedWithPreviousInputLocked(const InputWindow* window);
+    void releaseTouchedWindowLocked();
+    String8 getApplicationWindowLabelLocked(const InputApplication* application,
+            const InputWindow* window);
+
     // Manage the dispatch cycle for a single connection.
     // These methods are deliberately not Interruptible because doing all of the work
     // with the mutex held makes it easier to ensure that connection invariants are maintained.
@@ -661,12 +949,15 @@
             bool resumeWithAppendedMotionSample);
     void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
     void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
-    void timeoutDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
-    void resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
-            const sp<Connection>& connection, nsecs_t newTimeout);
+    void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
     void abortDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
             bool broken);
-    static bool handleReceiveCallback(int receiveFd, int events, void* data);
+    void drainOutboundQueueLocked(Connection* connection);
+    static int handleReceiveCallback(int receiveFd, int events, void* data);
+
+    // Dump state.
+    void dumpDispatchStateLocked(String8& dump);
+    void logDispatchStateLocked();
 
     // Add or remove a connection to the mActiveConnections vector.
     void activateConnectionLocked(Connection* connection);
@@ -676,16 +967,23 @@
     void onDispatchCycleStartedLocked(
             nsecs_t currentTime, const sp<Connection>& connection);
     void onDispatchCycleFinishedLocked(
-            nsecs_t currentTime, const sp<Connection>& connection, bool recoveredFromANR);
-    void onDispatchCycleANRLocked(
             nsecs_t currentTime, const sp<Connection>& connection);
     void onDispatchCycleBrokenLocked(
             nsecs_t currentTime, const sp<Connection>& connection);
+    void onANRLocked(
+            nsecs_t currentTime, const InputApplication* application, const InputWindow* window,
+            nsecs_t eventTime, nsecs_t waitStartTime);
 
     // Outbound policy interactions.
+    void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry);
     void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
-    void doNotifyInputChannelANRLockedInterruptible(CommandEntry* commandEntry);
-    void doNotifyInputChannelRecoveredFromANRLockedInterruptible(CommandEntry* commandEntry);
+    void doNotifyANRLockedInterruptible(CommandEntry* commandEntry);
+    void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry);
+    void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry);
+
+    // Statistics gathering.
+    void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+            int32_t injectionResult, nsecs_t timeSpentWaitingForApplication);
 };
 
 /* Enqueues and dispatches input events, endlessly. */
@@ -702,4 +1000,4 @@
 
 } // namespace android
 
-#endif // _UI_INPUT_DISPATCHER_PRIV_H
+#endif // _UI_INPUT_DISPATCHER_H
diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h
index 4012c69..568568b 100644
--- a/include/ui/InputManager.h
+++ b/include/ui/InputManager.h
@@ -72,51 +72,11 @@
     /* Stops the input manager threads and waits for them to exit. */
     virtual status_t stop() = 0;
 
-    /* Registers an input channel prior to using it as the target of an event. */
-    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0;
+    /* Gets the input reader. */
+    virtual sp<InputReaderInterface> getReader() = 0;
 
-    /* Unregisters an input channel. */
-    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
-
-    /* Injects an input event and optionally waits for sync.
-     * The synchronization mode determines whether the method blocks while waiting for
-     * input injection to proceed.
-     * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
-     */
-    virtual int32_t injectInputEvent(const InputEvent* event,
-            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0;
-
-    /* Preempts input dispatch in progress by making pending synchronous
-     * dispatches asynchronous instead.  This method is generally called during a focus
-     * transition from one application to the next so as to enable the new application
-     * to start receiving input as soon as possible without having to wait for the
-     * old application to finish up.
-     */
-    virtual void preemptInputDispatch() = 0;
-
-    /* Gets input device configuration. */
-    virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0;
-
-    /* Gets information about the specified input device.
-     * Returns OK if the device information was obtained or NAME_NOT_FOUND if there
-     * was no such device.
-     */
-    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0;
-
-    /* Gets the list of all registered device ids. */
-    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0;
-
-    /* Queries current input state. */
-    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t scanCode) = 0;
-    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t keyCode) = 0;
-    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
-            int32_t sw) = 0;
-
-    /* Determines whether physical keys exist for the given framework-domain key codes. */
-    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
-            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0;
+    /* Gets the input dispatcher. */
+    virtual sp<InputDispatcherInterface> getDispatcher() = 0;
 };
 
 class InputManager : public InputManagerInterface {
@@ -137,25 +97,8 @@
     virtual status_t start();
     virtual status_t stop();
 
-    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel);
-    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
-
-    virtual int32_t injectInputEvent(const InputEvent* event,
-            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
-
-    virtual void preemptInputDispatch();
-
-    virtual void getInputConfiguration(InputConfiguration* outConfiguration);
-    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo);
-    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds);
-    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t scanCode);
-    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
-            int32_t keyCode);
-    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
-            int32_t sw);
-    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
-            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
+    virtual sp<InputReaderInterface> getReader();
+    virtual sp<InputDispatcherInterface> getDispatcher();
 
 private:
     sp<InputReaderInterface> mReader;
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
index 7a089a45..903c3c4 100644
--- a/include/ui/InputReader.h
+++ b/include/ui/InputReader.h
@@ -95,10 +95,6 @@
 
         // The input dispatcher should dispatch the input to the application.
         ACTION_DISPATCH = 0x00000001,
-
-        // The input dispatcher should perform special filtering in preparation for
-        // a pending app switch.
-        ACTION_APP_SWITCH_COMING = 0x00000002,
     };
 
     /* Gets information about the display with the specified id.
@@ -168,6 +164,11 @@
     virtual ~InputReaderInterface() { }
 
 public:
+    /* Dumps the state of the input reader.
+     *
+     * This method may be called on any thread (usually by the input manager). */
+    virtual void dump(String8& dump) = 0;
+
     /* Runs a single iteration of the processing loop.
      * Nominally reads and processes one incoming message from the EventHub.
      *
@@ -240,6 +241,8 @@
             const sp<InputDispatcherInterface>& dispatcher);
     virtual ~InputReader();
 
+    virtual void dump(String8& dump);
+
     virtual void loopOnce();
 
     virtual void getInputConfiguration(InputConfiguration* outConfiguration);
@@ -305,6 +308,9 @@
             GetStateFunc getStateFunc);
     bool markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
             const int32_t* keyCodes, uint8_t* outFlags);
+
+    // dump state
+    void dumpDeviceInfo(String8& dump);
 };
 
 
@@ -759,9 +765,11 @@
     } mLocked;
 
     virtual void configureParameters();
+    virtual void logParameters();
     virtual void configureRawAxes();
     virtual void logRawAxes();
     virtual bool configureSurfaceLocked();
+    virtual void logMotionRangesLocked();
     virtual void configureVirtualKeysLocked();
     virtual void parseCalibration();
     virtual void resolveCalibration();
diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h
index 82831e29..dc9e27a 100644
--- a/include/ui/InputTransport.h
+++ b/include/ui/InputTransport.h
@@ -33,7 +33,6 @@
 #include <semaphore.h>
 #include <ui/Input.h>
 #include <utils/Errors.h>
-#include <utils/PollLoop.h>
 #include <utils/Timers.h>
 #include <utils/RefBase.h>
 #include <utils/String8.h>
diff --git a/include/ui/PowerManager.h b/include/ui/PowerManager.h
new file mode 100644
index 0000000..5434b4f
--- /dev/null
+++ b/include/ui/PowerManager.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 _UI_POWER_MANAGER_H
+#define _UI_POWER_MANAGER_H
+
+
+namespace android {
+
+enum {
+    POWER_MANAGER_OTHER_EVENT = 0,
+    POWER_MANAGER_CHEEK_EVENT = 1,
+    POWER_MANAGER_TOUCH_EVENT = 2, // touch events are TOUCH for 300ms, and then either
+                                   // up events or LONG_TOUCH events.
+    POWER_MANAGER_LONG_TOUCH_EVENT = 3,
+    POWER_MANAGER_TOUCH_UP_EVENT = 4,
+    POWER_MANAGER_BUTTON_EVENT = 5, // Button and trackball events.
+
+    POWER_MANAGER_LAST_EVENT = POWER_MANAGER_BUTTON_EVENT, // Last valid event code.
+};
+
+} // namespace android
+
+#endif // _UI_POWER_MANAGER_H
diff --git a/include/utils/Looper.h b/include/utils/Looper.h
new file mode 100644
index 0000000..92e4b0a
--- /dev/null
+++ b/include/utils/Looper.h
@@ -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.
+ */
+
+#ifndef UTILS_LOOPER_H
+#define UTILS_LOOPER_H
+
+#include <utils/threads.h>
+#include <utils/RefBase.h>
+#include <utils/KeyedVector.h>
+
+#include <android/looper.h>
+
+/*
+ * Declare a concrete type for the NDK's looper forward declaration.
+ */
+struct ALooper {
+};
+
+namespace android {
+
+/**
+ * A polling loop that supports monitoring file descriptor events, optionally
+ * using callbacks.  The implementation uses epoll() internally.
+ *
+ * A looper can be associated with a thread although there is no requirement that it must be.
+ */
+class Looper : public ALooper, public RefBase {
+protected:
+    virtual ~Looper();
+
+public:
+    /**
+     * Creates a looper.
+     *
+     * If allowNonCallbaks is true, the looper will allow file descriptors to be
+     * registered without associated callbacks.  This assumes that the caller of
+     * pollOnce() is prepared to handle callback-less events itself.
+     */
+    Looper(bool allowNonCallbacks);
+
+    /**
+     * Returns whether this looper instance allows the registration of file descriptors
+     * using identifiers instead of callbacks.
+     */
+    bool getAllowNonCallbacks() const;
+
+    /**
+     * Waits for events to be available, with optional timeout in milliseconds.
+     * Invokes callbacks for all file descriptors on which an event occurred.
+     *
+     * If the timeout is zero, returns immediately without blocking.
+     * If the timeout is negative, waits indefinitely until an event appears.
+     *
+     * Returns ALOOPER_POLL_WAKE if the poll was awoken using wake() before
+     * the timeout expired and no callbacks were invoked and no other file
+     * descriptors were ready.
+     *
+     * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked.
+     *
+     * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
+     * timeout expired.
+     *
+     * Returns ALOOPER_POLL_ERROR if an error occurred.
+     *
+     * Returns a value >= 0 containing an identifier if its file descriptor has data
+     * and it has no callback function (requiring the caller here to handle it).
+     * In this (and only this) case outFd, outEvents and outData will contain the poll
+     * events and data associated with the fd, otherwise they will be set to NULL.
+     *
+     * This method does not return until it has finished invoking the appropriate callbacks
+     * for all file descriptors that were signalled.
+     */
+    int pollOnce(int timeoutMillis,
+            int* outFd = NULL, int* outEvents = NULL, void** outData = NULL);
+
+    /**
+     * Like pollOnce(), but performs all pending callbacks until all
+     * data has been consumed or a file descriptor is available with no callback.
+     * This function will never return ALOOPER_POLL_CALLBACK.
+     */
+    int pollAll(int timeoutMillis,
+            int* outFd = NULL, int* outEvents = NULL, void** outData = NULL);
+
+    /**
+     * Wakes the poll asynchronously.
+     *
+     * This method can be called on any thread.
+     * This method returns immediately.
+     */
+    void wake();
+
+    /**
+     * Adds a new file descriptor to be polled by the looper.
+     * If the same file descriptor was previously added, it is replaced.
+     *
+     * "fd" is the file descriptor to be added.
+     * "ident" is an identifier for this event, which is returned from ALooper_pollOnce().
+     * The identifier must be >= 0, or ALOOPER_POLL_CALLBACK if providing a non-NULL callback.
+     * "events" are the poll events to wake up on.  Typically this is ALOOPER_EVENT_INPUT.
+     * "callback" is the function to call when there is an event on the file descriptor.
+     * "data" is a private data pointer to supply to the callback.
+     *
+     * There are two main uses of this function:
+     *
+     * (1) If "callback" is non-NULL, then this function will be called when there is
+     * data on the file descriptor.  It should execute any events it has pending,
+     * appropriately reading from the file descriptor.  The 'ident' is ignored in this case.
+     *
+     * (2) If "callback" is NULL, the 'ident' will be returned by ALooper_pollOnce
+     * when its file descriptor has data available, requiring the caller to take
+     * care of processing it.
+     *
+     * Returns 1 if the file descriptor was added, 0 if the arguments were invalid.
+     *
+     * This method can be called on any thread.
+     * This method may block briefly if it needs to wake the poll.
+     */
+    int addFd(int fd, int ident,
+            int events, ALooper_callbackFunc callback, void* data = NULL);
+
+    /**
+     * Removes a previously added file descriptor from the looper.
+     *
+     * When this method returns, it is safe to close the file descriptor since the looper
+     * will no longer have a reference to it.  However, it is possible for the callback to
+     * already be running or for it to run one last time if the file descriptor was already
+     * signalled.  Calling code is responsible for ensuring that this case is safely handled.
+     * For example, if the callback takes care of removing itself during its own execution either
+     * by returning 0 or by calling this method, then it can be guaranteed to not be invoked
+     * again at any later time unless registered anew.
+     *
+     * Returns 1 if the file descriptor was removed, 0 if none was previously registered.
+     *
+     * This method can be called on any thread.
+     * This method may block briefly if it needs to wake the poll.
+     */
+    int removeFd(int fd);
+
+    /**
+     * Prepares a looper associated with the calling thread, and returns it.
+     * If the thread already has a looper, it is returned.  Otherwise, a new
+     * one is created, associated with the thread, and returned.
+     *
+     * The opts may be ALOOPER_PREPARE_ALLOW_NON_CALLBACKS or 0.
+     */
+    static sp<Looper> prepare(int opts);
+
+    /**
+     * Sets the given looper to be associated with the calling thread.
+     * If another looper is already associated with the thread, it is replaced.
+     *
+     * If "looper" is NULL, removes the currently associated looper.
+     */
+    static void setForThread(const sp<Looper>& looper);
+
+    /**
+     * Returns the looper associated with the calling thread, or NULL if
+     * there is not one.
+     */
+    static sp<Looper> getForThread();
+
+private:
+    struct Request {
+        int fd;
+        int ident;
+        ALooper_callbackFunc callback;
+        void* data;
+    };
+
+    struct Response {
+        int events;
+        Request request;
+    };
+
+    const bool mAllowNonCallbacks; // immutable
+
+    int mEpollFd; // immutable
+    int mWakeReadPipeFd;  // immutable
+    int mWakeWritePipeFd; // immutable
+
+    // Locked list of file descriptor monitoring requests.
+    Mutex mLock;
+    KeyedVector<int, Request> mRequests;
+
+    // This state is only used privately by pollOnce and does not require a lock since
+    // it runs on a single thread.
+    Vector<Response> mResponses;
+    size_t mResponseIndex;
+
+    int pollInner(int timeoutMillis);
+
+    static void threadDestructor(void *st);
+};
+
+} // namespace android
+
+#endif // UTILS_LOOPER_H
diff --git a/include/utils/PollLoop.h b/include/utils/PollLoop.h
deleted file mode 100644
index bc616eb..0000000
--- a/include/utils/PollLoop.h
+++ /dev/null
@@ -1,205 +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.
- */
-
-#ifndef UTILS_POLL_LOOP_H
-#define UTILS_POLL_LOOP_H
-
-#include <utils/Vector.h>
-#include <utils/threads.h>
-
-#include <sys/poll.h>
-
-#include <android/looper.h>
-
-struct ALooper : public android::RefBase {
-protected:
-    virtual ~ALooper() { }
-
-public:
-    ALooper() { }
-};
-
-namespace android {
-
-/**
- * A basic file descriptor polling loop based on poll() with callbacks.
- */
-class PollLoop : public ALooper {
-protected:
-    virtual ~PollLoop();
-
-public:
-    PollLoop(bool allowNonCallbacks);
-
-    /**
-     * A callback that it to be invoked when an event occurs on a file descriptor.
-     * Specifies the events that were triggered and the user data provided when the
-     * callback was set.
-     *
-     * Returns true if the callback should be kept, false if it should be removed automatically
-     * after the callback returns.
-     */
-    typedef bool (*Callback)(int fd, int events, void* data);
-
-    enum {
-        POLL_CALLBACK = ALOOPER_POLL_CALLBACK,
-        POLL_TIMEOUT = ALOOPER_POLL_TIMEOUT,
-        POLL_ERROR = ALOOPER_POLL_ERROR,
-    };
-    
-    /**
-     * Performs a single call to poll() with optional timeout in milliseconds.
-     * Invokes callbacks for all file descriptors on which an event occurred.
-     *
-     * If the timeout is zero, returns immediately without blocking.
-     * If the timeout is negative, waits indefinitely until awoken.
-     *
-     * Returns ALOOPER_POLL_CALLBACK if a callback was invoked.
-     *
-     * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
-     * timeout expired.
-     *
-     * Returns ALOPER_POLL_ERROR if an error occurred.
-     *
-     * Returns a value >= 0 containing a file descriptor if it has data
-     * and it has no callback function (requiring the caller here to handle it).
-     * In this (and only this) case outEvents and outData will contain the poll
-     * events and data associated with the fd.
-     *
-     * This method must only be called on the thread owning the PollLoop.
-     * This method blocks until either a file descriptor is signalled, a timeout occurs,
-     * or wake() is called.
-     * This method does not return until it has finished invoking the appropriate callbacks
-     * for all file descriptors that were signalled.
-     */
-    int32_t pollOnce(int timeoutMillis, int* outEvents = NULL, void** outData = NULL);
-
-    /**
-     * Wakes the loop asynchronously.
-     *
-     * This method can be called on any thread.
-     * This method returns immediately.
-     */
-    void wake();
-
-    /**
-     * Control whether this PollLoop instance allows using IDs instead
-     * of callbacks.
-     */
-    bool getAllowNonCallbacks() const;
-    
-    /**
-     * Sets the callback for a file descriptor, replacing the existing one, if any.
-     * It is an error to call this method with events == 0 or callback == NULL.
-     *
-     * Note that a callback can be invoked with the POLLERR, POLLHUP or POLLNVAL events
-     * even if it is not explicitly requested when registered.
-     *
-     * This method can be called on any thread.
-     * This method may block briefly if it needs to wake the poll loop.
-     */
-    void setCallback(int fd, int ident, int events, Callback callback, void* data = NULL);
-
-    /**
-     * Convenience for above setCallback when ident is not used.  In this case
-     * the ident is set to POLL_CALLBACK.
-     */
-    void setCallback(int fd, int events, Callback callback, void* data = NULL);
-    
-    /**
-     * Like setCallback(), but for the NDK callback function.
-     */
-    void setLooperCallback(int fd, int ident, int events, ALooper_callbackFunc* callback,
-            void* data);
-    
-    /**
-     * Removes the callback for a file descriptor, if one exists.
-     *
-     * When this method returns, it is safe to close the file descriptor since the poll loop
-     * will no longer have a reference to it.  However, it is possible for the callback to
-     * already be running or for it to run one last time if the file descriptor was already
-     * signalled.  Calling code is responsible for ensuring that this case is safely handled.
-     * For example, if the callback takes care of removing itself during its own execution either
-     * by returning false or calling this method, then it can be guaranteed to not be invoked
-     * again at any later time unless registered anew.
-     *
-     * This method can be called on any thread.
-     * This method may block briefly if it needs to wake the poll loop.
-     *
-     * Returns true if a callback was actually removed, false if none was registered.
-     */
-    bool removeCallback(int fd);
-
-    /**
-     * Set the given PollLoop to be associated with the
-     * calling thread.  There must be a 1:1 relationship between
-     * PollLoop and thread.
-     */
-    static void setForThread(const sp<PollLoop>& pollLoop);
-    
-    /**
-     * Return the PollLoop associated with the calling thread.
-     */
-    static sp<PollLoop> getForThread();
-    
-private:
-    struct RequestedCallback {
-        Callback callback;
-        ALooper_callbackFunc* looperCallback;
-        int ident;
-        void* data;
-    };
-
-    struct PendingCallback {
-        int fd;
-        int ident;
-        int events;
-        Callback callback;
-        ALooper_callbackFunc* looperCallback;
-        void* data;
-    };
-    
-    const bool mAllowNonCallbacks;
-    
-    Mutex mLock;
-    bool mPolling;
-    uint32_t mWaiters;
-    Condition mAwake;
-    Condition mResume;
-
-    int mWakeReadPipeFd;
-    int mWakeWritePipeFd;
-
-    Vector<struct pollfd> mRequestedFds;
-    Vector<RequestedCallback> mRequestedCallbacks;
-
-    Vector<PendingCallback> mPendingCallbacks; // used privately by pollOnce
-    Vector<PendingCallback> mPendingFds;       // used privately by pollOnce
-    size_t mPendingFdsPos;
-    
-    void openWakePipe();
-    void closeWakePipe();
-
-    void setCallbackCommon(int fd, int ident, int events, Callback callback,
-            ALooper_callbackFunc* looperCallback, void* data);
-    ssize_t getRequestIndexLocked(int fd);
-    void wakeAndLock();
-    static void threadDestructor(void *st);
-};
-
-} // namespace android
-
-#endif // UTILS_POLL_LOOP_H
diff --git a/libs/camera/CameraParameters.cpp b/libs/camera/CameraParameters.cpp
index 362d9ee..887b12c 100644
--- a/libs/camera/CameraParameters.cpp
+++ b/libs/camera/CameraParameters.cpp
@@ -129,7 +129,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";
diff --git a/libs/gui/SensorEventQueue.cpp b/libs/gui/SensorEventQueue.cpp
index 7eb6da5..f935524 100644
--- a/libs/gui/SensorEventQueue.cpp
+++ b/libs/gui/SensorEventQueue.cpp
@@ -21,7 +21,7 @@
 
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 
 #include <gui/Sensor.h>
 #include <gui/SensorChannel.h>
@@ -70,9 +70,13 @@
 ssize_t SensorEventQueue::read(ASensorEvent* events, size_t numEvents)
 {
     ssize_t size = mSensorChannel->read(events, numEvents*sizeof(events[0]));
+    LOGE_IF(size<0 && size!=-EAGAIN,
+            "SensorChannel::read error (%s)", strerror(-size));
     if (size >= 0) {
         if (size % sizeof(events[0])) {
             // partial read!!! should never happen.
+            LOGE("SensorEventQueue partial read (event-size=%u, read=%d)",
+                    sizeof(events[0]), int(size));
             return -EINVAL;
         }
         // returns number of events read
@@ -81,28 +85,38 @@
     return size;
 }
 
-sp<PollLoop> SensorEventQueue::getPollLoop() const
+sp<Looper> SensorEventQueue::getLooper() const
 {
     Mutex::Autolock _l(mLock);
-    if (mPollLoop == 0) {
-        mPollLoop = new PollLoop(true);
-        mPollLoop->setCallback(getFd(), getFd(), POLLIN, NULL, NULL);
+    if (mLooper == 0) {
+        mLooper = new Looper(true);
+        mLooper->addFd(getFd(), getFd(), ALOOPER_EVENT_INPUT, NULL, NULL);
     }
-    return mPollLoop;
+    return mLooper;
 }
 
 status_t SensorEventQueue::waitForEvent() const
 {
     const int fd = getFd();
-    sp<PollLoop> pollLoop(getPollLoop());
-    int32_t result = pollLoop->pollOnce(-1, NULL, NULL);
-    return (result == fd) ? NO_ERROR : -1;
+    sp<Looper> looper(getLooper());
+
+    int32_t result;
+    do {
+        result = looper->pollOnce(-1);
+        if (result == ALOOPER_EVENT_ERROR) {
+            LOGE("SensorChannel::waitForEvent error (errno=%d)", errno);
+            result = -EPIPE; // unknown error, so we make up one
+            break;
+        }
+    } while (result != fd);
+
+    return  (result == fd) ? status_t(NO_ERROR) : result;
 }
 
 status_t SensorEventQueue::wake() const
 {
-    sp<PollLoop> pollLoop(getPollLoop());
-    pollLoop->wake();
+    sp<Looper> looper(getLooper());
+    looper->wake();
     return NO_ERROR;
 }
 
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 5d7f8bf..2959814 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -166,6 +166,8 @@
     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) {
@@ -173,12 +175,16 @@
         return;
     }
 
-    int penX = x, penY = y;
+    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) {
@@ -190,23 +196,25 @@
         }
 
         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, penX, penY);
+                drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY);
                 break;
             case BITMAP:
-                drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
+                drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bitmap, bitmapW, bitmapH);
                 break;
             case MEASURE:
-                measureCachedGlyph(cachedGlyph, penX, penY, bounds);
+                measureCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bounds);
                 break;
             }
         }
 
-        penX += SkFixedFloor(cachedGlyph->mAdvanceX);
+        penX += cachedGlyph->mAdvanceX;
 
         // If we were given a specific number of glyphs, decrement
         if (numGlyphs > 0) {
@@ -220,6 +228,8 @@
     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;
@@ -352,7 +362,7 @@
 
 bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
     // If the glyph is too tall, don't cache it
-    if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
+    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;
@@ -616,7 +626,7 @@
     const float maxPrecacheFontSize = 40.0f;
     bool isNewFont = currentNumFonts != mActiveFonts.size();
 
-    if(isNewFont && fontSize <= maxPrecacheFontSize ){
+    if (isNewFont && fontSize <= maxPrecacheFontSize) {
         precacheLatin(paint);
     }
 }
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index a03ea92..de5c019 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -94,6 +94,9 @@
         // 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);
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 1a18766..39c3111 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -110,11 +110,9 @@
         layer = new Layer;
         layer->blend = true;
 
-        // Generate the texture in which the FBO will draw
         glGenTextures(1, &layer->texture);
         glBindTexture(GL_TEXTURE_2D, layer->texture);
 
-        // The FBO will not be scaled, so we can use lower quality filtering
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 
diff --git a/libs/hwui/Line.h b/libs/hwui/Line.h
new file mode 100644
index 0000000..48e18eb
--- /dev/null
+++ b/libs/hwui/Line.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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);
+    }
+
+    void update(float x1, float y1, float x2, float y2, float lineWidth, float& tx, float& ty) {
+        const float length = sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
+        const float half = lineWidth * 0.5f;
+
+        mPatch->updateVertices(gLineTextureWidth, gLineTextureHeight,
+                -gLineAABias, -half - gLineAABias, length + gLineAABias, half + gLineAABias,
+                mXDivs, mYDivs, mXDivsCount, mYDivsCount);
+
+        tx = -gLineAABias;
+        ty = -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/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index de27090..ecc02fa 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -39,6 +39,9 @@
 // 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
 ///////////////////////////////////////////////////////////////////////////////
@@ -80,6 +83,9 @@
         { 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 },
@@ -121,6 +127,8 @@
     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() {
@@ -158,6 +166,15 @@
     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()) {
@@ -280,6 +297,54 @@
     }
 }
 
+/**
+ * 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) {
     LAYER_LOGD("Requesting layer %fx%f", right - left, bottom - top);
@@ -289,8 +354,16 @@
     Rect bounds(left, top, right, bottom);
     mSnapshot->transform->mapRect(bounds);
 
-    LayerSize size(bounds.getWidth(), bounds.getHeight());
+    // 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;
+    }
+
+    LayerSize size(bounds.getWidth(), bounds.getHeight());
     Layer* layer = mCaches.layerCache.get(size);
     if (!layer) {
         return false;
@@ -319,6 +392,9 @@
     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");
@@ -333,16 +409,8 @@
     const Rect& rect = layer->layer;
 
     if (layer->alpha < 255) {
-        glEnable(GL_BLEND);
-        glBlendFuncSeparate(GL_ZERO, GL_SRC_ALPHA, GL_DST_ALPHA, GL_ZERO);
-
         drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
-                layer->alpha << 24, SkXfermode::kSrcOver_Mode, true, true);
-
-        glBlendFunc(mCaches.lastSrcMode, mCaches.lastDstMode);
-        if (!mCaches.blend) {
-            glDisable(GL_BLEND);
-        }
+                layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
     }
 
     // Layers are already drawn with a top-left origin, don't flip the texture
@@ -350,7 +418,7 @@
 
     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], NULL, 0, true, true);
+            &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
 
     resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
 
@@ -523,14 +591,58 @@
     getAlphaAndMode(paint, &alpha, &mode);
 
     Patch* mesh = mCaches.patchCache.get(patch);
-    mesh->updateVertices(bitmap, left, top, right, bottom,
+    mesh->updateVertices(bitmap->width(), bitmap->height(),left, top, right, bottom,
             &patch->xDivs[0], &patch->yDivs[0], patch->numXDivs, patch->numYDivs);
 
     // Specify right and bottom as +1.0f from left/top to prevent scaling since the
     // patch mesh already defines the final size
     drawTextureMesh(left, top, left + 1.0f, top + 1.0f, texture->id, alpha / 255.0f,
             mode, texture->blend, &mesh->vertices[0].position[0],
-            &mesh->vertices[0].texture[0], mesh->indices, mesh->indicesCount);
+            &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;
+
+    GLuint textureUnit = 0;
+    setupTextureAlpha8(mLine.getTexture(), 0, 0, textureUnit, 0.0f, 0.0f, r, g, b, a,
+            mode, false, true, mLine.getVertices(), mLine.getTexCoords());
+
+    for (int i = 0; i < count; i += 4) {
+        float tx = 0.0f;
+        float ty = 0.0f;
+
+        mLine.update(points[i], points[i + 1], points[i + 2], points[i + 3],
+                paint->getStrokeWidth(), tx, ty);
+
+        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);
+        mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+
+        if (mShader) {
+            mShader->updateTransforms(mCaches.currentProgram, mModelView, *mSnapshot);
+        }
+
+        glDrawArrays(GL_TRIANGLES, 0, mLine.getElementsCount());
+    }
+
+    glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
 }
 
 void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
@@ -571,14 +683,6 @@
     }
     paint->setAntiAlias(true);
 
-    float scaleX = paint->getTextScaleX();
-    bool applyScaleX = scaleX < 0.9999f || scaleX > 1.0001f;
-    if (applyScaleX) {
-        save(SkCanvas::kMatrix_SaveFlag);
-        translate(x - (x * scaleX), 0.0f);
-        scale(scaleX, 1.0f);
-    }
-
     float length = -1.0f;
     switch (paint->getTextAlign()) {
         case SkPaint::kCenter_Align:
@@ -634,10 +738,6 @@
     glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
 
     drawTextDecorations(text, bytesCount, length, x, y, paint);
-
-    if (applyScaleX) {
-        restore();
-    }
 }
 
 void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
@@ -740,12 +840,22 @@
         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);
+            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;
@@ -774,9 +884,9 @@
 
      // Setup attributes
      glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
-             gMeshStride, &mMeshVertices[0].position[0]);
+             gMeshStride, vertices);
      glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE,
-             gMeshStride, &mMeshVertices[0].texture[0]);
+             gMeshStride, texCoords);
 
      // Setup uniforms
      if (transforms) {
@@ -853,7 +963,7 @@
 }
 
 void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom,
-        int color, SkXfermode::Mode mode, bool ignoreTransform, bool ignoreBlending) {
+        int color, SkXfermode::Mode mode, bool ignoreTransform) {
     clearLayerRegions();
 
     // If a shader is set, preserve only the alpha
@@ -879,10 +989,8 @@
         mColorFilter->describe(description, mExtensions);
     }
 
-    if (!ignoreBlending) {
-        // Setup the blending mode
-        chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode, description);
-    }
+    // Setup the blending mode
+    chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode, description);
 
     // Build and use the appropriate shader
     useProgram(mCaches.programCache.get(description));
@@ -920,19 +1028,21 @@
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
 
-    drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode, texture->blend,
-            &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0], NULL);
+    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], NULL);
+            &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, GLvoid* indices, GLsizei elementsCount,
+        GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
         bool swapSrcDst, bool ignoreTransform) {
     clearLayerRegions();
 
@@ -974,11 +1084,7 @@
         mColorFilter->setupProgram(mCaches.currentProgram);
     }
 
-    if (!indices) {
-        glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
-    } else {
-        glDrawElements(GL_TRIANGLES, elementsCount, GL_UNSIGNED_SHORT, indices);
-    }
+    glDrawArrays(drawMode, 0, elementsCount);
     glDisableVertexAttribArray(texCoordsSlot);
 }
 
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 12ec276..cd7963f 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -40,11 +40,19 @@
 #include "SkiaShader.h"
 #include "SkiaColorFilter.h"
 #include "Caches.h"
+#include "Line.h"
 
 namespace android {
 namespace uirenderer {
 
 ///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_OPENGL 1
+
+///////////////////////////////////////////////////////////////////////////////
 // Renderer
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -59,6 +67,7 @@
 
     void setViewport(int width, int height);
     void prepare();
+    void finish();
     void acquireContext();
     void releaseContext();
 
@@ -91,6 +100,7 @@
     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 resetShader();
     void setupShader(SkiaShader* shader);
@@ -176,8 +186,7 @@
      * @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,
-    		bool ignoreBlending = false);
+            int color, SkXfermode::Mode mode, bool ignoreTransform = false);
 
     /**
      * Draws a textured rectangle with the specified texture. The specified coordinates
@@ -230,7 +239,7 @@
      */
     void drawTextureMesh(float left, float top, float right, float bottom, GLuint texture,
             float alpha, SkXfermode::Mode mode, bool blend,
-            GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount = 0,
+            GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
             bool swapSrcDst = false, bool ignoreTransform = false);
 
     /**
@@ -290,6 +299,15 @@
             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
@@ -396,6 +414,12 @@
     // List of rectangles to clear due to calls to saveLayer()
     Vector<Rect*> mLayers;
 
+    // Single object used to draw lines
+    Line mLine;
+
+    // Misc
+    GLint mMaxTextureSize;
+
 }; // class OpenGLRenderer
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp
index 4b6bb37..0a6eca3 100644
--- a/libs/hwui/Patch.cpp
+++ b/libs/hwui/Patch.cpp
@@ -16,9 +16,7 @@
 
 #define LOG_TAG "OpenGLRenderer"
 
-#include <cstring>
-
-#include <utils/Log.h>
+#include <cmath>
 
 #include "Patch.h"
 
@@ -29,38 +27,13 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-Patch::Patch(const uint32_t xCount, const uint32_t yCount):
-        xCount(xCount + 2), yCount(yCount + 2) {
-    verticesCount = (xCount + 2) * (yCount + 2);
-    vertices = new TextureVertex[verticesCount];
-
+Patch::Patch(const uint32_t xCount, const uint32_t yCount) {
     // 2 triangles per patch, 3 vertices per triangle
-    indicesCount = (xCount + 1) * (yCount + 1) * 2 * 3;
-    indices = new uint16_t[indicesCount];
-
-    const uint32_t xNum = xCount + 1;
-    const uint32_t yNum = yCount + 1;
-
-    uint16_t* startIndices = indices;
-    uint32_t n = 0;
-    for (uint32_t y = 0; y < yNum; y++) {
-        for (uint32_t x = 0; x < xNum; x++) {
-            *startIndices++ = n;
-            *startIndices++ = n + 1;
-            *startIndices++ = n + xNum + 2;
-
-            *startIndices++ = n;
-            *startIndices++ = n + xNum + 2;
-            *startIndices++ = n + xNum + 1;
-
-            n += 1;
-        }
-        n += 1;
-    }
+    verticesCount = (xCount + 1) * (yCount + 1) * 2 * 3;
+    vertices = new TextureVertex[verticesCount];
 }
 
 Patch::~Patch() {
-    delete indices;
     delete vertices;
 }
 
@@ -68,30 +41,26 @@
 // Vertices management
 ///////////////////////////////////////////////////////////////////////////////
 
-void Patch::updateVertices(const SkBitmap* bitmap, float left, float top, float right,
-        float bottom, const int32_t* xDivs,  const int32_t* yDivs, const uint32_t width,
-        const uint32_t height) {
+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 xStretch = 0;
-    float yStretch = 0;
-    float xStretchTex = 0;
-    float yStretchTex = 0;
+    float stretchX = 0.0f;
+    float stretchY = 0.0;
 
     const float meshWidth = right - left;
 
-    const float bitmapWidth = float(bitmap->width());
-    const float bitmapHeight = float(bitmap->height());
-
     if (xStretchCount > 0) {
         uint32_t stretchSize = 0;
         for (uint32_t i = 1; i < width; i += 2) {
             stretchSize += xDivs[i] - xDivs[i - 1];
         }
-        xStretchTex = (stretchSize / bitmapWidth) / xStretchCount;
+        const float xStretchTex = stretchSize;
         const float fixed = bitmapWidth - stretchSize;
-        xStretch = (right - left - fixed) / xStretchCount;
+        const float xStretch = right - left - fixed;
+        stretchX = xStretch / xStretchTex;
     }
 
     if (yStretchCount > 0) {
@@ -99,89 +68,86 @@
         for (uint32_t i = 1; i < height; i += 2) {
             stretchSize += yDivs[i] - yDivs[i - 1];
         }
-        yStretchTex = (stretchSize / bitmapHeight) / yStretchCount;
+        const float yStretchTex = stretchSize;
         const float fixed = bitmapHeight - stretchSize;
-        yStretch = (bottom - top - fixed) / yStretchCount;
+        const float yStretch = bottom - top - fixed;
+        stretchY = yStretch / yStretchTex;
     }
 
-    float vy = 0.0f;
-    float ty = 0.0f;
     TextureVertex* vertex = vertices;
 
-    generateVertices(vertex, 0.0f, 0.0f, xDivs, width, xStretch, xStretchTex,
-            meshWidth, bitmapWidth);
-    vertex += width + 2;
+    float previousStepY = 0.0f;
 
-    for (uint32_t y = 0; y < height; y++) {
-        if (y & 1) {
-            vy += yStretch;
-            ty += yStretchTex;
+    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 {
-            const float step = float(yDivs[y]);
-            vy += step;
-            ty += step / bitmapHeight;
+            y2 = y1 + stepY - previousStepY;
         }
-        generateVertices(vertex, vy, ty, xDivs, width, xStretch, xStretchTex,
-                meshWidth, bitmapWidth);
-        vertex += width + 2;
+        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;
     }
 
-    generateVertices(vertex, bottom - top, 1.0f, xDivs, width, xStretch, xStretchTex,
-            meshWidth, bitmapWidth);
+    generateRow(vertex, y1, bottom - top, v1, 1.0f, xDivs, width, stretchX,
+            right - left, bitmapWidth);
 }
 
-inline void Patch::generateVertices(TextureVertex* vertex, float y, float v,
-        const int32_t xDivs[], uint32_t xCount, float xStretch, float xStretchTex,
-        float width, float widthTex) {
-    float vx = 0.0f;
-    float tx = 0.0f;
+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;
 
-    TextureVertex::set(vertex, vx, y, tx, v);
-    vertex++;
+    float x1 = 0.0f;
+    float u1 = 0.0f;
 
-    for (uint32_t x = 0; x < xCount; x++) {
-        if (x & 1) {
-            vx += xStretch;
-            tx += xStretchTex;
+    // 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 {
-            const float step = float(xDivs[x]);
-            vx += step;
-            tx += step / widthTex;
+            x2 = x1 + stepX - previousStepX;
         }
+        float u2 = fmax(0.0f, stepX - 0.5f) / bitmapWidth;
 
-        TextureVertex::set(vertex, vx, y, tx, v);
-        vertex++;
+        generateQuad(vertex, x1, y1, x2, y2, u1, v1, u2, v2);
+
+        x1 = x2;
+        u1 = (stepX + 0.5f) / bitmapWidth;
+
+        previousStepX = stepX;
     }
 
-    TextureVertex::set(vertex, width, y, 1.0f, v);
-    vertex++;
+    generateQuad(vertex, x1, y1, width, y2, u1, v1, 1.0f, v2);
 }
 
-///////////////////////////////////////////////////////////////////////////////
-// Debug tools
-///////////////////////////////////////////////////////////////////////////////
+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);
 
-void Patch::dump() {
-    LOGD("Vertices [");
-    for (uint32_t y = 0; y < yCount; y++) {
-        char buffer[512];
-        buffer[0] = '\0';
-        uint32_t offset = 0;
-        for (uint32_t x = 0; x < xCount; x++) {
-            TextureVertex* v = &vertices[y * xCount + x];
-            offset += sprintf(&buffer[offset], " (%.4f,%.4f)-(%.4f,%.4f)",
-                    v->position[0], v->position[1], v->texture[0], v->texture[1]);
-        }
-        LOGD("  [%s ]", buffer);
-    }
-    LOGD("]\nIndices [ ");
-    char buffer[4096];
-    buffer[0] = '\0';
-    uint32_t offset = 0;
-    for (uint32_t i = 0; i < indicesCount; i++) {
-        offset += sprintf(&buffer[offset], "%d ", indices[i]);
-    }
-    LOGD("  %s\n]", buffer);
+    // 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
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
index 5d3ad03..1d08c64 100644
--- a/libs/hwui/Patch.h
+++ b/libs/hwui/Patch.h
@@ -19,8 +19,6 @@
 
 #include <sys/types.h>
 
-#include <SkBitmap.h>
-
 #include "Vertex.h"
 
 namespace android {
@@ -59,24 +57,21 @@
     Patch(const uint32_t xCount, const uint32_t yCount);
     ~Patch();
 
-    void updateVertices(const SkBitmap* bitmap, float left, float top, float right, float bottom,
-            const int32_t* xDivs,  const int32_t* yDivs,
+    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);
-    void dump();
-
-    uint32_t xCount;
-    uint32_t yCount;
-
-    uint16_t* indices;
-    uint32_t indicesCount;
 
     TextureVertex* vertices;
     uint32_t verticesCount;
 
 private:
-    static inline void generateVertices(TextureVertex* vertex, float y, float v,
-            const int32_t xDivs[], uint32_t xCount, float xStretch, float xStretchTex,
-            float width, float widthTex);
+    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
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 6b22c2b..f03a602 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -148,6 +148,13 @@
         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);
     }
@@ -157,4 +164,4 @@
 }; // namespace uirenderer
 }; // namespace android
 
-#endif // ANDROID_RECT_H
+#endif // ANDROID_UI_RECT_H
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index a5e0f78..946cc4b 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -128,6 +128,22 @@
     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
 ///////////////////////////////////////////////////////////////////////////////
@@ -185,6 +201,13 @@
     glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
 }
 
+void SkiaLinearGradientShader::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
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index d95e3b0..cc94ae6 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -73,6 +73,10 @@
         mGradientCache = gradientCache;
     }
 
+    virtual void updateTransforms(Program* program, const mat4& modelView,
+            const Snapshot& snapshot) {
+    }
+
     void setMatrix(SkMatrix* matrix) {
         mMatrix = matrix;
     }
@@ -106,6 +110,7 @@
     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:
     /**
@@ -130,6 +135,7 @@
     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:
     float* mBounds;
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index ebedf53..062c986 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -116,6 +116,11 @@
     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;
diff --git a/libs/rs/RenderScriptEnv.h b/libs/rs/RenderScriptEnv.h
index 9225904..c83ece4 100644
--- a/libs/rs/RenderScriptEnv.h
+++ b/libs/rs/RenderScriptEnv.h
@@ -28,4 +28,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/Fountain/src/com/android/fountain/FountainRS.java b/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java
index 297ea07..0b26cfd 100644
--- a/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java
+++ b/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java
@@ -29,7 +29,7 @@
 
     private Resources mRes;
     private RenderScriptGL mRS;
-    private ScriptC_Fountain mScript;
+    private ScriptC_fountain mScript;
     public void init(RenderScriptGL rs, Resources res, int width, int height) {
         mRS = rs;
         mRes = res;
@@ -45,7 +45,7 @@
         smb.addIndexType(Primitive.POINT);
         Mesh sm = smb.create();
 
-        mScript = new ScriptC_Fountain(mRS, mRes, R.raw.fountain, true);
+        mScript = new ScriptC_fountain(mRS, mRes, R.raw.fountain, true);
         mScript.set_partMesh(sm);
         mScript.bind_point(points);
         mRS.contextBindRootScript(mScript);
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 75b8d33..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
@@ -41,9 +41,9 @@
     private Bitmap mBitmapIn;
     private Bitmap mBitmapOut;
     private Bitmap mBitmapScratch;
-    private ScriptC_Threshold mScript;
-    private ScriptC_VerticalBlur mScriptVBlur;
-    private ScriptC_HorizontalBlur mScriptHBlur;
+    private ScriptC_threshold mScript;
+    private ScriptC_vertical_blur mScriptVBlur;
+    private ScriptC_horizontal_blur mScriptHBlur;
     private int mRadius = 0;
     private SeekBar mRadiusSeekBar;
 
@@ -352,7 +352,7 @@
         mSaturationSeekBar.setProgress(50);
 
         mBenchmarkResult = (TextView) findViewById(R.id.benchmarkText);
-        mBenchmarkResult.setText("Benchmark no yet run");
+        mBenchmarkResult.setText("Benchmark not yet run");
     }
 
     public void surfaceCreated(SurfaceHolder holder) {
@@ -380,10 +380,10 @@
         mScratchPixelsAllocation1 = Allocation.createTyped(mRS, tb.create());
         mScratchPixelsAllocation2 = Allocation.createTyped(mRS, tb.create());
 
-        mScriptVBlur = new ScriptC_VerticalBlur(mRS, getResources(), R.raw.verticalblur, false);
-        mScriptHBlur = new ScriptC_HorizontalBlur(mRS, getResources(), R.raw.horizontalblur, false);
+        mScriptVBlur = new ScriptC_vertical_blur(mRS, getResources(), R.raw.vertical_blur, false);
+        mScriptHBlur = new ScriptC_horizontal_blur(mRS, getResources(), R.raw.horizontal_blur, false);
 
-        mScript = new ScriptC_Threshold(mRS, getResources(), R.raw.threshold, false);
+        mScript = new ScriptC_threshold(mRS, getResources(), R.raw.threshold, false);
         mScript.set_width(mBitmapIn.getWidth());
         mScript.set_height(mBitmapIn.getHeight());
         mScript.set_radius(mRadius);
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
index 85c1d42..b5592f0 100644
--- a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
@@ -63,8 +63,8 @@
     private Font mItalic;
     private Allocation mTextAlloc;
 
-    private ScriptC_Scenegraph mScript;
-    private ScriptC_Transform mTransformScript;
+    private ScriptC_scenegraph mScript;
+    private ScriptC_transform mTransformScript;
 
     int mLastX;
     int mLastY;
@@ -172,8 +172,8 @@
 
     private void initRS() {
 
-        mScript = new ScriptC_Scenegraph(mRS, mRes, R.raw.scenegraph, true);
-        mTransformScript = new ScriptC_Transform(mRS, mRes, R.raw.transform, false);
+        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);
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java
index d8d1a6e..afbf30b 100644
--- a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java
@@ -60,7 +60,7 @@
     private Font mItalic;
     private Allocation mTextAlloc;
 
-    private ScriptC_Simplemodel mScript;
+    private ScriptC_simplemodel mScript;
 
     int mLastX;
     int mLastY;
@@ -138,7 +138,7 @@
 
     private void initRS() {
 
-        mScript = new ScriptC_Simplemodel(mRS, mRes, R.raw.simplemodel, true);
+        mScript = new ScriptC_simplemodel(mRS, mRes, R.raw.simplemodel, true);
 
         initPFS();
         initPF();
diff --git a/libs/rs/java/Samples/res/raw/shaderf.glsl b/libs/rs/java/Samples/res/raw/shaderf.glsl
index fdcf481..fcbe7ee 100644
--- a/libs/rs/java/Samples/res/raw/shaderf.glsl
+++ b/libs/rs/java/Samples/res/raw/shaderf.glsl
@@ -3,10 +3,11 @@
 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;
+   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;
diff --git a/libs/rs/java/Samples/res/raw/shaderv.glsl b/libs/rs/java/Samples/res/raw/shaderv.glsl
index 7f61197..867589cf 100644
--- a/libs/rs/java/Samples/res/raw/shaderv.glsl
+++ b/libs/rs/java/Samples/res/raw/shaderv.glsl
@@ -2,24 +2,12 @@
 varying float light0_Specular;
 varying float light1_Diffuse;
 varying float light1_Specular;
-
-/*
-rs_matrix4x4 model;
- float3 light0_Posision;
- float light0_Diffuse;
- float light0_Specular;
- float light0_CosinePower;
-
- float3 light1_Posision;
- float light1_Diffuse;
- float light1_Specular;
- float light1_CosinePower;
-*/
+varying vec2 varTex0;
 
 // This is where actual shader code begins
 void main() {
    vec4 worldPos = UNI_model * ATTRIB_position;
-   gl_Position = UNI_MVP * worldPos;
+   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;
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsListRS.java b/libs/rs/java/Samples/src/com/android/samples/RsListRS.java
index a782e0e..aaeea876 100644
--- a/libs/rs/java/Samples/src/com/android/samples/RsListRS.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsListRS.java
@@ -93,7 +93,7 @@
 
     ScriptField_ListAllocs_s mListAllocs;
 
-    private ScriptC_Rslist mScript;
+    private ScriptC_rslist mScript;
 
     int mLastX;
     int mLastY;
@@ -121,7 +121,7 @@
 
     private void initRS() {
 
-        mScript = new ScriptC_Rslist(mRS, mRes, R.raw.rslist, true);
+        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 ++) {
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
index e76e740..18b94d9 100644
--- a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
@@ -67,7 +67,6 @@
 
     // Custom shaders
     private ProgramVertex mProgVertexCustom;
-    private ProgramVertex.MatrixAllocation mPVACustom;
     private ProgramFragment mProgFragmentCustom;
     private ScriptField_VertexShaderConstants_s mVSConst;
     private ScriptField_FragentShaderConstants_s mFSConst;
@@ -93,7 +92,7 @@
 
     private Allocation mTextAlloc;
 
-    private ScriptC_Rsrenderstates mScript;
+    private ScriptC_rsrenderstates mScript;
 
     private final BitmapFactory.Options mOptionsARGB = new BitmapFactory.Options();
 
@@ -202,9 +201,7 @@
         pvbCustom.addConstant(mVSConst.getAllocation().getType());
         mProgVertexCustom = pvbCustom.create();
         // Bind the source of constant data
-        mProgVertexCustom.bindConstants(mVSConst.getAllocation(), 1);
-        mPVACustom = new ProgramVertex.MatrixAllocation(mRS);
-        mProgVertexCustom.bindAllocation(mPVACustom);
+        mProgVertexCustom.bindConstants(mVSConst.getAllocation(), 0);
 
         ProgramFragment.ShaderBuilder pfbCustom = new ProgramFragment.ShaderBuilder(mRS);
         // Specify the resource that contains the shader string
@@ -215,7 +212,7 @@
         pfbCustom.addConstant(mFSConst.getAllocation().getType());
         mProgFragmentCustom = pfbCustom.create();
         // Bind the source of constant data
-        mProgFragmentCustom.bindConstants(mFSConst.getAllocation(), 1);
+        mProgFragmentCustom.bindConstants(mFSConst.getAllocation(), 0);
 
         mScript.set_gProgVertexCustom(mProgVertexCustom);
         mScript.set_gProgFragmentCustom(mProgFragmentCustom);
@@ -307,7 +304,7 @@
 
     private void initRS() {
 
-        mScript = new ScriptC_Rsrenderstates(mRS, mRes, R.raw.rsrenderstates, true);
+        mScript = new ScriptC_rsrenderstates(mRS, mRes, R.raw.rsrenderstates, true);
 
         initSamplers();
         initProgramStore();
diff --git a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
index 4bcf7f5..c7bea93 100644
--- a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
+++ b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
@@ -395,14 +395,12 @@
     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);
-    // 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);
@@ -416,7 +414,7 @@
 
     rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
     rsgBindFont(gFontMono);
-    rsgDrawText("Custom shader sample", 10, rsgGetHeight() - 10);
+    //rsgDrawText("Custom shader sample", 10, rsgGetHeight() - 10);
 }
 
 int root(int launchID) {
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
index 1b697ca..e3f6206 100644
--- a/libs/rs/java/Samples/src/com/android/samples/shader_def.rsh
+++ b/libs/rs/java/Samples/src/com/android/samples/shader_def.rsh
@@ -18,6 +18,7 @@
 
 typedef struct VertexShaderConstants_s {
     rs_matrix4x4 model;
+    rs_matrix4x4 proj;
     float3 light0_Posision;
     float light0_Diffuse;
     float light0_Specular;
@@ -27,7 +28,6 @@
     float light1_Diffuse;
     float light1_Specular;
     float light1_CosinePower;
-
 } VertexShaderConstants;
 
 typedef struct FragentShaderConstants_s {
@@ -42,6 +42,6 @@
 typedef struct VertexShaderInputs_s {
     float4 position;
     float3 normal;
-    float4 texture0;
+    float2 texture0;
 } VertexShaderInputs;
 
diff --git a/libs/rs/java/tests/AndroidManifest.xml b/libs/rs/java/tests/AndroidManifest.xml
index bc144ab..b660398 100644
--- a/libs/rs/java/tests/AndroidManifest.xml
+++ b/libs/rs/java/tests/AndroidManifest.xml
@@ -4,7 +4,8 @@
     <application 
         android:label="_RS_Test"
         android:icon="@drawable/test_pattern">
-        <activity android:name="RSTest">
+        <activity android:name="RSTest"
+                  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/tests/src/com/android/rs/test/RSTest.java b/libs/rs/java/tests/src/com/android/rs/test/RSTest.java
index 121793d..c264649 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/RSTest.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/RSTest.java
@@ -61,7 +61,7 @@
     @Override
     protected void onResume() {
         // Ideally a game should implement onResume() and onPause()
-        // to take appropriate action when the activity looses focus
+        // to take appropriate action when the activity loses focus
         super.onResume();
         mView.onResume();
     }
@@ -69,7 +69,7 @@
     @Override
     protected void onPause() {
         // Ideally a game should implement onResume() and onPause()
-        // to take appropriate action when the activity looses focus
+        // to take appropriate action when the activity loses focus
         super.onPause();
         mView.onPause();
     }
@@ -82,4 +82,3 @@
 
 
 }
-
diff --git a/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
index eb1e48b..6645372 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
@@ -30,10 +30,10 @@
     private Resources mRes;
     private RenderScriptGL mRS;
 
-    private ScriptC_TestRoot mRootScript;
+    private ScriptC_test_root mRootScript;
 
     private boolean fp_mad() {
-        ScriptC_FpMad s = new ScriptC_FpMad(mRS, mRes, R.raw.fpmad, true);
+        ScriptC_fp_mad s = new ScriptC_fp_mad(mRS, mRes, R.raw.fp_mad, true);
         s.invoke_doTest(0, 0);
         return true;
     }
@@ -43,7 +43,7 @@
         mRS = rs;
         mRes = res;
 
-        mRootScript = new ScriptC_TestRoot(mRS, mRes, R.raw.testroot, true);
+        mRootScript = new ScriptC_test_root(mRS, mRes, R.raw.test_root, true);
 
         fp_mad();
 
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 60998c31..87c4f2b 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -724,10 +724,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);
diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h
index b6b5d2f..ce5372f 100644
--- a/libs/rs/rsAllocation.h
+++ b/libs/rs/rsAllocation.h
@@ -90,6 +90,7 @@
     void decRefs(const void *ptr, size_t ct) const;
 
     void sendDirty() const;
+    bool getHasGraphicsMipmaps() const {return mTextureGenMipmap;}
 
 protected:
     ObjectBaseRef<const Type> mType;
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index f67a9d5..3dbdbfb 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -88,7 +88,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");
 
@@ -97,7 +97,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);
 
@@ -109,14 +109,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");
@@ -150,7 +150,7 @@
 {
     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);
     }
 }
 
@@ -232,23 +232,15 @@
 
 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;
 }
 
@@ -348,7 +340,7 @@
          }
      }
 
-     LOGV("RS Thread exiting");
+     LOGV("%p, RS Thread exiting", rsc);
      if (rsc->mIsGraphicsContext) {
          rsc->mRaster.clear();
          rsc->mFragment.clear();
@@ -370,7 +362,7 @@
          pthread_mutex_unlock(&gInitMutex);
      }
 
-     LOGV("RS Thread exited");
+     LOGV("%p, RS Thread exited", rsc);
      return NULL;
 }
 
@@ -940,6 +932,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;
 }
 
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 6d1a41d..e38ba55 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -153,9 +153,6 @@
     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;
diff --git a/libs/rs/rsElement.cpp b/libs/rs/rsElement.cpp
index 0b7bb27..d0909c8 100644
--- a/libs/rs/rsElement.cpp
+++ b/libs/rs/rsElement.cpp
@@ -314,6 +314,11 @@
 
 ElementState::ElementState()
 {
+    const uint32_t initialCapacity = 32;
+    mBuilderElements.setCapacity(initialCapacity);
+    mBuilderNameStrings.setCapacity(initialCapacity);
+    mBuilderNameLengths.setCapacity(initialCapacity);
+    mBuilderArrays.setCapacity(initialCapacity);
 }
 
 ElementState::~ElementState()
@@ -321,6 +326,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());
+}
+
 
 /////////////////////////////////////////
 //
diff --git a/libs/rs/rsElement.h b/libs/rs/rsElement.h
index 42eef4a..ae6a6cc 100644
--- a/libs/rs/rsElement.h
+++ b/libs/rs/rsElement.h
@@ -99,8 +99,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<Element *> mElements;
+private:
+    Vector<const Element *> mBuilderElements;
+    Vector<const char*> mBuilderNameStrings;
+    Vector<size_t> mBuilderNameLengths;
+    Vector<uint32_t> mBuilderArrays;
 };
 
 
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index 5889bfb..0f815a2 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -387,14 +387,34 @@
 
 void FontState::initRenderState()
 {
-    uint32_t tmp[] = {
-        RS_TEX_ENV_MODE_REPLACE, 1,
-        RS_TEX_ENV_MODE_NONE, 0,
-        0, 0
-    };
-    ProgramFragment *pf = new ProgramFragment(mRSC, tmp, 6);
+    String8 shaderString("varying vec4 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("  gl_FragColor = col;\n");
+    shaderString.append("}\n");
+
+    const Element *colorElem = Element::create(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 4);
+    mRSC->mStateElement.elementBuilderBegin();
+    mRSC->mStateElement.elementBuilderAdd(colorElem, "Color", 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->init(mRSC);
+    mFontShaderF->bindAllocation(mFontShaderFConstant.get(), 0);
 
     Sampler *sampler = new Sampler(mRSC, RS_SAMPLER_NEAREST, RS_SAMPLER_NEAREST,
                                       RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP);
@@ -539,7 +559,7 @@
     mRSC->setFragmentStore(mFontProgramStore.get());
 
     if(mFontColorDirty) {
-        mFontShaderF->setConstantColor(mFontColor[0], mFontColor[1], mFontColor[2], mFontColor[3]);
+        mFontShaderFConstant->data(mRSC, &mFontColor, 4*sizeof(float));
         mFontColorDirty = false;
     }
 
diff --git a/libs/rs/rsFont.h b/libs/rs/rsFont.h
index defe38b..027ed1d 100644
--- a/libs/rs/rsFont.h
+++ b/libs/rs/rsFont.h
@@ -171,6 +171,7 @@
     Vector<Font*> mActiveFonts;
 
     // Render state for the font
+    ObjectBaseRef<Allocation> mFontShaderFConstant;
     ObjectBaseRef<ProgramFragment> mFontShaderF;
     ObjectBaseRef<Sampler> mFontSampler;
     ObjectBaseRef<ProgramStore> mFontProgramStore;
diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp
index 6041db8..2441491 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -46,6 +46,7 @@
     mOutputCount = 0;
     mConstantCount = 0;
     mIsValid = false;
+    mIsInternal = false;
 }
 
 Program::Program(Context *rsc, const char * shaderText, uint32_t shaderLength,
@@ -97,6 +98,14 @@
             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);
 }
 
@@ -281,9 +290,9 @@
 }
 
 void Program::setupUserConstants(ShaderCache *sc, bool isFragment) {
-    uint32_t uidx = 1;
+    uint32_t uidx = 0;
     for (uint32_t ct=0; ct < mConstantCount; ct++) {
-        Allocation *alloc = mConstants[ct+1].get();
+        Allocation *alloc = mConstants[ct].get();
         if (!alloc) {
             continue;
         }
@@ -313,6 +322,9 @@
             if (slot >= 0) {
                 if(f->getType() == RS_TYPE_MATRIX_4X4) {
                     glUniformMatrix4fv(slot, 1, GL_FALSE, fd);
+                    /*for(int i = 0; i < 4; i++) {
+                        LOGE("Mat = %f %f %f %f", fd[i*4 + 0], fd[i*4 + 1], fd[i*4 + 2], fd[i*4 + 3]);
+                    }*/
                 }
                 else if(f->getType() == RS_TYPE_MATRIX_3X3) {
                     glUniformMatrix3fv(slot, 1, GL_FALSE, fd);
diff --git a/libs/rs/rsProgram.h b/libs/rs/rsProgram.h
index ddc5e8a..e7329c2 100644
--- a/libs/rs/rsProgram.h
+++ b/libs/rs/rsProgram.h
@@ -23,10 +23,10 @@
 // ---------------------------------------------------------------------------
 namespace android {
 namespace renderscript {
-
-
 class ShaderCache;
 
+#define RS_SHADER_INTERNAL "//rs_shader_internal\n"
+
 class Program : public ObjectBase
 {
 public:
@@ -42,7 +42,7 @@
     void bindAllocation(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 *);
@@ -71,6 +71,7 @@
     uint32_t mOutputCount;
     uint32_t mConstantCount;
     bool mIsValid;
+    bool mIsInternal;
 
     // Applies to vertex and fragment shaders only
     void appendUserConstants();
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 83321d3..9817fca 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -92,20 +92,19 @@
 
 void ProgramFragment::setConstantColor(float r, float g, float b, float a)
 {
+    if(isUserProgram()) {
+        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::setupGL(const Context *rsc, ProgramFragmentState *state)
-{
-}
-
 void ProgramFragment::setupGL2(const Context *rsc, ProgramFragmentState *state, ShaderCache *sc)
 {
-
     //LOGE("sgl2 frag1 %x", glGetError());
     if ((state->mLast.get() == this) && !mDirty) {
         return;
@@ -114,13 +113,6 @@
 
     rsc->checkError("ProgramFragment::setupGL2 start");
 
-    if (!mVaryingColor &&
-        (sc->fragUniformSlot(mConstantColorUniformIndex) >= 0)) {
-        //LOGE("mConstantColorUniformIndex %i %i", mConstantColorUniformIndex, sc->fragUniformSlot(mConstantColorUniformIndex));
-        glUniform4fv(sc->fragUniformSlot(mConstantColorUniformIndex), 1, mConstantColor);
-        rsc->checkError("ProgramFragment::color setup");
-    }
-
     rsc->checkError("ProgramFragment::setupGL2 begin uniforms");
     setupUserConstants(sc, true);
 
@@ -134,12 +126,12 @@
         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");
         }
 
@@ -158,124 +150,32 @@
 
 void ProgramFragment::createShader()
 {
-    mShader.setTo("precision mediump float;\n");
-    mShader.append("varying lowp vec4 varColor;\n");
-    mShader.append("varying vec4 varTex0;\n");
-    mShader.append("uniform vec4 uni_Color;\n");
-
     if (mUserShader.length() > 1) {
+        mShader.append("precision mediump float;\n");
         appendUserConstants();
         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");
-        if (mVaryingColor) {
-            mShader.append("  lowp vec4 col = varColor;\n");
-        } else {
-            mShader.append("  lowp vec4 col = uni_Color;\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 = 0;
-    //if (!mVaryingColor) {
-        mConstantColorUniformIndex = mUniformCount;
-        mUniformNames[mUniformCount++].setTo("uni_Color");
-    //}
-
     if (mUserShader.size() > 0) {
         for (uint32_t ct=0; ct < mConstantCount; ct++) {
             initAddUserElement(mConstantTypes[ct]->getElement(), mUniformNames, &mUniformCount, "UNI_");
         }
     }
     mTextureUniformIndexStart = mUniformCount;
-    mUniformNames[mUniformCount++].setTo("uni_Tex0");
-    mUniformNames[mUniformCount++].setTo("uni_Tex1");
+    mUniformNames[mUniformCount++].setTo("UNI_Tex0");
+    mUniformNames[mUniformCount++].setTo("UNI_Tex1");
 
     createShader();
 }
@@ -303,12 +203,36 @@
 
 void ProgramFragmentState::init(Context *rsc)
 {
-    uint32_t tmp[] = {
-        RS_TEX_ENV_MODE_NONE, 0,
-        RS_TEX_ENV_MODE_NONE, 0,
-        0, 0
-    };
-    ProgramFragment *pf = new ProgramFragment(rsc, tmp, 6);
+    String8 shaderString(RS_SHADER_INTERNAL);
+    shaderString.append("varying lowp vec4 varColor;\n");
+    shaderString.append("varying vec4 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(constAlloc, 0);
+    pf->setConstantColor(1.0f, 1.0f, 1.0f, 1.0f);
+
     mDefault.set(pf);
 }
 
diff --git a/libs/rs/rsProgramFragment.h b/libs/rs/rsProgramFragment.h
index f08bb25..fb78b3f 100644
--- a/libs/rs/rsProgramFragment.h
+++ b/libs/rs/rsProgramFragment.h
@@ -34,7 +34,6 @@
                              uint32_t paramLength);
     virtual ~ProgramFragment();
 
-    virtual void setupGL(const Context *, ProgramFragmentState *);
     virtual void setupGL2(const Context *, ProgramFragmentState *, ShaderCache *sc);
 
     virtual void createShader();
diff --git a/libs/rs/rsProgramRaster.cpp b/libs/rs/rsProgramRaster.cpp
index 5b69370..62d060d 100644
--- a/libs/rs/rsProgramRaster.cpp
+++ b/libs/rs/rsProgramRaster.cpp
@@ -61,52 +61,6 @@
     mDirty = true;
 }
 
-void ProgramRaster::setupGL(const Context *rsc, ProgramRasterState *state)
-{
-    if (state->mLast.get() == this && !mDirty) {
-        return;
-    }
-    state->mLast.set(this);
-    mDirty = false;
-
-    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()) {
-#ifndef ANDROID_RS_BUILD_FOR_HOST
-        if (mPointSprite) {
-            glEnable(GL_POINT_SPRITE_OES);
-        } else {
-            glDisable(GL_POINT_SPRITE_OES);
-        }
-#endif //ANDROID_RS_BUILD_FOR_HOST
-    }
-
-    switch(mCull) {
-        case RS_CULL_BACK:
-            glEnable(GL_CULL_FACE);
-            glCullFace(GL_BACK);
-            break;
-        case RS_CULL_FRONT:
-            glEnable(GL_CULL_FACE);
-            glCullFace(GL_FRONT);
-            break;
-        case RS_CULL_NONE:
-            glDisable(GL_CULL_FACE);
-            break;
-    }
-}
-
 void ProgramRaster::setupGL2(const Context *rsc, ProgramRasterState *state)
 {
     if (state->mLast.get() == this && !mDirty) {
diff --git a/libs/rs/rsProgramRaster.h b/libs/rs/rsProgramRaster.h
index 801ab2a..d5ed686 100644
--- a/libs/rs/rsProgramRaster.h
+++ b/libs/rs/rsProgramRaster.h
@@ -34,7 +34,6 @@
                   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; }
diff --git a/libs/rs/rsProgramStore.cpp b/libs/rs/rsProgramStore.cpp
index e741c0a..3f90d7a 100644
--- a/libs/rs/rsProgramStore.cpp
+++ b/libs/rs/rsProgramStore.cpp
@@ -56,41 +56,6 @@
 {
 }
 
-void ProgramStore::setupGL(const Context *rsc, ProgramStoreState *state)
-{
-    if (state->mLast.get() == this) {
-        return;
-    }
-    state->mLast.set(this);
-
-    glColorMask(mColorRWriteEnable,
-                mColorGWriteEnable,
-                mColorBWriteEnable,
-                mColorAWriteEnable);
-    if (mBlendEnable) {
-        glEnable(GL_BLEND);
-        glBlendFunc(mBlendSrc, mBlendDst);
-    } else {
-        glDisable(GL_BLEND);
-    }
-
-    //LOGE("pfs  %i, %i, %x", mDepthWriteEnable, mDepthTestEnable, mDepthFunc);
-
-    glDepthMask(mDepthWriteEnable);
-    if(mDepthTestEnable || mDepthWriteEnable) {
-        glEnable(GL_DEPTH_TEST);
-        glDepthFunc(mDepthFunc);
-    } else {
-        glDisable(GL_DEPTH_TEST);
-    }
-
-    if (mDitherEnable) {
-        glEnable(GL_DITHER);
-    } else {
-        glDisable(GL_DITHER);
-    }
-}
-
 void ProgramStore::setupGL2(const Context *rsc, ProgramStoreState *state)
 {
     if (state->mLast.get() == this) {
diff --git a/libs/rs/rsProgramStore.h b/libs/rs/rsProgramStore.h
index fe8d78e..95bcf3c 100644
--- a/libs/rs/rsProgramStore.h
+++ b/libs/rs/rsProgramStore.h
@@ -32,7 +32,6 @@
     ProgramStore(Context *);
     virtual ~ProgramStore();
 
-    virtual void setupGL(const Context *, ProgramStoreState *);
     virtual void setupGL2(const Context *, ProgramStoreState *);
 
     void setDepthFunc(RsDepthFunc);
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index 68e3705..6446b55 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -68,26 +68,13 @@
     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)
-{
-    assert(0);
-}
-
 void ProgramVertex::loadShader(Context *rsc) {
     Program::loadShader(rsc, GL_VERTEX_SHADER);
 }
 
 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");
 
         appendUserConstants();
 
@@ -118,28 +105,8 @@
         }
         mShader.append(mUserShader);
     } else {
-        mShader.append("attribute vec4 ATTRIB_position;\n");
-        mShader.append("attribute vec4 ATTRIB_color;\n");
-        mShader.append("attribute vec3 ATTRIB_normal;\n");
-        mShader.append("attribute vec4 ATTRIB_texture0;\n");
-
-        for (uint32_t ct=0; ct < mUniformCount; ct++) {
-            mShader.append("uniform mat4 ");
-            mShader.append(mUniformNames[ct]);
-            mShader.append(";\n");
-        }
-
-        mShader.append("void main() {\n");
-        mShader.append("  gl_Position = UNI_MVP * ATTRIB_position;\n");
-        mShader.append("  gl_PointSize = 1.0;\n");
-
-        mShader.append("  varColor = ATTRIB_color;\n");
-        if (mTextureMatrixEnable) {
-            mShader.append("  varTex0 = UNI_TexMatrix * ATTRIB_texture0;\n");
-        } else {
-            mShader.append("  varTex0 = ATTRIB_texture0;\n");
-        }
-        mShader.append("}\n");
+        LOGE("ProgramFragment::createShader cannot create program, shader code not defined");
+        rsAssert(0);
     }
 }
 
@@ -152,18 +119,16 @@
 
     rsc->checkError("ProgramVertex::setupGL2 start");
 
-    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()) {
+        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");
@@ -183,6 +148,9 @@
 
 void ProgramVertex::setProjectionMatrix(const rsc_Matrix *m) const
 {
+    if(isUserProgram()) {
+        return;
+    }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
     memcpy(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET], m, sizeof(rsc_Matrix));
     mDirty = true;
@@ -190,6 +158,9 @@
 
 void ProgramVertex::setModelviewMatrix(const rsc_Matrix *m) const
 {
+    if(isUserProgram()) {
+        return;
+    }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
     memcpy(&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET], m, sizeof(rsc_Matrix));
     mDirty = true;
@@ -197,6 +168,9 @@
 
 void ProgramVertex::setTextureMatrix(const rsc_Matrix *m) const
 {
+    if(isUserProgram()) {
+        return;
+    }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
     memcpy(&f[RS_PROGRAM_VERTEX_TEXTURE_OFFSET], m, sizeof(rsc_Matrix));
     mDirty = true;
@@ -204,12 +178,18 @@
 
 void ProgramVertex::getProjectionMatrix(rsc_Matrix *m) const
 {
+    if(isUserProgram()) {
+        return;
+    }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
     memcpy(m, &f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET], sizeof(rsc_Matrix));
 }
 
 void ProgramVertex::transformToScreen(const 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],
@@ -225,17 +205,11 @@
             initAddUserElement(mInputElements[ct].get(), mAttribNames, &mAttribCount, "ATTRIB_");
         }
 
-        mUniformCount = 1;
-        mUniformNames[0].setTo("UNI_MVP");
+        mUniformCount = 0;
         for (uint32_t ct=0; ct < mConstantCount; ct++) {
             initAddUserElement(mConstantTypes[ct]->getElement(), mUniformNames, &mUniformCount, "UNI_");
         }
-    } else {
-        mUniformCount = 2;
-        mUniformNames[0].setTo("UNI_MVP");
-        mUniformNames[1].setTo("UNI_TexMatrix");
     }
-
     createShader();
 }
 
@@ -262,41 +236,78 @@
 
 void ProgramVertexState::init(Context *rsc)
 {
-#ifndef ANDROID_RS_BUILD_FOR_HOST
-    RsElement e = (RsElement) Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 1);
+    const Element *matrixElem = Element::create(rsc, RS_TYPE_MATRIX_4X4, RS_KIND_USER, false, 1);
+    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(f4Elem, "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 vec4 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(alloc, 0);
 
     mDefaultAlloc.set(alloc);
     mDefault.set(pv);
-    pv->init(rsc);
     pv->bindAllocation(alloc, 0);
 
     updateSize(rsc);
-#endif //ANDROID_RS_BUILD_FOR_HOST
 
 }
 
 void ProgramVertexState::updateSize(Context *rsc)
 {
+    float *f = static_cast<float *>(mDefaultAlloc->getPtr());
+
     Matrix m;
     m.loadOrtho(0,rsc->getWidth(), rsc->getHeight(),0, -1,1);
-    mDefaultAlloc->subData(rsc, RS_PROGRAM_VERTEX_PROJECTION_OFFSET, 16, &m.m[0], 16*4);
+    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(rsc, 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();
 }
 
diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h
index 59fd3195..65ce541 100644
--- a/libs/rs/rsProgramVertex.h
+++ b/libs/rs/rsProgramVertex.h
@@ -35,7 +35,6 @@
     ProgramVertex(Context *, bool texMat);
     virtual ~ProgramVertex();
 
-    virtual void setupGL(const Context *rsc, ProgramVertexState *state);
     virtual void setupGL2(const Context *rsc, ProgramVertexState *state, ShaderCache *sc);
 
 
@@ -79,8 +78,6 @@
     ObjectBaseRef<ProgramVertex> mDefault;
     ObjectBaseRef<ProgramVertex> mLast;
     ObjectBaseRef<Allocation> mDefaultAlloc;
-
-    ObjectBaseRef<Type> mAllocType;
 };
 
 
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index f41f295..c6a848c 100644
--- a/libs/rs/rsSampler.cpp
+++ b/libs/rs/rsSampler.cpp
@@ -59,7 +59,7 @@
 {
 }
 
-void Sampler::setupGL(const Context *rsc, bool npot)
+void Sampler::setupGL(const Context *rsc, const Allocation *tex)
 {
     GLenum trans[] = {
         GL_NEAREST, //RS_SAMPLER_NEAREST,
@@ -67,25 +67,33 @@
         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]);
 
-
-    rsc->checkError("ProgramFragment::setupGL2 tex env");
+    rsc->checkError("Sampler::setupGL2 tex env");
 }
 
 void Sampler::bindToContext(SamplerState *ss, uint32_t slot)
@@ -103,7 +111,7 @@
 
 void Sampler::serialize(OStream *stream) const
 {
-    
+
 }
 
 Sampler *Sampler::createFromStream(Context *rsc, IStream *stream)
diff --git a/libs/rs/rsSampler.h b/libs/rs/rsSampler.h
index 3786439..32a8efd 100644
--- a/libs/rs/rsSampler.h
+++ b/libs/rs/rsSampler.h
@@ -41,7 +41,7 @@
     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 *);
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index 662791d..cbc5df9 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -56,9 +56,16 @@
                 = nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC));
 
     for (uint32_t ct=0; ct < mEnviroment.mFieldCount; ct++) {
-        if (!mSlots[ct].get())
+        if (mSlots[ct].get() && !mTypes[ct].get()) {
+            mTypes[ct].set(mSlots[ct]->getType());
+        }
+
+        if (!mTypes[ct].get())
             continue;
-        void *ptr = mSlots[ct]->getPtr();
+        void *ptr = NULL;
+        if (mSlots[ct].get()) {
+            ptr = mSlots[ct]->getPtr();
+        }
         void **dest = ((void ***)mEnviroment.mFieldAddress)[ct];
         //LOGE("setupScript %i %p = %p    %p %i", ct, dest, ptr, mSlots[ct]->getType(), mSlots[ct]->getType()->getDimX());
 
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 41828dc..22fd421 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -263,13 +263,23 @@
 }
 
 static void SC_setObject(void **vdst, void * vsrc) {
-    static_cast<ObjectBase *>(vsrc)->incSysRef();
-    static_cast<ObjectBase *>(vdst[0])->decSysRef();
+    //LOGE("SC_setObject  %p,%p  %p", vdst, *vdst, vsrc);
+    if (vsrc) {
+        static_cast<ObjectBase *>(vsrc)->incSysRef();
+    }
+    if (vdst[0]) {
+        static_cast<ObjectBase *>(vdst[0])->decSysRef();
+    }
     *vdst = vsrc;
+    //LOGE("SC_setObject *");
 }
 static void SC_clearObject(void **vdst) {
-    static_cast<ObjectBase *>(vdst[0])->decSysRef();
+    //LOGE("SC_clearObject  %p,%p", vdst, *vdst);
+    if (vdst[0]) {
+        static_cast<ObjectBase *>(vdst[0])->decSysRef();
+    }
     *vdst = NULL;
+    //LOGE("SC_clearObject *");
 }
 static bool SC_isObject(RsAllocation vsrc) {
     return vsrc != NULL;
@@ -423,10 +433,51 @@
     { "_Z14rsGetElementAt13rs_allocationjj", (void *)&SC_getElementAtXY },
     { "_Z14rsGetElementAt13rs_allocationjjj", (void *)&SC_getElementAtXYZ },
 
-    { "_Z11rsSetObjectP13rs_allocation13rs_allocation", (void *)&SC_setObject },
+    { "_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 },
 
 
diff --git a/libs/rs/rsVertexArray.h b/libs/rs/rsVertexArray.h
index 7c609b2..bd76d87 100644
--- a/libs/rs/rsVertexArray.h
+++ b/libs/rs/rsVertexArray.h
@@ -65,7 +65,6 @@
     //void addLegacy(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset);
     void add(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset, const char *name);
 
-    void setupGL(const Context *rsc, class VertexArrayState *) const;
     void setupGL2(const Context *rsc, class VertexArrayState *, ShaderCache *) const;
     void logAttrib(uint32_t idx, uint32_t slot) const;
 
diff --git a/libs/surfaceflinger_client/Surface.cpp b/libs/surfaceflinger_client/Surface.cpp
index 2bc5fad..c77d48e 100644
--- a/libs/surfaceflinger_client/Surface.cpp
+++ b/libs/surfaceflinger_client/Surface.cpp
@@ -32,6 +32,7 @@
 #include <ui/DisplayInfo.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicBufferMapper.h>
+#include <ui/GraphicLog.h>
 #include <ui/Rect.h>
 
 #include <surfaceflinger/Surface.h>
@@ -578,7 +579,13 @@
     if (err != NO_ERROR)
         return err;
 
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_APP_DEQUEUE_BEFORE, mIdentity, -1);
+
     ssize_t bufIdx = mSharedBufferClient->dequeue();
+
+    logger.log(GraphicLog::SF_APP_DEQUEUE_AFTER, mIdentity, bufIdx);
+
     if (bufIdx < 0) {
         LOGE("error dequeuing a buffer (%s)", strerror(bufIdx));
         return bufIdx;
@@ -627,13 +634,20 @@
         return err;
 
     int32_t bufIdx = getBufferIndex(GraphicBuffer::getSelf(buffer));
+
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_APP_LOCK_BEFORE, mIdentity, bufIdx);
+
     err = mSharedBufferClient->lock(bufIdx);
+
+    logger.log(GraphicLog::SF_APP_LOCK_AFTER, mIdentity, bufIdx);
+
     LOGE_IF(err, "error locking buffer %d (%s)", bufIdx, strerror(-err));
     return err;
 }
 
 int Surface::queueBuffer(android_native_buffer_t* buffer)
-{   
+{
     status_t err = validate();
     if (err != NO_ERROR)
         return err;
@@ -643,6 +657,9 @@
     }
     
     int32_t bufIdx = getBufferIndex(GraphicBuffer::getSelf(buffer));
+
+    GraphicLog::getInstance().log(GraphicLog::SF_APP_QUEUE, mIdentity, bufIdx);
+
     mSharedBufferClient->setTransform(bufIdx, mNextBufferTransform);
     mSharedBufferClient->setCrop(bufIdx, mNextBufferCrop);
     mSharedBufferClient->setDirtyRegion(bufIdx, mDirtyRegion);
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index 9f49348..c4a09d6 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -9,6 +9,7 @@
 	GraphicBuffer.cpp \
 	GraphicBufferAllocator.cpp \
 	GraphicBufferMapper.cpp \
+	GraphicLog.cpp \
 	KeyLayoutMap.cpp \
 	KeyCharacterMap.cpp \
 	Input.cpp \
diff --git a/libs/ui/FramebufferNativeWindow.cpp b/libs/ui/FramebufferNativeWindow.cpp
index 6f8948d..a36d555 100644
--- a/libs/ui/FramebufferNativeWindow.cpp
+++ b/libs/ui/FramebufferNativeWindow.cpp
@@ -29,6 +29,7 @@
 
 #include <ui/Rect.h>
 #include <ui/FramebufferNativeWindow.h>
+#include <ui/GraphicLog.h>
 
 #include <EGL/egl.h>
 
@@ -174,6 +175,14 @@
     return fb->setSwapInterval(fb, interval);
 }
 
+// only for debugging / logging
+int FramebufferNativeWindow::getCurrentBufferIndex() const
+{
+    Mutex::Autolock _l(mutex);
+    const int index = mCurrentBufferIndex;
+    return index;
+}
+
 int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window, 
         android_native_buffer_t** buffer)
 {
@@ -181,18 +190,24 @@
     Mutex::Autolock _l(self->mutex);
     framebuffer_device_t* fb = self->fbDev;
 
+    int index = self->mBufferHead++;
+    if (self->mBufferHead >= self->mNumBuffers)
+        self->mBufferHead = 0;
+
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_FB_DEQUEUE_BEFORE, index);
+
     // wait for a free buffer
     while (!self->mNumFreeBuffers) {
         self->mCondition.wait(self->mutex);
     }
     // get this buffer
     self->mNumFreeBuffers--;
-    int index = self->mBufferHead++;
-    if (self->mBufferHead >= self->mNumBuffers)
-        self->mBufferHead = 0;
+    self->mCurrentBufferIndex = index;
 
     *buffer = self->buffers[index].get();
 
+    logger.log(GraphicLog::SF_FB_DEQUEUE_AFTER, index);
     return 0;
 }
 
@@ -202,11 +217,17 @@
     FramebufferNativeWindow* self = getSelf(window);
     Mutex::Autolock _l(self->mutex);
 
+    const int index = self->mCurrentBufferIndex;
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_FB_LOCK_BEFORE, index);
+
     // wait that the buffer we're locking is not front anymore
     while (self->front == buffer) {
         self->mCondition.wait(self->mutex);
     }
 
+    logger.log(GraphicLog::SF_FB_LOCK_AFTER, index);
+
     return NO_ERROR;
 }
 
@@ -217,7 +238,15 @@
     Mutex::Autolock _l(self->mutex);
     framebuffer_device_t* fb = self->fbDev;
     buffer_handle_t handle = static_cast<NativeBuffer*>(buffer)->handle;
+
+    const int index = self->mCurrentBufferIndex;
+    GraphicLog& logger(GraphicLog::getInstance());
+    logger.log(GraphicLog::SF_FB_POST_BEFORE, index);
+
     int res = fb->post(fb, handle);
+
+    logger.log(GraphicLog::SF_FB_POST_AFTER, index);
+
     self->front = static_cast<NativeBuffer*>(buffer);
     self->mNumFreeBuffers++;
     self->mCondition.broadcast();
diff --git a/libs/ui/GraphicLog.cpp b/libs/ui/GraphicLog.cpp
new file mode 100644
index 0000000..7ba2779
--- /dev/null
+++ b/libs/ui/GraphicLog.cpp
@@ -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.
+ */
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <utils/Endian.h>
+#include <utils/Timers.h>
+
+#include <ui/GraphicLog.h>
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(GraphicLog)
+
+static inline
+void writeInt32(uint8_t* base, size_t& pos, int32_t value) {
+#ifdef HAVE_LITTLE_ENDIAN
+    int32_t v = value;
+#else
+    int32_t v = htole32(value);
+#endif
+    base[pos] = EVENT_TYPE_INT;
+    memcpy(&base[pos+1], &v, sizeof(int32_t));
+    pos += 1+sizeof(int32_t);
+}
+
+static inline
+void writeInt64(uint8_t* base,  size_t& pos, int64_t value) {
+#ifdef HAVE_LITTLE_ENDIAN
+    int64_t v = value;
+#else
+    int64_t v = htole64(value);
+#endif
+    base[pos] = EVENT_TYPE_LONG;
+    memcpy(&base[pos+1], &v, sizeof(int64_t));
+    pos += 1+sizeof(int64_t);
+}
+
+void GraphicLog::logImpl(int32_t tag, int32_t buffer)
+{
+    uint8_t scratch[2 + 2 + sizeof(int32_t) + sizeof(int64_t)];
+    size_t pos = 0;
+    scratch[pos++] = EVENT_TYPE_LIST;
+    scratch[pos++] = 2;
+    writeInt32(scratch, pos, buffer);
+    writeInt64(scratch, pos, ns2ms( systemTime( SYSTEM_TIME_MONOTONIC ) ));
+    android_bWriteLog(tag, scratch, sizeof(scratch));
+}
+
+void GraphicLog::logImpl(int32_t tag, int32_t identity, int32_t buffer)
+{
+    uint8_t scratch[2 + 3 + sizeof(int32_t) + sizeof(int32_t) + sizeof(int64_t)];
+    size_t pos = 0;
+    scratch[pos++] = EVENT_TYPE_LIST;
+    scratch[pos++] = 3;
+    writeInt32(scratch, pos, buffer);
+    writeInt32(scratch, pos, identity);
+    writeInt64(scratch, pos, ns2ms( systemTime( SYSTEM_TIME_MONOTONIC ) ));
+    android_bWriteLog(tag, scratch, sizeof(scratch));
+}
+
+GraphicLog::GraphicLog()
+    : mEnabled(0)
+{
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get("debug.graphic_log", property, NULL) > 0) {
+        mEnabled = atoi(property);
+    }
+}
+
+void GraphicLog::setEnabled(bool enable)
+{
+    mEnabled = enable;
+}
+
+}
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index df232d4..1cf7592 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -31,8 +31,15 @@
 // Log debug messages about input event throttling.
 #define DEBUG_THROTTLING 0
 
+// Log debug messages about input focus tracking.
+#define DEBUG_FOCUS 0
+
+// Log debug messages about the app switch latency optimization.
+#define DEBUG_APP_SWITCH 0
+
 #include <cutils/log.h>
 #include <ui/InputDispatcher.h>
+#include <ui/PowerManager.h>
 
 #include <stddef.h>
 #include <unistd.h>
@@ -41,31 +48,62 @@
 
 namespace android {
 
-// TODO, this needs to be somewhere else, perhaps in the policy
-static inline bool isMovementKey(int32_t keyCode) {
-    return keyCode == AKEYCODE_DPAD_UP
-            || keyCode == AKEYCODE_DPAD_DOWN
-            || keyCode == AKEYCODE_DPAD_LEFT
-            || keyCode == AKEYCODE_DPAD_RIGHT;
-}
+// Delay between reporting long touch events to the power manager.
+const nsecs_t EVENT_IGNORE_DURATION = 300 * 1000000LL; // 300 ms
+
+// Default input dispatching timeout if there is no focused application or paused window
+// from which to determine an appropriate dispatching timeout.
+const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
+
+// Amount of time to allow for all pending events to be processed when an app switch
+// key is on the way.  This is used to preempt input dispatch and drop input events
+// when an application takes too long to respond and the user has pressed an app switch key.
+const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
 
 static inline nsecs_t now() {
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+
+// --- InputWindow ---
+
+bool InputWindow::visibleFrameIntersects(const InputWindow* other) const {
+    return visibleFrameRight > other->visibleFrameLeft
+        && visibleFrameLeft < other->visibleFrameRight
+        && visibleFrameBottom > other->visibleFrameTop
+        && visibleFrameTop < other->visibleFrameBottom;
+}
+
+bool InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const {
+    return x >= touchableAreaLeft && x <= touchableAreaRight
+            && y >= touchableAreaTop && y <= touchableAreaBottom;
+}
+
+
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
-    mPolicy(policy) {
-    mPollLoop = new PollLoop(false);
+    mPolicy(policy),
+    mPendingEvent(NULL), mAppSwitchDueTime(LONG_LONG_MAX),
+    mDispatchEnabled(true), mDispatchFrozen(false),
+    mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL),
+    mFocusedApplication(NULL),
+    mCurrentInputTargetsValid(false),
+    mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
+    mLooper = new Looper(false);
 
-    mInboundQueue.head.refCount = -1;
-    mInboundQueue.head.type = EventEntry::TYPE_SENTINEL;
-    mInboundQueue.head.eventTime = LONG_LONG_MIN;
+    mInboundQueue.headSentinel.refCount = -1;
+    mInboundQueue.headSentinel.type = EventEntry::TYPE_SENTINEL;
+    mInboundQueue.headSentinel.eventTime = LONG_LONG_MIN;
 
-    mInboundQueue.tail.refCount = -1;
-    mInboundQueue.tail.type = EventEntry::TYPE_SENTINEL;
-    mInboundQueue.tail.eventTime = LONG_LONG_MAX;
+    mInboundQueue.tailSentinel.refCount = -1;
+    mInboundQueue.tailSentinel.type = EventEntry::TYPE_SENTINEL;
+    mInboundQueue.tailSentinel.eventTime = LONG_LONG_MAX;
 
     mKeyRepeatState.lastKeyEntry = NULL;
 
@@ -77,189 +115,265 @@
     mThrottleState.originalSampleCount = 0;
     LOGD("Throttling - Max events per second = %d", maxEventsPerSecond);
 #endif
-
-    mCurrentInputTargetsValid = false;
 }
 
 InputDispatcher::~InputDispatcher() {
-    resetKeyRepeatLocked();
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        resetKeyRepeatLocked();
+        releasePendingEventLocked();
+        drainInboundQueueLocked();
+    }
 
     while (mConnectionsByReceiveFd.size() != 0) {
         unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel);
     }
-
-    for (EventEntry* entry = mInboundQueue.head.next; entry != & mInboundQueue.tail; ) {
-        EventEntry* next = entry->next;
-        mAllocator.releaseEventEntry(next);
-        entry = next;
-    }
 }
 
 void InputDispatcher::dispatchOnce() {
     nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
     nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
 
-    bool skipPoll = false;
-    nsecs_t currentTime;
     nsecs_t nextWakeupTime = LONG_LONG_MAX;
     { // acquire lock
         AutoMutex _l(mLock);
-        currentTime = now();
+        dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);
 
-        // Reset the key repeat timer whenever we disallow key events, even if the next event
-        // is not a key.  This is to ensure that we abort a key repeat if the device is just coming
-        // out of sleep.
-        // XXX we should handle resetting input state coming out of sleep more generally elsewhere
-        if (keyRepeatTimeout < 0) {
-            resetKeyRepeatLocked();
+        if (runCommandsLockedInterruptible()) {
+            nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
         }
-
-        // Detect and process timeouts for all connections and determine if there are any
-        // synchronous event dispatches pending.  This step is entirely non-interruptible.
-        bool hasPendingSyncTarget = false;
-        size_t activeConnectionCount = mActiveConnections.size();
-        for (size_t i = 0; i < activeConnectionCount; i++) {
-            Connection* connection = mActiveConnections.itemAt(i);
-
-            if (connection->hasPendingSyncTarget()) {
-                hasPendingSyncTarget = true;
-            }
-
-            nsecs_t connectionTimeoutTime  = connection->nextTimeoutTime;
-            if (connectionTimeoutTime <= currentTime) {
-                mTimedOutConnections.add(connection);
-            } else if (connectionTimeoutTime < nextWakeupTime) {
-                nextWakeupTime = connectionTimeoutTime;
-            }
-        }
-
-        size_t timedOutConnectionCount = mTimedOutConnections.size();
-        for (size_t i = 0; i < timedOutConnectionCount; i++) {
-            Connection* connection = mTimedOutConnections.itemAt(i);
-            timeoutDispatchCycleLocked(currentTime, connection);
-            skipPoll = true;
-        }
-        mTimedOutConnections.clear();
-
-        // If we don't have a pending sync target, then we can begin delivering a new event.
-        // (Otherwise we wait for dispatch to complete for that target.)
-        if (! hasPendingSyncTarget) {
-            if (mInboundQueue.isEmpty()) {
-                if (mKeyRepeatState.lastKeyEntry) {
-                    if (currentTime >= mKeyRepeatState.nextRepeatTime) {
-                        processKeyRepeatLockedInterruptible(currentTime, keyRepeatDelay);
-                        skipPoll = true;
-                    } else {
-                        if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) {
-                            nextWakeupTime = mKeyRepeatState.nextRepeatTime;
-                        }
-                    }
-                }
-            } else {
-                // Inbound queue has at least one entry.
-                EventEntry* entry = mInboundQueue.head.next;
-
-                // Consider throttling the entry if it is a move event and there are no
-                // other events behind it in the queue.  Due to movement batching, additional
-                // samples may be appended to this event by the time the throttling timeout
-                // expires.
-                // TODO Make this smarter and consider throttling per device independently.
-                if (entry->type == EventEntry::TYPE_MOTION) {
-                    MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
-                    int32_t deviceId = motionEntry->deviceId;
-                    uint32_t source = motionEntry->source;
-                    if (motionEntry->next == & mInboundQueue.tail
-                            && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
-                            && deviceId == mThrottleState.lastDeviceId
-                            && source == mThrottleState.lastSource) {
-                        nsecs_t nextTime = mThrottleState.lastEventTime
-                                + mThrottleState.minTimeBetweenEvents;
-                        if (currentTime < nextTime) {
-                            // Throttle it!
-#if DEBUG_THROTTLING
-                            LOGD("Throttling - Delaying motion event for "
-                                    "device 0x%x, source 0x%08x by up to %0.3fms.",
-                                    deviceId, source, (nextTime - currentTime) * 0.000001);
-#endif
-                            if (nextTime < nextWakeupTime) {
-                                nextWakeupTime = nextTime;
-                            }
-                            if (mThrottleState.originalSampleCount == 0) {
-                                mThrottleState.originalSampleCount =
-                                        motionEntry->countSamples();
-                            }
-                            goto Throttle;
-                        }
-                    }
-
-#if DEBUG_THROTTLING
-                    if (mThrottleState.originalSampleCount != 0) {
-                        uint32_t count = motionEntry->countSamples();
-                        LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
-                                count - mThrottleState.originalSampleCount,
-                                mThrottleState.originalSampleCount, count);
-                        mThrottleState.originalSampleCount = 0;
-                    }
-#endif
-
-                    mThrottleState.lastEventTime = entry->eventTime < currentTime
-                            ? entry->eventTime : currentTime;
-                    mThrottleState.lastDeviceId = deviceId;
-                    mThrottleState.lastSource = source;
-                }
-
-                // Start processing the entry but leave it on the queue until later so that the
-                // input reader can keep appending samples onto a motion event between the
-                // time we started processing it and the time we finally enqueue dispatch
-                // entries for it.
-                switch (entry->type) {
-                case EventEntry::TYPE_CONFIGURATION_CHANGED: {
-                    ConfigurationChangedEntry* typedEntry =
-                            static_cast<ConfigurationChangedEntry*>(entry);
-                    processConfigurationChangedLockedInterruptible(currentTime, typedEntry);
-                    break;
-                }
-
-                case EventEntry::TYPE_KEY: {
-                    KeyEntry* typedEntry = static_cast<KeyEntry*>(entry);
-                    processKeyLockedInterruptible(currentTime, typedEntry, keyRepeatTimeout);
-                    break;
-                }
-
-                case EventEntry::TYPE_MOTION: {
-                    MotionEntry* typedEntry = static_cast<MotionEntry*>(entry);
-                    processMotionLockedInterruptible(currentTime, typedEntry);
-                    break;
-                }
-
-                default:
-                    assert(false);
-                    break;
-                }
-
-                // Dequeue and release the event entry that we just processed.
-                mInboundQueue.dequeue(entry);
-                mAllocator.releaseEventEntry(entry);
-                skipPoll = true;
-
-            Throttle: ;
-            }
-        }
-
-        // Run any deferred commands.
-        skipPoll |= runCommandsLockedInterruptible();
     } // release lock
 
-    // If we dispatched anything, don't poll just now.  Wait for the next iteration.
-    // Contents may have shifted during flight.
-    if (skipPoll) {
+    // Wait for callback or timeout or wake.  (make sure we round up, not down)
+    nsecs_t currentTime = now();
+    int32_t timeoutMillis;
+    if (nextWakeupTime > currentTime) {
+        uint64_t timeout = uint64_t(nextWakeupTime - currentTime);
+        timeout = (timeout + 999999LL) / 1000000LL;
+        timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout);
+    } else {
+        timeoutMillis = 0;
+    }
+
+    mLooper->pollOnce(timeoutMillis);
+}
+
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,
+        nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) {
+    nsecs_t currentTime = now();
+
+    // Reset the key repeat timer whenever we disallow key events, even if the next event
+    // is not a key.  This is to ensure that we abort a key repeat if the device is just coming
+    // out of sleep.
+    if (keyRepeatTimeout < 0) {
+        resetKeyRepeatLocked();
+    }
+
+    // If dispatching is disabled, drop all events in the queue.
+    if (! mDispatchEnabled) {
+        if (mPendingEvent || ! mInboundQueue.isEmpty()) {
+            LOGI("Dropping pending events because input dispatch is disabled.");
+            releasePendingEventLocked();
+            drainInboundQueueLocked();
+        }
         return;
     }
 
-    // Wait for callback or timeout or wake.  (make sure we round up, not down)
-    nsecs_t timeout = (nextWakeupTime - currentTime + 999999LL) / 1000000LL;
-    int32_t timeoutMillis = timeout > INT_MAX ? -1 : timeout > 0 ? int32_t(timeout) : 0;
-    mPollLoop->pollOnce(timeoutMillis);
+    // If dispatching is frozen, do not process timeouts or try to deliver any new events.
+    if (mDispatchFrozen) {
+#if DEBUG_FOCUS
+        LOGD("Dispatch frozen.  Waiting some more.");
+#endif
+        return;
+    }
+
+    // Optimize latency of app switches.
+    // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
+    // been pressed.  When it expires, we preempt dispatch and drop all other pending events.
+    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
+    if (mAppSwitchDueTime < *nextWakeupTime) {
+        *nextWakeupTime = mAppSwitchDueTime;
+    }
+
+    // Ready to start a new event.
+    // If we don't already have a pending event, go grab one.
+    if (! mPendingEvent) {
+        if (mInboundQueue.isEmpty()) {
+            if (isAppSwitchDue) {
+                // The inbound queue is empty so the app switch key we were waiting
+                // for will never arrive.  Stop waiting for it.
+                resetPendingAppSwitchLocked(false);
+                isAppSwitchDue = false;
+            }
+
+            // Synthesize a key repeat if appropriate.
+            if (mKeyRepeatState.lastKeyEntry) {
+                if (currentTime >= mKeyRepeatState.nextRepeatTime) {
+                    mPendingEvent = synthesizeKeyRepeatLocked(currentTime, keyRepeatDelay);
+                } else {
+                    if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
+                        *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
+                    }
+                }
+            }
+            if (! mPendingEvent) {
+                return;
+            }
+        } else {
+            // Inbound queue has at least one entry.
+            EventEntry* entry = mInboundQueue.headSentinel.next;
+
+            // Throttle the entry if it is a move event and there are no
+            // other events behind it in the queue.  Due to movement batching, additional
+            // samples may be appended to this event by the time the throttling timeout
+            // expires.
+            // TODO Make this smarter and consider throttling per device independently.
+            if (entry->type == EventEntry::TYPE_MOTION) {
+                MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+                int32_t deviceId = motionEntry->deviceId;
+                uint32_t source = motionEntry->source;
+                if (! isAppSwitchDue
+                        && motionEntry->next == & mInboundQueue.tailSentinel // exactly one event
+                        && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
+                        && deviceId == mThrottleState.lastDeviceId
+                        && source == mThrottleState.lastSource) {
+                    nsecs_t nextTime = mThrottleState.lastEventTime
+                            + mThrottleState.minTimeBetweenEvents;
+                    if (currentTime < nextTime) {
+                        // Throttle it!
+#if DEBUG_THROTTLING
+                        LOGD("Throttling - Delaying motion event for "
+                                "device 0x%x, source 0x%08x by up to %0.3fms.",
+                                deviceId, source, (nextTime - currentTime) * 0.000001);
+#endif
+                        if (nextTime < *nextWakeupTime) {
+                            *nextWakeupTime = nextTime;
+                        }
+                        if (mThrottleState.originalSampleCount == 0) {
+                            mThrottleState.originalSampleCount =
+                                    motionEntry->countSamples();
+                        }
+                        return;
+                    }
+                }
+
+#if DEBUG_THROTTLING
+                if (mThrottleState.originalSampleCount != 0) {
+                    uint32_t count = motionEntry->countSamples();
+                    LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
+                            count - mThrottleState.originalSampleCount,
+                            mThrottleState.originalSampleCount, count);
+                    mThrottleState.originalSampleCount = 0;
+                }
+#endif
+
+                mThrottleState.lastEventTime = entry->eventTime < currentTime
+                        ? entry->eventTime : currentTime;
+                mThrottleState.lastDeviceId = deviceId;
+                mThrottleState.lastSource = source;
+            }
+
+            mInboundQueue.dequeue(entry);
+            mPendingEvent = entry;
+        }
+    }
+
+    // Now we have an event to dispatch.
+    assert(mPendingEvent != NULL);
+    bool done = false;
+    switch (mPendingEvent->type) {
+    case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+        ConfigurationChangedEntry* typedEntry =
+                static_cast<ConfigurationChangedEntry*>(mPendingEvent);
+        done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+        break;
+    }
+
+    case EventEntry::TYPE_KEY: {
+        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
+        bool appSwitchKey = isAppSwitchKey(typedEntry->keyCode);
+        bool dropEvent = isAppSwitchDue && ! appSwitchKey;
+        done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout, dropEvent,
+                nextWakeupTime);
+        if (done) {
+            if (dropEvent) {
+                LOGI("Dropped key because of pending overdue app switch.");
+            } else if (appSwitchKey) {
+                resetPendingAppSwitchLocked(true);
+            }
+        }
+        break;
+    }
+
+    case EventEntry::TYPE_MOTION: {
+        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
+        bool dropEvent = isAppSwitchDue;
+        done = dispatchMotionLocked(currentTime, typedEntry, dropEvent, nextWakeupTime);
+        if (done) {
+            if (dropEvent) {
+                LOGI("Dropped motion because of pending overdue app switch.");
+            }
+        }
+        break;
+    }
+
+    default:
+        assert(false);
+        break;
+    }
+
+    if (done) {
+        releasePendingEventLocked();
+        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
+    }
+}
+
+bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
+    bool needWake = mInboundQueue.isEmpty();
+    mInboundQueue.enqueueAtTail(entry);
+
+    switch (entry->type) {
+    case EventEntry::TYPE_KEY:
+        needWake |= detectPendingAppSwitchLocked(static_cast<KeyEntry*>(entry));
+        break;
+    }
+
+    return needWake;
+}
+
+bool InputDispatcher::isAppSwitchKey(int32_t keyCode) {
+    return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL;
+}
+
+bool InputDispatcher::isAppSwitchPendingLocked() {
+    return mAppSwitchDueTime != LONG_LONG_MAX;
+}
+
+bool InputDispatcher::detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry) {
+    if (inboundKeyEntry->action == AKEY_EVENT_ACTION_UP
+            && ! (inboundKeyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
+            && isAppSwitchKey(inboundKeyEntry->keyCode)
+            && isEventFromReliableSourceLocked(inboundKeyEntry)) {
+#if DEBUG_APP_SWITCH
+        LOGD("App switch is pending!");
+#endif
+        mAppSwitchDueTime = inboundKeyEntry->eventTime + APP_SWITCH_TIMEOUT;
+        return true; // need wake
+    }
+    return false;
+}
+
+void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
+    mAppSwitchDueTime = LONG_LONG_MAX;
+
+#if DEBUG_APP_SWITCH
+    if (handled) {
+        LOGD("App switch has arrived.");
+    } else {
+        LOGD("App switch was abandoned.");
+    }
+#endif
 }
 
 bool InputDispatcher::runCommandsLockedInterruptible() {
@@ -285,78 +399,52 @@
     return commandEntry;
 }
 
-void InputDispatcher::processConfigurationChangedLockedInterruptible(
-        nsecs_t currentTime, ConfigurationChangedEntry* entry) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    LOGD("processConfigurationChanged - eventTime=%lld", entry->eventTime);
-#endif
-
-    // Reset key repeating in case a keyboard device was added or removed or something.
-    resetKeyRepeatLocked();
-
-    mLock.unlock();
-
-    mPolicy->notifyConfigurationChanged(entry->eventTime);
-
-    mLock.lock();
-}
-
-void InputDispatcher::processKeyLockedInterruptible(
-        nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    LOGD("processKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
-            "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
-            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action,
-            entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
-            entry->downTime);
-#endif
-
-    if (entry->action == AKEY_EVENT_ACTION_DOWN && ! entry->isInjected()) {
-        if (mKeyRepeatState.lastKeyEntry
-                && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
-            // We have seen two identical key downs in a row which indicates that the device
-            // driver is automatically generating key repeats itself.  We take note of the
-            // repeat here, but we disable our own next key repeat timer since it is clear that
-            // we will not need to synthesize key repeats ourselves.
-            entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
-            resetKeyRepeatLocked();
-            mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
-        } else {
-            // Not a repeat.  Save key down state in case we do see a repeat later.
-            resetKeyRepeatLocked();
-            mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout;
-        }
-        mKeyRepeatState.lastKeyEntry = entry;
-        entry->refCount += 1;
-    } else {
-        resetKeyRepeatLocked();
+void InputDispatcher::drainInboundQueueLocked() {
+    while (! mInboundQueue.isEmpty()) {
+        EventEntry* entry = mInboundQueue.dequeueAtHead();
+        releaseInboundEventLocked(entry);
     }
-
-    identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry);
 }
 
-void InputDispatcher::processKeyRepeatLockedInterruptible(
+void InputDispatcher::releasePendingEventLocked() {
+    if (mPendingEvent) {
+        releaseInboundEventLocked(mPendingEvent);
+        mPendingEvent = NULL;
+    }
+}
+
+void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) {
+    if (entry->injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("Inbound event was dropped.  Setting injection result to failed.");
+#endif
+        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+    }
+    mAllocator.releaseEventEntry(entry);
+}
+
+bool InputDispatcher::isEventFromReliableSourceLocked(EventEntry* entry) {
+    return ! entry->isInjected()
+            || entry->injectorUid == 0
+            || mPolicy->checkInjectEventsPermissionNonReentrant(
+                    entry->injectorPid, entry->injectorUid);
+}
+
+void InputDispatcher::resetKeyRepeatLocked() {
+    if (mKeyRepeatState.lastKeyEntry) {
+        mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
+        mKeyRepeatState.lastKeyEntry = NULL;
+    }
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked(
         nsecs_t currentTime, nsecs_t keyRepeatDelay) {
     KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
 
-    // Search the inbound queue for a key up corresponding to this device.
-    // It doesn't make sense to generate a key repeat event if the key is already up.
-    for (EventEntry* queuedEntry = mInboundQueue.head.next;
-            queuedEntry != & mInboundQueue.tail; queuedEntry = entry->next) {
-        if (queuedEntry->type == EventEntry::TYPE_KEY) {
-            KeyEntry* queuedKeyEntry = static_cast<KeyEntry*>(queuedEntry);
-            if (queuedKeyEntry->deviceId == entry->deviceId
-                    && entry->action == AKEY_EVENT_ACTION_UP) {
-                resetKeyRepeatLocked();
-                return;
-            }
-        }
-    }
-
-    // Synthesize a key repeat.
     // Reuse the repeated key entry if it is otherwise unreferenced.
     uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK;
     if (entry->refCount == 1) {
+        entry->recycle();
         entry->eventTime = currentTime;
         entry->policyFlags = policyFlags;
         entry->repeatCount += 1;
@@ -371,31 +459,222 @@
 
         entry = newEntry;
     }
+    entry->syntheticRepeat = true;
+
+    // Increment reference count since we keep a reference to the event in
+    // mKeyRepeatState.lastKeyEntry in addition to the one we return.
+    entry->refCount += 1;
 
     if (entry->repeatCount == 1) {
         entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
     }
 
     mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay;
-
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
-            "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
-            "repeatCount=%d, downTime=%lld",
-            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
-            entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
-            entry->repeatCount, entry->downTime);
-#endif
-
-    identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry);
+    return entry;
 }
 
-void InputDispatcher::processMotionLockedInterruptible(
-        nsecs_t currentTime, MotionEntry* entry) {
+bool InputDispatcher::dispatchConfigurationChangedLocked(
+        nsecs_t currentTime, ConfigurationChangedEntry* entry) {
 #if DEBUG_OUTBOUND_EVENT_DETAILS
-    LOGD("processMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
+    LOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime);
+#endif
+
+    // Reset key repeating in case a keyboard device was added or removed or something.
+    resetKeyRepeatLocked();
+
+    // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doNotifyConfigurationChangedInterruptible);
+    commandEntry->eventTime = entry->eventTime;
+    return true;
+}
+
+bool InputDispatcher::dispatchKeyLocked(
+        nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
+        bool dropEvent, nsecs_t* nextWakeupTime) {
+    // Give the policy a chance to intercept the key.
+    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+        bool trusted;
+        if (! dropEvent && mFocusedWindow) {
+            trusted = checkInjectionPermission(mFocusedWindow,
+                    entry->injectorPid, entry->injectorUid);
+        } else {
+            trusted = isEventFromReliableSourceLocked(entry);
+        }
+        if (trusted) {
+            CommandEntry* commandEntry = postCommandLocked(
+                    & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
+            if (! dropEvent && mFocusedWindow) {
+                commandEntry->inputChannel = mFocusedWindow->inputChannel;
+            }
+            commandEntry->keyEntry = entry;
+            entry->refCount += 1;
+            return false; // wait for the command to run
+        } else {
+            entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+        }
+    } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+        resetTargetsLocked();
+        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_SUCCEEDED);
+        return true;
+    }
+
+    // Clean up if dropping the event.
+    if (dropEvent) {
+        resetTargetsLocked();
+        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+        return true;
+    }
+
+    // Preprocessing.
+    if (! entry->dispatchInProgress) {
+        logOutboundKeyDetailsLocked("dispatchKey - ", entry);
+
+        if (entry->repeatCount == 0
+                && entry->action == AKEY_EVENT_ACTION_DOWN
+                && ! entry->isInjected()) {
+            if (mKeyRepeatState.lastKeyEntry
+                    && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
+                // We have seen two identical key downs in a row which indicates that the device
+                // driver is automatically generating key repeats itself.  We take note of the
+                // repeat here, but we disable our own next key repeat timer since it is clear that
+                // we will not need to synthesize key repeats ourselves.
+                entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
+                resetKeyRepeatLocked();
+                mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+            } else {
+                // Not a repeat.  Save key down state in case we do see a repeat later.
+                resetKeyRepeatLocked();
+                mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout;
+            }
+            mKeyRepeatState.lastKeyEntry = entry;
+            entry->refCount += 1;
+        } else if (! entry->syntheticRepeat) {
+            resetKeyRepeatLocked();
+        }
+
+        entry->dispatchInProgress = true;
+        resetTargetsLocked();
+    }
+
+    // Identify targets.
+    if (! mCurrentInputTargetsValid) {
+        InputWindow* window = NULL;
+        int32_t injectionResult = findFocusedWindowLocked(currentTime,
+                entry, nextWakeupTime, & window);
+        if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+            return false;
+        }
+
+        setInjectionResultLocked(entry, injectionResult);
+        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+            return true;
+        }
+
+        addMonitoringTargetsLocked();
+        commitTargetsLocked(window);
+    }
+
+    // Dispatch the key.
+    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+
+    // Poke user activity.
+    pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, POWER_MANAGER_BUTTON_EVENT);
+    return true;
+}
+
+void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("%seventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
+            "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
+            "downTime=%lld",
+            prefix,
+            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+            entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
+            entry->downTime);
+#endif
+}
+
+bool InputDispatcher::dispatchMotionLocked(
+        nsecs_t currentTime, MotionEntry* entry, bool dropEvent, nsecs_t* nextWakeupTime) {
+    // Clean up if dropping the event.
+    if (dropEvent) {
+        resetTargetsLocked();
+        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+        return true;
+    }
+
+    // Preprocessing.
+    if (! entry->dispatchInProgress) {
+        logOutboundMotionDetailsLocked("dispatchMotion - ", entry);
+
+        entry->dispatchInProgress = true;
+        resetTargetsLocked();
+    }
+
+    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
+
+    // Identify targets.
+    if (! mCurrentInputTargetsValid) {
+        InputWindow* window = NULL;
+        int32_t injectionResult;
+        if (isPointerEvent) {
+            // Pointer event.  (eg. touchscreen)
+            injectionResult = findTouchedWindowLocked(currentTime,
+                    entry, nextWakeupTime, & window);
+        } else {
+            // Non touch event.  (eg. trackball)
+            injectionResult = findFocusedWindowLocked(currentTime,
+                    entry, nextWakeupTime, & window);
+        }
+        if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+            return false;
+        }
+
+        setInjectionResultLocked(entry, injectionResult);
+        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+            return true;
+        }
+
+        addMonitoringTargetsLocked();
+        commitTargetsLocked(window);
+    }
+
+    // Dispatch the motion.
+    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+
+    // Poke user activity.
+    int32_t eventType;
+    if (isPointerEvent) {
+        switch (entry->action) {
+        case AMOTION_EVENT_ACTION_DOWN:
+            eventType = POWER_MANAGER_TOUCH_EVENT;
+            break;
+        case AMOTION_EVENT_ACTION_UP:
+            eventType = POWER_MANAGER_TOUCH_UP_EVENT;
+            break;
+        default:
+            if (entry->eventTime - entry->downTime >= EVENT_IGNORE_DURATION) {
+                eventType = POWER_MANAGER_TOUCH_EVENT;
+            } else {
+                eventType = POWER_MANAGER_LONG_TOUCH_EVENT;
+            }
+            break;
+        }
+    } else {
+        eventType = POWER_MANAGER_BUTTON_EVENT;
+    }
+    pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, eventType);
+    return true;
+}
+
+
+void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("%seventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
             "action=0x%x, flags=0x%x, "
             "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
+            prefix,
             entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
             entry->action, entry->flags,
             entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision,
@@ -403,7 +682,7 @@
 
     // Print the most recent sample that we have available, this may change due to batching.
     size_t sampleCount = 1;
-    MotionSample* sample = & entry->firstSample;
+    const MotionSample* sample = & entry->firstSample;
     for (; sample->next != NULL; sample = sample->next) {
         sampleCount += 1;
     }
@@ -425,68 +704,6 @@
         LOGD("  ... Total movement samples currently batched %d ...", sampleCount);
     }
 #endif
-
-    identifyInputTargetsAndDispatchMotionLockedInterruptible(currentTime, entry);
-}
-
-void InputDispatcher::identifyInputTargetsAndDispatchKeyLockedInterruptible(
-        nsecs_t currentTime, KeyEntry* entry) {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("identifyInputTargetsAndDispatchKey");
-#endif
-
-    entry->dispatchInProgress = true;
-    mCurrentInputTargetsValid = false;
-    mLock.unlock();
-
-    mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
-            entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
-            entry->downTime, entry->eventTime);
-
-    mCurrentInputTargets.clear();
-    int32_t injectionResult = mPolicy->waitForKeyEventTargets(& mReusableKeyEvent,
-            entry->policyFlags, entry->injectorPid, entry->injectorUid,
-            mCurrentInputTargets);
-
-    mLock.lock();
-    mCurrentInputTargetsValid = true;
-
-    setInjectionResultLocked(entry, injectionResult);
-
-    if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
-        dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
-    }
-}
-
-void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible(
-        nsecs_t currentTime, MotionEntry* entry) {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("identifyInputTargetsAndDispatchMotion");
-#endif
-
-    entry->dispatchInProgress = true;
-    mCurrentInputTargetsValid = false;
-    mLock.unlock();
-
-    mReusableMotionEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
-            entry->edgeFlags, entry->metaState,
-            0, 0, entry->xPrecision, entry->yPrecision,
-            entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
-            entry->firstSample.pointerCoords);
-
-    mCurrentInputTargets.clear();
-    int32_t injectionResult = mPolicy->waitForMotionEventTargets(& mReusableMotionEvent,
-            entry->policyFlags, entry->injectorPid, entry->injectorUid,
-            mCurrentInputTargets);
-
-    mLock.lock();
-    mCurrentInputTargetsValid = true;
-
-    setInjectionResultLocked(entry, injectionResult);
-
-    if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
-        dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
-    }
 }
 
 void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
@@ -494,7 +711,7 @@
 #if DEBUG_DISPATCH_CYCLE
     LOGD("dispatchEventToCurrentInputTargets - "
             "resumeWithAppendedMotionSample=%s",
-            resumeWithAppendedMotionSample ? "true" : "false");
+            toString(resumeWithAppendedMotionSample));
 #endif
 
     assert(eventEntry->dispatchInProgress); // should already have been set to true
@@ -502,7 +719,7 @@
     for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
         const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
 
-        ssize_t connectionIndex = getConnectionIndex(inputTarget.inputChannel);
+        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
         if (connectionIndex >= 0) {
             sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
             prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
@@ -515,23 +732,581 @@
     }
 }
 
+void InputDispatcher::resetTargetsLocked() {
+    mCurrentInputTargetsValid = false;
+    mCurrentInputTargets.clear();
+    mCurrentInputChannel.clear();
+    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+}
+
+void InputDispatcher::commitTargetsLocked(const InputWindow* window) {
+    mCurrentInputWindowType = window->layoutParamsType;
+    mCurrentInputChannel = window->inputChannel;
+    mCurrentInputTargetsValid = true;
+}
+
+int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
+        const EventEntry* entry, const InputApplication* application, const InputWindow* window,
+        nsecs_t* nextWakeupTime) {
+    if (application == NULL && window == NULL) {
+        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
+#if DEBUG_FOCUS
+            LOGD("Waiting for system to become ready for input.");
+#endif
+            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
+            mInputTargetWaitStartTime = currentTime;
+            mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
+            mInputTargetWaitTimeoutExpired = false;
+        }
+    } else {
+        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+#if DEBUG_FOCUS
+            LOGD("Waiting for application to become ready for input: %s",
+                    getApplicationWindowLabelLocked(application, window).string());
+#endif
+            nsecs_t timeout = window ? window->dispatchingTimeout :
+                application ? application->dispatchingTimeout : DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+
+            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
+            mInputTargetWaitStartTime = currentTime;
+            mInputTargetWaitTimeoutTime = currentTime + timeout;
+            mInputTargetWaitTimeoutExpired = false;
+        }
+    }
+
+    if (mInputTargetWaitTimeoutExpired) {
+        return INPUT_EVENT_INJECTION_TIMED_OUT;
+    }
+
+    if (currentTime >= mInputTargetWaitTimeoutTime) {
+        onANRLocked(currentTime, application, window, entry->eventTime, mInputTargetWaitStartTime);
+
+        // Force poll loop to wake up immediately on next iteration once we get the
+        // ANR response back from the policy.
+        *nextWakeupTime = LONG_LONG_MIN;
+        return INPUT_EVENT_INJECTION_PENDING;
+    } else {
+        // Force poll loop to wake up when timeout is due.
+        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
+            *nextWakeupTime = mInputTargetWaitTimeoutTime;
+        }
+        return INPUT_EVENT_INJECTION_PENDING;
+    }
+}
+
+void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+        const sp<InputChannel>& inputChannel) {
+    if (newTimeout > 0) {
+        // Extend the timeout.
+        mInputTargetWaitTimeoutTime = now() + newTimeout;
+    } else {
+        // Give up.
+        mInputTargetWaitTimeoutExpired = true;
+
+        // Release the touch target.
+        releaseTouchedWindowLocked();
+
+        // Input state will not be realistic.  Mark it out of sync.
+        if (inputChannel.get()) {
+            ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+            if (connectionIndex >= 0) {
+                sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+                connection->inputState.setOutOfSync();
+            }
+        }
+    }
+}
+
+nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationLocked(
+        nsecs_t currentTime) {
+    if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+        return currentTime - mInputTargetWaitStartTime;
+    }
+    return 0;
+}
+
+void InputDispatcher::resetANRTimeoutsLocked() {
+#if DEBUG_FOCUS
+        LOGD("Resetting ANR timeouts.");
+#endif
+
+    // Reset input target wait timeout.
+    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+}
+
+int32_t InputDispatcher::findFocusedWindowLocked(nsecs_t currentTime, const EventEntry* entry,
+        nsecs_t* nextWakeupTime, InputWindow** outWindow) {
+    *outWindow = NULL;
+    mCurrentInputTargets.clear();
+
+    int32_t injectionResult;
+
+    // If there is no currently focused window and no focused application
+    // then drop the event.
+    if (! mFocusedWindow) {
+        if (mFocusedApplication) {
+#if DEBUG_FOCUS
+            LOGD("Waiting because there is no focused window but there is a "
+                    "focused application that may eventually add a window: %s.",
+                    getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    mFocusedApplication, NULL, nextWakeupTime);
+            goto Unresponsive;
+        }
+
+        LOGI("Dropping event because there is no focused window or focused application.");
+        injectionResult = INPUT_EVENT_INJECTION_FAILED;
+        goto Failed;
+    }
+
+    // Check permissions.
+    if (! checkInjectionPermission(mFocusedWindow, entry->injectorPid, entry->injectorUid)) {
+        injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+        goto Failed;
+    }
+
+    // If the currently focused window is paused then keep waiting.
+    if (mFocusedWindow->paused) {
+#if DEBUG_FOCUS
+        LOGD("Waiting because focused window is paused.");
+#endif
+        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                mFocusedApplication, mFocusedWindow, nextWakeupTime);
+        goto Unresponsive;
+    }
+
+    // If the currently focused window is still working on previous events then keep waiting.
+    if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindow)) {
+#if DEBUG_FOCUS
+        LOGD("Waiting because focused window still processing previous input.");
+#endif
+        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                mFocusedApplication, mFocusedWindow, nextWakeupTime);
+        goto Unresponsive;
+    }
+
+    // Success!  Output targets.
+    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+    *outWindow = mFocusedWindow;
+    addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND);
+
+    // Done.
+Failed:
+Unresponsive:
+    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+    updateDispatchStatisticsLocked(currentTime, entry,
+            injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+    LOGD("findFocusedWindow finished: injectionResult=%d, "
+            "timeSpendWaitingForApplication=%0.1fms",
+            injectionResult, timeSpentWaitingForApplication / 1000000.0);
+#endif
+    return injectionResult;
+}
+
+int32_t InputDispatcher::findTouchedWindowLocked(nsecs_t currentTime, const MotionEntry* entry,
+        nsecs_t* nextWakeupTime, InputWindow** outWindow) {
+    enum InjectionPermission {
+        INJECTION_PERMISSION_UNKNOWN,
+        INJECTION_PERMISSION_GRANTED,
+        INJECTION_PERMISSION_DENIED
+    };
+
+    *outWindow = NULL;
+    mCurrentInputTargets.clear();
+
+    nsecs_t startTime = now();
+
+    // For security reasons, we defer updating the touch state until we are sure that
+    // event injection will be allowed.
+    //
+    // FIXME In the original code, screenWasOff could never be set to true.
+    //       The reason is that the POLICY_FLAG_WOKE_HERE
+    //       and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw
+    //       EV_KEY, EV_REL and EV_ABS events.  As it happens, the touch event was
+    //       actually enqueued using the policyFlags that appeared in the final EV_SYN
+    //       events upon which no preprocessing took place.  So policyFlags was always 0.
+    //       In the new native input dispatcher we're a bit more careful about event
+    //       preprocessing so the touches we receive can actually have non-zero policyFlags.
+    //       Unfortunately we obtain undesirable behavior.
+    //
+    //       Here's what happens:
+    //
+    //       When the device dims in anticipation of going to sleep, touches
+    //       in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause
+    //       the device to brighten and reset the user activity timer.
+    //       Touches on other windows (such as the launcher window)
+    //       are dropped.  Then after a moment, the device goes to sleep.  Oops.
+    //
+    //       Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE
+    //       instead of POLICY_FLAG_WOKE_HERE...
+    //
+    bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE;
+
+    int32_t action = entry->action;
+
+    // Update the touch state as needed based on the properties of the touch event.
+    int32_t injectionResult;
+    InjectionPermission injectionPermission;
+    if (action == AMOTION_EVENT_ACTION_DOWN) {
+        /* Case 1: ACTION_DOWN */
+
+        InputWindow* newTouchedWindow = NULL;
+        mTempTouchedOutsideTargets.clear();
+
+        int32_t x = int32_t(entry->firstSample.pointerCoords[0].x);
+        int32_t y = int32_t(entry->firstSample.pointerCoords[0].y);
+        InputWindow* topErrorWindow = NULL;
+        bool obscured = false;
+
+        // Traverse windows from front to back to find touched window and outside targets.
+        size_t numWindows = mWindows.size();
+        for (size_t i = 0; i < numWindows; i++) {
+            InputWindow* window = & mWindows.editItemAt(i);
+            int32_t flags = window->layoutParamsFlags;
+
+            if (flags & InputWindow::FLAG_SYSTEM_ERROR) {
+                if (! topErrorWindow) {
+                    topErrorWindow = window;
+                }
+            }
+
+            if (window->visible) {
+                if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) {
+                    bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE
+                            | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0;
+                    if (isTouchModal || window->touchableAreaContainsPoint(x, y)) {
+                        if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) {
+                            newTouchedWindow = window;
+                            obscured = isWindowObscuredLocked(window);
+                        }
+                        break; // found touched window, exit window loop
+                    }
+                }
+
+                if (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH) {
+                    OutsideTarget outsideTarget;
+                    outsideTarget.window = window;
+                    outsideTarget.obscured = isWindowObscuredLocked(window);
+                    mTempTouchedOutsideTargets.push(outsideTarget);
+                }
+            }
+        }
+
+        // If there is an error window but it is not taking focus (typically because
+        // it is invisible) then wait for it.  Any other focused window may in
+        // fact be in ANR state.
+        if (topErrorWindow && newTouchedWindow != topErrorWindow) {
+#if DEBUG_FOCUS
+            LOGD("Waiting because system error window is pending.");
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    NULL, NULL, nextWakeupTime);
+            injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+            goto Unresponsive;
+        }
+
+        // If we did not find a touched window then fail.
+        if (! newTouchedWindow) {
+            if (mFocusedApplication) {
+#if DEBUG_FOCUS
+                LOGD("Waiting because there is no touched window but there is a "
+                        "focused application that may eventually add a new window: %s.",
+                        getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
+#endif
+                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                        mFocusedApplication, NULL, nextWakeupTime);
+                injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+                goto Unresponsive;
+            }
+
+            LOGI("Dropping event because there is no touched window or focused application.");
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+            goto Failed;
+        }
+
+        // Check permissions.
+        if (! checkInjectionPermission(newTouchedWindow, entry->injectorPid, entry->injectorUid)) {
+            injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+            injectionPermission = INJECTION_PERMISSION_DENIED;
+            goto Failed;
+        }
+
+        // If the touched window is paused then keep waiting.
+        if (newTouchedWindow->paused) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+            LOGD("Waiting because touched window is paused.");
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    NULL, newTouchedWindow, nextWakeupTime);
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+            goto Unresponsive;
+        }
+
+        // If the touched window is still working on previous events then keep waiting.
+        if (! isWindowFinishedWithPreviousInputLocked(newTouchedWindow)) {
+#if DEBUG_FOCUS
+            LOGD("Waiting because touched window still processing previous input.");
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    NULL, newTouchedWindow, nextWakeupTime);
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+            goto Unresponsive;
+        }
+
+        // Success!  Update the touch dispatch state for real.
+        releaseTouchedWindowLocked();
+
+        mTouchedWindow = newTouchedWindow;
+        mTouchedWindowIsObscured = obscured;
+
+        if (newTouchedWindow->hasWallpaper) {
+            mTouchedWallpaperWindows.appendVector(mWallpaperWindows);
+        }
+    } else {
+        /* Case 2: Everything but ACTION_DOWN */
+
+        // Check permissions.
+        if (! checkInjectionPermission(mTouchedWindow, entry->injectorPid, entry->injectorUid)) {
+            injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+            injectionPermission = INJECTION_PERMISSION_DENIED;
+            goto Failed;
+        }
+
+        // If the pointer is not currently down, then ignore the event.
+        if (! mTouchDown) {
+            LOGI("Dropping event because the pointer is not down.");
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+            goto Failed;
+        }
+
+        // If there is no currently touched window then fail.
+        if (! mTouchedWindow) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+            LOGD("Dropping event because there is no touched window to receive it.");
+#endif
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+            goto Failed;
+        }
+
+        // If the touched window is paused then keep waiting.
+        if (mTouchedWindow->paused) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+            LOGD("Waiting because touched window is paused.");
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    NULL, mTouchedWindow, nextWakeupTime);
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+            goto Unresponsive;
+        }
+
+        // If the touched window is still working on previous events then keep waiting.
+        if (! isWindowFinishedWithPreviousInputLocked(mTouchedWindow)) {
+#if DEBUG_FOCUS
+            LOGD("Waiting because touched window still processing previous input.");
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    NULL, mTouchedWindow, nextWakeupTime);
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+            goto Unresponsive;
+        }
+    }
+
+    // Success!  Output targets.
+    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+    injectionPermission = INJECTION_PERMISSION_GRANTED;
+
+    {
+        size_t numWallpaperWindows = mTouchedWallpaperWindows.size();
+        for (size_t i = 0; i < numWallpaperWindows; i++) {
+            addWindowTargetLocked(mTouchedWallpaperWindows[i],
+                    InputTarget::FLAG_WINDOW_IS_OBSCURED);
+        }
+
+        size_t numOutsideTargets = mTempTouchedOutsideTargets.size();
+        for (size_t i = 0; i < numOutsideTargets; i++) {
+            const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i];
+            int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE;
+            if (outsideTarget.obscured) {
+                outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+            }
+            addWindowTargetLocked(outsideTarget.window, outsideTargetFlags);
+        }
+        mTempTouchedOutsideTargets.clear();
+
+        int32_t targetFlags = InputTarget::FLAG_FOREGROUND;
+        if (mTouchedWindowIsObscured) {
+            targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+        }
+        addWindowTargetLocked(mTouchedWindow, targetFlags);
+        *outWindow = mTouchedWindow;
+    }
+
+Failed:
+    // Check injection permission once and for all.
+    if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
+        if (checkInjectionPermission(action == AMOTION_EVENT_ACTION_DOWN ? NULL : mTouchedWindow,
+                entry->injectorPid, entry->injectorUid)) {
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+        } else {
+            injectionPermission = INJECTION_PERMISSION_DENIED;
+        }
+    }
+
+    // Update final pieces of touch state if the injector had permission.
+    if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
+        if (action == AMOTION_EVENT_ACTION_DOWN) {
+            if (mTouchDown) {
+                // This is weird.  We got a down but we thought it was already down!
+                LOGW("Pointer down received while already down.");
+            } else {
+                mTouchDown = true;
+            }
+
+            if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+                // Since we failed to identify a target for this touch down, we may still
+                // be holding on to an earlier target from a previous touch down.  Release it.
+                releaseTouchedWindowLocked();
+            }
+        } else if (action == AMOTION_EVENT_ACTION_UP) {
+            mTouchDown = false;
+            releaseTouchedWindowLocked();
+        }
+    } else {
+        LOGW("Not updating touch focus because injection was denied.");
+    }
+
+Unresponsive:
+    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+    updateDispatchStatisticsLocked(currentTime, entry,
+            injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+    LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d,"
+            "timeSpendWaitingForApplication=%0.1fms",
+            injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0);
+#endif
+    return injectionResult;
+}
+
+void InputDispatcher::releaseTouchedWindowLocked() {
+    mTouchedWindow = NULL;
+    mTouchedWindowIsObscured = false;
+    mTouchedWallpaperWindows.clear();
+}
+
+void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags) {
+    mCurrentInputTargets.push();
+
+    InputTarget& target = mCurrentInputTargets.editTop();
+    target.inputChannel = window->inputChannel;
+    target.flags = targetFlags;
+    target.xOffset = - window->frameLeft;
+    target.yOffset = - window->frameTop;
+}
+
+void InputDispatcher::addMonitoringTargetsLocked() {
+    for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+        mCurrentInputTargets.push();
+
+        InputTarget& target = mCurrentInputTargets.editTop();
+        target.inputChannel = mMonitoringChannels[i];
+        target.flags = 0;
+        target.xOffset = 0;
+        target.yOffset = 0;
+    }
+}
+
+bool InputDispatcher::checkInjectionPermission(const InputWindow* window,
+        int32_t injectorPid, int32_t injectorUid) {
+    if (injectorUid > 0 && (window == NULL || window->ownerUid != injectorUid)) {
+        bool result = mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid);
+        if (! result) {
+            if (window) {
+                LOGW("Permission denied: injecting event from pid %d uid %d to window "
+                        "with input channel %s owned by uid %d",
+                        injectorPid, injectorUid, window->inputChannel->getName().string(),
+                        window->ownerUid);
+            } else {
+                LOGW("Permission denied: injecting event from pid %d uid %d",
+                        injectorPid, injectorUid);
+            }
+            return false;
+        }
+    }
+    return true;
+}
+
+bool InputDispatcher::isWindowObscuredLocked(const InputWindow* window) {
+    size_t numWindows = mWindows.size();
+    for (size_t i = 0; i < numWindows; i++) {
+        const InputWindow* other = & mWindows.itemAt(i);
+        if (other == window) {
+            break;
+        }
+        if (other->visible && window->visibleFrameIntersects(other)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool InputDispatcher::isWindowFinishedWithPreviousInputLocked(const InputWindow* window) {
+    ssize_t connectionIndex = getConnectionIndexLocked(window->inputChannel);
+    if (connectionIndex >= 0) {
+        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+        return connection->outboundQueue.isEmpty();
+    } else {
+        return true;
+    }
+}
+
+String8 InputDispatcher::getApplicationWindowLabelLocked(const InputApplication* application,
+        const InputWindow* window) {
+    if (application) {
+        if (window) {
+            String8 label(application->name);
+            label.append(" - ");
+            label.append(window->name);
+            return label;
+        } else {
+            return application->name;
+        }
+    } else if (window) {
+        return window->name;
+    } else {
+        return String8("<unknown application or window>");
+    }
+}
+
+void InputDispatcher::pokeUserActivityLocked(nsecs_t eventTime,
+        int32_t windowType, int32_t eventType) {
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doPokeUserActivityLockedInterruptible);
+    commandEntry->eventTime = eventTime;
+    commandEntry->windowType = windowType;
+    commandEntry->userActivityEventType = eventType;
+}
+
 void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
         const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
         bool resumeWithAppendedMotionSample) {
 #if DEBUG_DISPATCH_CYCLE
-    LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, timeout=%lldns, "
+    LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, "
             "xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s",
-            connection->getInputChannelName(), inputTarget->flags, inputTarget->timeout,
+            connection->getInputChannelName(), inputTarget->flags,
             inputTarget->xOffset, inputTarget->yOffset,
-            resumeWithAppendedMotionSample ? "true" : "false");
+            toString(resumeWithAppendedMotionSample));
 #endif
 
     // Skip this event if the connection status is not normal.
-    // We don't want to queue outbound events at all if the connection is broken or
-    // not responding.
+    // We don't want to enqueue additional outbound events if the connection is broken.
     if (connection->status != Connection::STATUS_NORMAL) {
-        LOGV("channel '%s' ~ Dropping event because the channel status is %s",
-                connection->getStatusLabel());
+        LOGW("channel '%s' ~ Dropping event because the channel status is %s",
+                connection->getInputChannelName(), connection->getStatusLabel());
         return;
     }
 
@@ -612,19 +1387,46 @@
         }
     }
 
+    // Bring the input state back in line with reality in case it drifted off during an ANR.
+    if (connection->inputState.isOutOfSync()) {
+        mTempCancelationEvents.clear();
+        connection->inputState.synthesizeCancelationEvents(& mAllocator, mTempCancelationEvents);
+        connection->inputState.resetOutOfSync();
+
+        if (! mTempCancelationEvents.isEmpty()) {
+            LOGI("channel '%s' ~ Generated %d cancelation events to bring channel back in sync "
+                    "with reality.",
+                    connection->getInputChannelName(), mTempCancelationEvents.size());
+
+            for (size_t i = 0; i < mTempCancelationEvents.size(); i++) {
+                EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i);
+                switch (cancelationEventEntry->type) {
+                case EventEntry::TYPE_KEY:
+                    logOutboundKeyDetailsLocked("  ",
+                            static_cast<KeyEntry*>(cancelationEventEntry));
+                    break;
+                case EventEntry::TYPE_MOTION:
+                    logOutboundMotionDetailsLocked("  ",
+                            static_cast<MotionEntry*>(cancelationEventEntry));
+                    break;
+                }
+
+                DispatchEntry* cancelationDispatchEntry =
+                        mAllocator.obtainDispatchEntry(cancelationEventEntry,
+                        0, inputTarget->xOffset, inputTarget->yOffset); // increments ref
+                connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
+
+                mAllocator.releaseEventEntry(cancelationEventEntry);
+            }
+        }
+    }
+
     // This is a new event.
     // Enqueue a new dispatch entry onto the outbound queue for this connection.
-    DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry); // increments ref
-    dispatchEntry->targetFlags = inputTarget->flags;
-    dispatchEntry->xOffset = inputTarget->xOffset;
-    dispatchEntry->yOffset = inputTarget->yOffset;
-    dispatchEntry->timeout = inputTarget->timeout;
-    dispatchEntry->inProgress = false;
-    dispatchEntry->headMotionSample = NULL;
-    dispatchEntry->tailMotionSample = NULL;
-
-    if (dispatchEntry->isSyncTarget()) {
-        eventEntry->pendingSyncDispatches += 1;
+    DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref
+            inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset);
+    if (dispatchEntry->hasForegroundTarget()) {
+        eventEntry->pendingForegroundDispatches += 1;
     }
 
     // Handle the case where we could not stream a new motion sample because the consumer has
@@ -661,12 +1463,37 @@
     assert(connection->status == Connection::STATUS_NORMAL);
     assert(! connection->outboundQueue.isEmpty());
 
-    DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
+    DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
     assert(! dispatchEntry->inProgress);
 
-    // TODO throttle successive ACTION_MOVE motion events for the same device
-    //      possible implementation could set a brief poll timeout here and resume starting the
-    //      dispatch cycle when elapsed
+    // Mark the dispatch entry as in progress.
+    dispatchEntry->inProgress = true;
+
+    // Update the connection's input state.
+    InputState::Consistency consistency = connection->inputState.trackEvent(
+            dispatchEntry->eventEntry);
+
+#if FILTER_INPUT_EVENTS
+    // Filter out inconsistent sequences of input events.
+    // The input system may drop or inject events in a way that could violate implicit
+    // invariants on input state and potentially cause an application to crash
+    // or think that a key or pointer is stuck down.  Technically we make no guarantees
+    // of consistency but it would be nice to improve on this where possible.
+    // XXX: This code is a proof of concept only.  Not ready for prime time.
+    if (consistency == InputState::TOLERABLE) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Sending an event that is inconsistent with the connection's "
+                "current input state but that is likely to be tolerated by the application.",
+                connection->getInputChannelName());
+#endif
+    } else if (consistency == InputState::BROKEN) {
+        LOGI("channel '%s' ~ Dropping an event that is inconsistent with the connection's "
+                "current input state and that is likely to cause the application to crash.",
+                connection->getInputChannelName());
+        startNextDispatchCycleLocked(currentTime, connection);
+        return;
+    }
+#endif
 
     // Publish the event.
     status_t status;
@@ -790,14 +1617,9 @@
     }
 
     // Record information about the newly started dispatch cycle.
-    dispatchEntry->inProgress = true;
-
     connection->lastEventTime = dispatchEntry->eventEntry->eventTime;
     connection->lastDispatchTime = currentTime;
 
-    nsecs_t timeout = dispatchEntry->timeout;
-    connection->setNextTimeoutTime(currentTime, timeout);
-
     // Notify other system components.
     onDispatchCycleStartedLocked(currentTime, connection);
 }
@@ -817,21 +1639,8 @@
         return;
     }
 
-    // Clear the pending timeout.
-    connection->nextTimeoutTime = LONG_LONG_MAX;
-
-    if (connection->status == Connection::STATUS_NOT_RESPONDING) {
-        // Recovering from an ANR.
-        connection->status = Connection::STATUS_NORMAL;
-
-        // Notify other system components.
-        onDispatchCycleFinishedLocked(currentTime, connection, true /*recoveredFromANR*/);
-    } else {
-        // Normal finish.  Not much to do here.
-
-        // Notify other system components.
-        onDispatchCycleFinishedLocked(currentTime, connection, false /*recoveredFromANR*/);
-    }
+    // Notify other system components.
+    onDispatchCycleFinishedLocked(currentTime, connection);
 
     // Reset the publisher since the event has been consumed.
     // We do this now so that the publisher can release some of its internal resources
@@ -844,9 +1653,14 @@
         return;
     }
 
+    startNextDispatchCycleLocked(currentTime, connection);
+}
+
+void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection) {
     // Start the next dispatch cycle for this connection.
     while (! connection->outboundQueue.isEmpty()) {
-        DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
+        DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
         if (dispatchEntry->inProgress) {
              // Finish or resume current event in progress.
             if (dispatchEntry->tailMotionSample) {
@@ -860,13 +1674,13 @@
             }
             // Finished.
             connection->outboundQueue.dequeueAtHead();
-            if (dispatchEntry->isSyncTarget()) {
-                decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
+            if (dispatchEntry->hasForegroundTarget()) {
+                decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
             }
             mAllocator.releaseDispatchEntry(dispatchEntry);
         } else {
             // If the head is not in progress, then we must have already dequeued the in
-            // progress event, which means we actually aborted it (due to ANR).
+            // progress event, which means we actually aborted it.
             // So just start the next event for this connection.
             startDispatchCycleLocked(currentTime, connection);
             return;
@@ -877,71 +1691,23 @@
     deactivateConnectionLocked(connection.get());
 }
 
-void InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime,
-        const sp<Connection>& connection) {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("channel '%s' ~ timeoutDispatchCycle",
-            connection->getInputChannelName());
-#endif
-
-    if (connection->status != Connection::STATUS_NORMAL) {
-        return;
-    }
-
-    // Enter the not responding state.
-    connection->status = Connection::STATUS_NOT_RESPONDING;
-    connection->lastANRTime = currentTime;
-
-    // Notify other system components.
-    // This enqueues a command which will eventually either call
-    // resumeAfterTimeoutDispatchCycleLocked or abortDispatchCycleLocked.
-    onDispatchCycleANRLocked(currentTime, connection);
-}
-
-void InputDispatcher::resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
-        const sp<Connection>& connection, nsecs_t newTimeout) {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked",
-            connection->getInputChannelName());
-#endif
-
-    if (connection->status != Connection::STATUS_NOT_RESPONDING) {
-        return;
-    }
-
-    // Resume normal dispatch.
-    connection->status = Connection::STATUS_NORMAL;
-    connection->setNextTimeoutTime(currentTime, newTimeout);
-}
-
 void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime,
         const sp<Connection>& connection, bool broken) {
 #if DEBUG_DISPATCH_CYCLE
     LOGD("channel '%s' ~ abortDispatchCycle - broken=%s",
-            connection->getInputChannelName(), broken ? "true" : "false");
+            connection->getInputChannelName(), toString(broken));
 #endif
 
-    // Clear the pending timeout.
-    connection->nextTimeoutTime = LONG_LONG_MAX;
+    // Input state will no longer be realistic.
+    connection->inputState.setOutOfSync();
 
     // Clear the outbound queue.
-    if (! connection->outboundQueue.isEmpty()) {
-        do {
-            DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
-            if (dispatchEntry->isSyncTarget()) {
-                decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
-            }
-            mAllocator.releaseDispatchEntry(dispatchEntry);
-        } while (! connection->outboundQueue.isEmpty());
-
-        deactivateConnectionLocked(connection.get());
-    }
+    drainOutboundQueueLocked(connection.get());
 
     // Handle the case where the connection appears to be unrecoverably broken.
     // Ignore already broken or zombie connections.
     if (broken) {
-        if (connection->status == Connection::STATUS_NORMAL
-                || connection->status == Connection::STATUS_NOT_RESPONDING) {
+        if (connection->status == Connection::STATUS_NORMAL) {
             connection->status = Connection::STATUS_BROKEN;
 
             // Notify other system components.
@@ -950,7 +1716,19 @@
     }
 }
 
-bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
+void InputDispatcher::drainOutboundQueueLocked(Connection* connection) {
+    while (! connection->outboundQueue.isEmpty()) {
+        DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
+        if (dispatchEntry->hasForegroundTarget()) {
+            decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
+        }
+        mAllocator.releaseDispatchEntry(dispatchEntry);
+    }
+
+    deactivateConnectionLocked(connection);
+}
+
+int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
     InputDispatcher* d = static_cast<InputDispatcher*>(data);
 
     { // acquire lock
@@ -960,24 +1738,24 @@
         if (connectionIndex < 0) {
             LOGE("Received spurious receive callback for unknown input channel.  "
                     "fd=%d, events=0x%x", receiveFd, events);
-            return false; // remove the callback
+            return 0; // remove the callback
         }
 
         nsecs_t currentTime = now();
 
         sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);
-        if (events & (POLLERR | POLLHUP | POLLNVAL)) {
+        if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
             LOGE("channel '%s' ~ Consumer closed input channel or an error occurred.  "
                     "events=0x%x", connection->getInputChannelName(), events);
             d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
             d->runCommandsLockedInterruptible();
-            return false; // remove the callback
+            return 0; // remove the callback
         }
 
-        if (! (events & POLLIN)) {
+        if (! (events & ALOOPER_EVENT_INPUT)) {
             LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
                     "events=0x%x", connection->getInputChannelName(), events);
-            return true;
+            return 1;
         }
 
         status_t status = connection->inputPublisher.receiveFinishedSignal();
@@ -986,12 +1764,12 @@
                     connection->getInputChannelName(), status);
             d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
             d->runCommandsLockedInterruptible();
-            return false; // remove the callback
+            return 0; // remove the callback
         }
 
         d->finishDispatchCycleLocked(currentTime, connection);
         d->runCommandsLockedInterruptible();
-        return true;
+        return 1;
     } // release lock
 }
 
@@ -1000,57 +1778,19 @@
     LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime);
 #endif
 
-    bool wasEmpty;
+    bool needWake;
     { // acquire lock
         AutoMutex _l(mLock);
 
         ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime);
-
-        wasEmpty = mInboundQueue.isEmpty();
-        mInboundQueue.enqueueAtTail(newEntry);
+        needWake = enqueueInboundEventLocked(newEntry);
     } // release lock
 
-    if (wasEmpty) {
-        mPollLoop->wake();
+    if (needWake) {
+        mLooper->wake();
     }
 }
 
-void InputDispatcher::notifyAppSwitchComing(nsecs_t eventTime) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    LOGD("notifyAppSwitchComing - eventTime=%lld", eventTime);
-#endif
-
-    // Remove movement keys from the queue from most recent to least recent, stopping at the
-    // first non-movement key.
-    // TODO: Include a detailed description of why we do this...
-
-    { // acquire lock
-        AutoMutex _l(mLock);
-
-        for (EventEntry* entry = mInboundQueue.tail.prev; entry != & mInboundQueue.head; ) {
-            EventEntry* prev = entry->prev;
-
-            if (entry->type == EventEntry::TYPE_KEY) {
-                KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
-                if (isMovementKey(keyEntry->keyCode)) {
-                    LOGV("Dropping movement key during app switch: keyCode=%d, action=%d",
-                            keyEntry->keyCode, keyEntry->action);
-                    mInboundQueue.dequeue(keyEntry);
-
-                    setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
-
-                    mAllocator.releaseKeyEntry(keyEntry);
-                } else {
-                    // stop at last non-movement key
-                    break;
-                }
-            }
-
-            entry = prev;
-        }
-    } // release lock
-}
-
 void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
         uint32_t policyFlags, int32_t action, int32_t flags,
         int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {
@@ -1061,7 +1801,7 @@
             keyCode, scanCode, metaState, downTime);
 #endif
 
-    bool wasEmpty;
+    bool needWake;
     { // acquire lock
         AutoMutex _l(mLock);
 
@@ -1070,12 +1810,11 @@
                 deviceId, source, policyFlags, action, flags, keyCode, scanCode,
                 metaState, repeatCount, downTime);
 
-        wasEmpty = mInboundQueue.isEmpty();
-        mInboundQueue.enqueueAtTail(newEntry);
+        needWake = enqueueInboundEventLocked(newEntry);
     } // release lock
 
-    if (wasEmpty) {
-        mPollLoop->wake();
+    if (needWake) {
+        mLooper->wake();
     }
 }
 
@@ -1101,7 +1840,7 @@
     }
 #endif
 
-    bool wasEmpty;
+    bool needWake;
     { // acquire lock
         AutoMutex _l(mLock);
 
@@ -1112,8 +1851,8 @@
             // Try to append a move sample to the tail of the inbound queue for this device.
             // Give up if we encounter a non-move motion event for this device since that
             // means we cannot append any new samples until a new motion event has started.
-            for (EventEntry* entry = mInboundQueue.tail.prev;
-                    entry != & mInboundQueue.head; entry = entry->prev) {
+            for (EventEntry* entry = mInboundQueue.tailSentinel.prev;
+                    entry != & mInboundQueue.headSentinel; entry = entry->prev) {
                 if (entry->type != EventEntry::TYPE_MOTION) {
                     // Keep looking for motion events.
                     continue;
@@ -1140,74 +1879,69 @@
                 LOGD("Appended motion sample onto batch for most recent "
                         "motion event for this device in the inbound queue.");
 #endif
-
-                // Sanity check for special case because dispatch is interruptible.
-                // The dispatch logic is partially interruptible and releases its lock while
-                // identifying targets.  However, as soon as the targets have been identified,
-                // the dispatcher proceeds to write a dispatch entry into all relevant outbound
-                // queues and then promptly removes the motion entry from the queue.
-                //
-                // Consequently, we should never observe the case where the inbound queue contains
-                // an in-progress motion entry unless the current input targets are invalid
-                // (currently being computed).  Check for this!
-                assert(! (motionEntry->dispatchInProgress && mCurrentInputTargetsValid));
-
                 return; // done!
             }
 
             // STREAMING CASE
             //
             // There is no pending motion event (of any kind) for this device in the inbound queue.
-            // Search the outbound queues for a synchronously dispatched motion event for this
-            // device.  If found, then we append the new sample to that event and then try to
-            // push it out to all current targets.  It is possible that some targets will already
-            // have consumed the motion event.  This case is automatically handled by the
-            // logic in prepareDispatchCycleLocked by tracking where resumption takes place.
-            //
-            // The reason we look for a synchronously dispatched motion event is because we
-            // want to be sure that no other motion events have been dispatched since the move.
-            // It's also convenient because it means that the input targets are still valid.
-            // This code could be improved to support streaming of asynchronously dispatched
-            // motion events (which might be significantly more efficient) but it may become
-            // a little more complicated as a result.
-            //
-            // Note: This code crucially depends on the invariant that an outbound queue always
-            //       contains at most one synchronous event and it is always last (but it might
-            //       not be first!).
+            // Search the outbound queue for the current foreground targets to find a dispatched
+            // motion event that is still in progress.  If found, then, appen the new sample to
+            // that event and push it out to all current targets.  The logic in
+            // prepareDispatchCycleLocked takes care of the case where some targets may
+            // already have consumed the motion event by starting a new dispatch cycle if needed.
             if (mCurrentInputTargetsValid) {
-                for (size_t i = 0; i < mActiveConnections.size(); i++) {
-                    Connection* connection = mActiveConnections.itemAt(i);
-                    if (! connection->outboundQueue.isEmpty()) {
-                        DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev;
-                        if (dispatchEntry->isSyncTarget()) {
-                            if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) {
-                                goto NoBatchingOrStreaming;
-                            }
-
-                            MotionEntry* syncedMotionEntry = static_cast<MotionEntry*>(
-                                    dispatchEntry->eventEntry);
-                            if (syncedMotionEntry->action != AMOTION_EVENT_ACTION_MOVE
-                                    || syncedMotionEntry->deviceId != deviceId
-                                    || syncedMotionEntry->pointerCount != pointerCount
-                                    || syncedMotionEntry->isInjected()) {
-                                goto NoBatchingOrStreaming;
-                            }
-
-                            // Found synced move entry.  Append sample and resume dispatch.
-                            mAllocator.appendMotionSample(syncedMotionEntry, eventTime,
-                                    pointerCoords);
-    #if DEBUG_BATCHING
-                            LOGD("Appended motion sample onto batch for most recent synchronously "
-                                    "dispatched motion event for this device in the outbound queues.");
-    #endif
-                            nsecs_t currentTime = now();
-                            dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry,
-                                    true /*resumeWithAppendedMotionSample*/);
-
-                            runCommandsLockedInterruptible();
-                            return; // done!
-                        }
+                for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
+                    const InputTarget& inputTarget = mCurrentInputTargets[i];
+                    if ((inputTarget.flags & InputTarget::FLAG_FOREGROUND) == 0) {
+                        // Skip non-foreground targets.  We only want to stream if there is at
+                        // least one foreground target whose dispatch is still in progress.
+                        continue;
                     }
+
+                    ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
+                    if (connectionIndex < 0) {
+                        // Connection must no longer be valid.
+                        continue;
+                    }
+
+                    sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+                    if (connection->outboundQueue.isEmpty()) {
+                        // This foreground target has an empty outbound queue.
+                        continue;
+                    }
+
+                    DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+                    if (! dispatchEntry->inProgress
+                            || dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) {
+                        // No motion event is being dispatched.
+                        continue;
+                    }
+
+                    MotionEntry* motionEntry = static_cast<MotionEntry*>(
+                            dispatchEntry->eventEntry);
+                    if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE
+                            || motionEntry->deviceId != deviceId
+                            || motionEntry->pointerCount != pointerCount
+                            || motionEntry->isInjected()) {
+                        // The motion event is not compatible with this move.
+                        continue;
+                    }
+
+                    // Hurray!  This foreground target is currently dispatching a move event
+                    // that we can stream onto.  Append the motion sample and resume dispatch.
+                    mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
+#if DEBUG_BATCHING
+                    LOGD("Appended motion sample onto batch for most recently dispatched "
+                            "motion event for this device in the outbound queues.  "
+                            "Attempting to stream the motion sample.");
+#endif
+                    nsecs_t currentTime = now();
+                    dispatchEventToCurrentInputTargetsLocked(currentTime, motionEntry,
+                            true /*resumeWithAppendedMotionSample*/);
+
+                    runCommandsLockedInterruptible();
+                    return; // done!
                 }
             }
 
@@ -1220,12 +1954,11 @@
                 xPrecision, yPrecision, downTime,
                 pointerCount, pointerIds, pointerCoords);
 
-        wasEmpty = mInboundQueue.isEmpty();
-        mInboundQueue.enqueueAtTail(newEntry);
+        needWake = enqueueInboundEventLocked(newEntry);
     } // release lock
 
-    if (wasEmpty) {
-        mPollLoop->wake();
+    if (needWake) {
+        mLooper->wake();
     }
 }
 
@@ -1240,11 +1973,15 @@
     nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
 
     EventEntry* injectedEntry;
-    bool wasEmpty;
+    bool needWake;
     { // acquire lock
         AutoMutex _l(mLock);
 
-        injectedEntry = createEntryFromInputEventLocked(event);
+        injectedEntry = createEntryFromInjectedInputEventLocked(event);
+        if (! injectedEntry) {
+            return INPUT_EVENT_INJECTION_FAILED;
+        }
+
         injectedEntry->refCount += 1;
         injectedEntry->injectorPid = injectorPid;
         injectedEntry->injectorUid = injectorUid;
@@ -1253,13 +1990,11 @@
             injectedEntry->injectionIsAsync = true;
         }
 
-        wasEmpty = mInboundQueue.isEmpty();
-        mInboundQueue.enqueueAtTail(injectedEntry);
-
+        needWake = enqueueInboundEventLocked(injectedEntry);
     } // release lock
 
-    if (wasEmpty) {
-        mPollLoop->wake();
+    if (needWake) {
+        mLooper->wake();
     }
 
     int32_t injectionResult;
@@ -1290,15 +2025,15 @@
 
             if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED
                     && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) {
-                while (injectedEntry->pendingSyncDispatches != 0) {
+                while (injectedEntry->pendingForegroundDispatches != 0) {
 #if DEBUG_INJECTION
-                    LOGD("injectInputEvent - Waiting for %d pending synchronous dispatches.",
-                            injectedEntry->pendingSyncDispatches);
+                    LOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
+                            injectedEntry->pendingForegroundDispatches);
 #endif
                     nsecs_t remainingTimeout = endTime - now();
                     if (remainingTimeout <= 0) {
 #if DEBUG_INJECTION
-                    LOGD("injectInputEvent - Timed out waiting for pending synchronous "
+                    LOGD("injectInputEvent - Timed out waiting for pending foreground "
                             "dispatches to finish.");
 #endif
                         injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
@@ -1353,19 +2088,50 @@
     }
 }
 
-void InputDispatcher::decrementPendingSyncDispatchesLocked(EventEntry* entry) {
-    entry->pendingSyncDispatches -= 1;
+void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+    entry->pendingForegroundDispatches -= 1;
 
-    if (entry->isInjected() && entry->pendingSyncDispatches == 0) {
+    if (entry->isInjected() && entry->pendingForegroundDispatches == 0) {
         mInjectionSyncFinishedCondition.broadcast();
     }
 }
 
-InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked(
+static bool isValidKeyAction(int32_t action) {
+    switch (action) {
+    case AKEY_EVENT_ACTION_DOWN:
+    case AKEY_EVENT_ACTION_UP:
+        return true;
+    default:
+        return false;
+    }
+}
+
+static bool isValidMotionAction(int32_t action) {
+    switch (action & AMOTION_EVENT_ACTION_MASK) {
+    case AMOTION_EVENT_ACTION_DOWN:
+    case AMOTION_EVENT_ACTION_UP:
+    case AMOTION_EVENT_ACTION_CANCEL:
+    case AMOTION_EVENT_ACTION_MOVE:
+    case AMOTION_EVENT_ACTION_POINTER_DOWN:
+    case AMOTION_EVENT_ACTION_POINTER_UP:
+    case AMOTION_EVENT_ACTION_OUTSIDE:
+        return true;
+    default:
+        return false;
+    }
+}
+
+InputDispatcher::EventEntry* InputDispatcher::createEntryFromInjectedInputEventLocked(
         const InputEvent* event) {
     switch (event->getType()) {
     case AINPUT_EVENT_TYPE_KEY: {
         const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
+        if (! isValidKeyAction(keyEvent->getAction())) {
+            LOGE("Dropping injected key event since it has invalid action code 0x%x",
+                    keyEvent->getAction());
+            return NULL;
+        }
+
         uint32_t policyFlags = POLICY_FLAG_INJECTED;
 
         KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
@@ -1378,6 +2144,17 @@
 
     case AINPUT_EVENT_TYPE_MOTION: {
         const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
+        if (! isValidMotionAction(motionEvent->getAction())) {
+            LOGE("Dropping injected motion event since it has invalid action code 0x%x.",
+                    motionEvent->getAction());
+            return NULL;
+        }
+        if (motionEvent->getPointerCount() == 0
+                || motionEvent->getPointerCount() > MAX_POINTERS) {
+            LOGE("Dropping injected motion event since it has an invalid pointer count %d.",
+                    motionEvent->getPointerCount());
+        }
+
         uint32_t policyFlags = POLICY_FLAG_INJECTED;
 
         const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
@@ -1405,50 +2182,226 @@
     }
 }
 
-void InputDispatcher::resetKeyRepeatLocked() {
-    if (mKeyRepeatState.lastKeyEntry) {
-        mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
-        mKeyRepeatState.lastKeyEntry = NULL;
-    }
-}
-
-void InputDispatcher::preemptInputDispatch() {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("preemptInputDispatch");
+void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) {
+#if DEBUG_FOCUS
+    LOGD("setInputWindows");
 #endif
-
-    bool preemptedOne = false;
     { // acquire lock
         AutoMutex _l(mLock);
 
-        for (size_t i = 0; i < mActiveConnections.size(); i++) {
-            Connection* connection = mActiveConnections[i];
-            if (connection->hasPendingSyncTarget()) {
-#if DEBUG_DISPATCH_CYCLE
-                LOGD("channel '%s' ~ Preempted pending synchronous dispatch",
-                        connection->getInputChannelName());
-#endif
-                connection->outboundQueue.tail.prev->targetFlags &= ~ InputTarget::FLAG_SYNC;
-                preemptedOne = true;
+        // Clear old window pointers but remember their associated channels.
+        mFocusedWindow = NULL;
+
+        sp<InputChannel> touchedWindowChannel;
+        if (mTouchedWindow) {
+            touchedWindowChannel = mTouchedWindow->inputChannel;
+            mTouchedWindow = NULL;
+        }
+        size_t numTouchedWallpapers = mTouchedWallpaperWindows.size();
+        if (numTouchedWallpapers != 0) {
+            for (size_t i = 0; i < numTouchedWallpapers; i++) {
+                mTempTouchedWallpaperChannels.push(mTouchedWallpaperWindows[i]->inputChannel);
+            }
+            mTouchedWallpaperWindows.clear();
+        }
+        mWallpaperWindows.clear();
+        mWindows.clear();
+
+        // Loop over new windows and rebuild the necessary window pointers for
+        // tracking focus and touch.
+        mWindows.appendVector(inputWindows);
+
+        size_t numWindows = mWindows.size();
+        for (size_t i = 0; i < numWindows; i++) {
+            InputWindow* window = & mWindows.editItemAt(i);
+            if (window->hasFocus) {
+                mFocusedWindow = window;
+            }
+
+            if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) {
+                mWallpaperWindows.push(window);
+
+                for (size_t j = 0; j < numTouchedWallpapers; j++) {
+                    if (window->inputChannel == mTempTouchedWallpaperChannels[i]) {
+                        mTouchedWallpaperWindows.push(window);
+                    }
+                }
+            }
+
+            if (window->inputChannel == touchedWindowChannel) {
+                mTouchedWindow = window;
             }
         }
+        mTempTouchedWallpaperChannels.clear();
+
+#if DEBUG_FOCUS
+        logDispatchStateLocked();
+#endif
     } // release lock
 
-    if (preemptedOne) {
-        // Wake up the poll loop so it can get a head start dispatching the next event.
-        mPollLoop->wake();
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+}
+
+void InputDispatcher::setFocusedApplication(const InputApplication* inputApplication) {
+#if DEBUG_FOCUS
+    LOGD("setFocusedApplication");
+#endif
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        releaseFocusedApplicationLocked();
+
+        if (inputApplication) {
+            mFocusedApplicationStorage = *inputApplication;
+            mFocusedApplication = & mFocusedApplicationStorage;
+        }
+
+#if DEBUG_FOCUS
+        logDispatchStateLocked();
+#endif
+    } // release lock
+
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+}
+
+void InputDispatcher::releaseFocusedApplicationLocked() {
+    if (mFocusedApplication) {
+        mFocusedApplication = NULL;
+        mFocusedApplicationStorage.handle.clear();
     }
 }
 
-status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {
+void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
+#if DEBUG_FOCUS
+    LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
+#endif
+
+    bool changed;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
+            if (mDispatchFrozen && ! frozen) {
+                resetANRTimeoutsLocked();
+            }
+
+            mDispatchEnabled = enabled;
+            mDispatchFrozen = frozen;
+            changed = true;
+        } else {
+            changed = false;
+        }
+
+#if DEBUG_FOCUS
+        logDispatchStateLocked();
+#endif
+    } // release lock
+
+    if (changed) {
+        // Wake up poll loop since it may need to make new input dispatching choices.
+        mLooper->wake();
+    }
+}
+
+void InputDispatcher::logDispatchStateLocked() {
+    String8 dump;
+    dumpDispatchStateLocked(dump);
+
+    char* text = dump.lockBuffer(dump.size());
+    char* start = text;
+    while (*start != '\0') {
+        char* end = strchr(start, '\n');
+        if (*end == '\n') {
+            *(end++) = '\0';
+        }
+        LOGD("%s", start);
+        start = end;
+    }
+}
+
+void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
+    dump.appendFormat("  dispatchEnabled: %d\n", mDispatchEnabled);
+    dump.appendFormat("  dispatchFrozen: %d\n", mDispatchFrozen);
+
+    if (mFocusedApplication) {
+        dump.appendFormat("  focusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
+                mFocusedApplication->name.string(),
+                mFocusedApplication->dispatchingTimeout / 1000000.0);
+    } else {
+        dump.append("  focusedApplication: <null>\n");
+    }
+    dump.appendFormat("  focusedWindow: name='%s'\n",
+            mFocusedWindow != NULL ? mFocusedWindow->name.string() : "<null>");
+    dump.appendFormat("  touchedWindow: name='%s', touchDown=%d\n",
+            mTouchedWindow != NULL ? mTouchedWindow->name.string() : "<null>",
+            mTouchDown);
+    for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) {
+        dump.appendFormat("  touchedWallpaperWindows[%d]: name='%s'\n",
+                i, mTouchedWallpaperWindows[i]->name.string());
+    }
+    for (size_t i = 0; i < mWindows.size(); i++) {
+        dump.appendFormat("  windows[%d]: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
+                "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
+                "frame=[%d,%d][%d,%d], "
+                "visibleFrame=[%d,%d][%d,%d], "
+                "touchableArea=[%d,%d][%d,%d], "
+                "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+                i, mWindows[i].name.string(),
+                toString(mWindows[i].paused),
+                toString(mWindows[i].hasFocus),
+                toString(mWindows[i].hasWallpaper),
+                toString(mWindows[i].visible),
+                toString(mWindows[i].canReceiveKeys),
+                mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType,
+                mWindows[i].layer,
+                mWindows[i].frameLeft, mWindows[i].frameTop,
+                mWindows[i].frameRight, mWindows[i].frameBottom,
+                mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop,
+                mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom,
+                mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop,
+                mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom,
+                mWindows[i].ownerPid, mWindows[i].ownerUid,
+                mWindows[i].dispatchingTimeout / 1000000.0);
+    }
+
+    for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+        const sp<InputChannel>& channel = mMonitoringChannels[i];
+        dump.appendFormat("  monitoringChannel[%d]: '%s'\n",
+                i, channel->getName().string());
+    }
+
+    dump.appendFormat("  inboundQueue: length=%u", mInboundQueue.count());
+
+    for (size_t i = 0; i < mActiveConnections.size(); i++) {
+        const Connection* connection = mActiveConnections[i];
+        dump.appendFormat("  activeConnection[%d]: '%s', status=%s, outboundQueueLength=%u"
+                "inputState.isNeutral=%s, inputState.isOutOfSync=%s\n",
+                i, connection->getInputChannelName(), connection->getStatusLabel(),
+                connection->outboundQueue.count(),
+                toString(connection->inputState.isNeutral()),
+                toString(connection->inputState.isOutOfSync()));
+    }
+
+    if (isAppSwitchPendingLocked()) {
+        dump.appendFormat("  appSwitch: pending, due in %01.1fms\n",
+                (mAppSwitchDueTime - now()) / 1000000.0);
+    } else {
+        dump.append("  appSwitch: not pending\n");
+    }
+}
+
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) {
 #if DEBUG_REGISTRATION
-    LOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().string());
+    LOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
+            toString(monitor));
 #endif
 
     { // acquire lock
         AutoMutex _l(mLock);
 
-        if (getConnectionIndex(inputChannel) >= 0) {
+        if (getConnectionIndexLocked(inputChannel) >= 0) {
             LOGW("Attempted to register already registered input channel '%s'",
                     inputChannel->getName().string());
             return BAD_VALUE;
@@ -1465,7 +2418,11 @@
         int32_t receiveFd = inputChannel->getReceivePipeFd();
         mConnectionsByReceiveFd.add(receiveFd, connection);
 
-        mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
+        if (monitor) {
+            mMonitoringChannels.push(inputChannel);
+        }
+
+        mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
 
         runCommandsLockedInterruptible();
     } // release lock
@@ -1480,7 +2437,7 @@
     { // acquire lock
         AutoMutex _l(mLock);
 
-        ssize_t connectionIndex = getConnectionIndex(inputChannel);
+        ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
         if (connectionIndex < 0) {
             LOGW("Attempted to unregister already unregistered input channel '%s'",
                     inputChannel->getName().string());
@@ -1492,7 +2449,14 @@
 
         connection->status = Connection::STATUS_ZOMBIE;
 
-        mPollLoop->removeCallback(inputChannel->getReceivePipeFd());
+        for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+            if (mMonitoringChannels[i] == inputChannel) {
+                mMonitoringChannels.removeAt(i);
+                break;
+            }
+        }
+
+        mLooper->removeFd(inputChannel->getReceivePipeFd());
 
         nsecs_t currentTime = now();
         abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
@@ -1502,11 +2466,11 @@
 
     // Wake the poll loop because removing the connection may have changed the current
     // synchronization state.
-    mPollLoop->wake();
+    mLooper->wake();
     return OK;
 }
 
-ssize_t InputDispatcher::getConnectionIndex(const sp<InputChannel>& inputChannel) {
+ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
     ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd());
     if (connectionIndex >= 0) {
         sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
@@ -1541,31 +2505,7 @@
 }
 
 void InputDispatcher::onDispatchCycleFinishedLocked(
-        nsecs_t currentTime, const sp<Connection>& connection, bool recoveredFromANR) {
-    if (recoveredFromANR) {
-        LOGI("channel '%s' ~ Recovered from ANR.  %01.1fms since event, "
-                "%01.1fms since dispatch, %01.1fms since ANR",
-                connection->getInputChannelName(),
-                connection->getEventLatencyMillis(currentTime),
-                connection->getDispatchLatencyMillis(currentTime),
-                connection->getANRLatencyMillis(currentTime));
-
-        CommandEntry* commandEntry = postCommandLocked(
-                & InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible);
-        commandEntry->connection = connection;
-    }
-}
-
-void InputDispatcher::onDispatchCycleANRLocked(
         nsecs_t currentTime, const sp<Connection>& connection) {
-    LOGI("channel '%s' ~ Not responding!  %01.1fms since event, %01.1fms since dispatch",
-            connection->getInputChannelName(),
-            connection->getEventLatencyMillis(currentTime),
-            connection->getDispatchLatencyMillis(currentTime));
-
-    CommandEntry* commandEntry = postCommandLocked(
-            & InputDispatcher::doNotifyInputChannelANRLockedInterruptible);
-    commandEntry->connection = connection;
 }
 
 void InputDispatcher::onDispatchCycleBrokenLocked(
@@ -1578,6 +2518,34 @@
     commandEntry->connection = connection;
 }
 
+void InputDispatcher::onANRLocked(
+        nsecs_t currentTime, const InputApplication* application, const InputWindow* window,
+        nsecs_t eventTime, nsecs_t waitStartTime) {
+    LOGI("Application is not responding: %s.  "
+            "%01.1fms since event, %01.1fms since wait started",
+            getApplicationWindowLabelLocked(application, window).string(),
+            (currentTime - eventTime) / 1000000.0,
+            (currentTime - waitStartTime) / 1000000.0);
+
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doNotifyANRLockedInterruptible);
+    if (application) {
+        commandEntry->inputApplicationHandle = application->handle;
+    }
+    if (window) {
+        commandEntry->inputChannel = window->inputChannel;
+    }
+}
+
+void InputDispatcher::doNotifyConfigurationChangedInterruptible(
+        CommandEntry* commandEntry) {
+    mLock.unlock();
+
+    mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
+
+    mLock.lock();
+}
+
 void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
         CommandEntry* commandEntry) {
     sp<Connection> connection = commandEntry->connection;
@@ -1591,38 +2559,66 @@
     }
 }
 
-void InputDispatcher::doNotifyInputChannelANRLockedInterruptible(
+void InputDispatcher::doNotifyANRLockedInterruptible(
         CommandEntry* commandEntry) {
-    sp<Connection> connection = commandEntry->connection;
+    mLock.unlock();
 
-    if (connection->status != Connection::STATUS_ZOMBIE) {
-        mLock.unlock();
+    nsecs_t newTimeout = mPolicy->notifyANR(
+            commandEntry->inputApplicationHandle, commandEntry->inputChannel);
 
-        nsecs_t newTimeout;
-        bool resume = mPolicy->notifyInputChannelANR(connection->inputChannel, newTimeout);
+    mLock.lock();
 
-        mLock.lock();
-
-        nsecs_t currentTime = now();
-        if (resume) {
-            resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout);
-        } else {
-            abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/);
-        }
-    }
+    resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, commandEntry->inputChannel);
 }
 
-void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible(
+void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
         CommandEntry* commandEntry) {
-    sp<Connection> connection = commandEntry->connection;
+    KeyEntry* entry = commandEntry->keyEntry;
+    mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
+            entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+            entry->downTime, entry->eventTime);
 
-    if (connection->status != Connection::STATUS_ZOMBIE) {
-        mLock.unlock();
+    mLock.unlock();
 
-        mPolicy->notifyInputChannelRecoveredFromANR(connection->inputChannel);
+    bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputChannel,
+            & mReusableKeyEvent, entry->policyFlags);
 
-        mLock.lock();
+    mLock.lock();
+
+    entry->interceptKeyResult = consumed
+            ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP
+            : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+    mAllocator.releaseKeyEntry(entry);
+}
+
+void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
+    mLock.unlock();
+
+    mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->windowType,
+            commandEntry->userActivityEventType);
+
+    mLock.lock();
+}
+
+void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+        int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) {
+    // TODO Write some statistics about how long we spend waiting.
+}
+
+void InputDispatcher::dump(String8& dump) {
+    dumpDispatchStateLocked(dump);
+}
+
+
+// --- InputDispatcher::Queue ---
+
+template <typename T>
+uint32_t InputDispatcher::Queue<T>::count() const {
+    uint32_t result = 0;
+    for (const T* entry = headSentinel.next; entry != & tailSentinel; entry = entry->next) {
+        result += 1;
     }
+    return result;
 }
 
 
@@ -1641,7 +2637,7 @@
     entry->injectionIsAsync = false;
     entry->injectorPid = -1;
     entry->injectorUid = -1;
-    entry->pendingSyncDispatches = 0;
+    entry->pendingForegroundDispatches = 0;
 }
 
 InputDispatcher::ConfigurationChangedEntry*
@@ -1668,6 +2664,8 @@
     entry->metaState = metaState;
     entry->repeatCount = repeatCount;
     entry->downTime = downTime;
+    entry->syntheticRepeat = false;
+    entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
     return entry;
 }
 
@@ -1702,10 +2700,17 @@
 }
 
 InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
-        EventEntry* eventEntry) {
+        EventEntry* eventEntry,
+        int32_t targetFlags, float xOffset, float yOffset) {
     DispatchEntry* entry = mDispatchEntryPool.alloc();
     entry->eventEntry = eventEntry;
     eventEntry->refCount += 1;
+    entry->targetFlags = targetFlags;
+    entry->xOffset = xOffset;
+    entry->yOffset = yOffset;
+    entry->inProgress = false;
+    entry->headMotionSample = NULL;
+    entry->tailMotionSample = NULL;
     return entry;
 }
 
@@ -1788,6 +2793,25 @@
     motionEntry->lastSample = sample;
 }
 
+
+// --- InputDispatcher::EventEntry ---
+
+void InputDispatcher::EventEntry::recycle() {
+    injectionResult = INPUT_EVENT_INJECTION_PENDING;
+    dispatchInProgress = false;
+    pendingForegroundDispatches = 0;
+}
+
+
+// --- InputDispatcher::KeyEntry ---
+
+void InputDispatcher::KeyEntry::recycle() {
+    EventEntry::recycle();
+    syntheticRepeat = false;
+    interceptKeyResult = INTERCEPT_KEY_RESULT_UNKNOWN;
+}
+
+
 // --- InputDispatcher::MotionEntry ---
 
 uint32_t InputDispatcher::MotionEntry::countSamples() const {
@@ -1798,13 +2822,194 @@
     return count;
 }
 
+
+// --- InputDispatcher::InputState ---
+
+InputDispatcher::InputState::InputState() :
+        mIsOutOfSync(false) {
+}
+
+InputDispatcher::InputState::~InputState() {
+}
+
+bool InputDispatcher::InputState::isNeutral() const {
+    return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
+}
+
+bool InputDispatcher::InputState::isOutOfSync() const {
+    return mIsOutOfSync;
+}
+
+void InputDispatcher::InputState::setOutOfSync() {
+    if (! isNeutral()) {
+        mIsOutOfSync = true;
+    }
+}
+
+void InputDispatcher::InputState::resetOutOfSync() {
+    mIsOutOfSync = false;
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent(
+        const EventEntry* entry) {
+    switch (entry->type) {
+    case EventEntry::TYPE_KEY:
+        return trackKey(static_cast<const KeyEntry*>(entry));
+
+    case EventEntry::TYPE_MOTION:
+        return trackMotion(static_cast<const MotionEntry*>(entry));
+
+    default:
+        return CONSISTENT;
+    }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackKey(
+        const KeyEntry* entry) {
+    int32_t action = entry->action;
+    for (size_t i = 0; i < mKeyMementos.size(); i++) {
+        KeyMemento& memento = mKeyMementos.editItemAt(i);
+        if (memento.deviceId == entry->deviceId
+                && memento.source == entry->source
+                && memento.keyCode == entry->keyCode
+                && memento.scanCode == entry->scanCode) {
+            switch (action) {
+            case AKEY_EVENT_ACTION_UP:
+                mKeyMementos.removeAt(i);
+                if (isNeutral()) {
+                    mIsOutOfSync = false;
+                }
+                return CONSISTENT;
+
+            case AKEY_EVENT_ACTION_DOWN:
+                return TOLERABLE;
+
+            default:
+                return BROKEN;
+            }
+        }
+    }
+
+    switch (action) {
+    case AKEY_EVENT_ACTION_DOWN: {
+        mKeyMementos.push();
+        KeyMemento& memento = mKeyMementos.editTop();
+        memento.deviceId = entry->deviceId;
+        memento.source = entry->source;
+        memento.keyCode = entry->keyCode;
+        memento.scanCode = entry->scanCode;
+        memento.downTime = entry->downTime;
+        return CONSISTENT;
+    }
+
+    default:
+        return BROKEN;
+    }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackMotion(
+        const MotionEntry* entry) {
+    int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK;
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
+        MotionMemento& memento = mMotionMementos.editItemAt(i);
+        if (memento.deviceId == entry->deviceId
+                && memento.source == entry->source) {
+            switch (action) {
+            case AMOTION_EVENT_ACTION_UP:
+            case AMOTION_EVENT_ACTION_CANCEL:
+                mMotionMementos.removeAt(i);
+                if (isNeutral()) {
+                    mIsOutOfSync = false;
+                }
+                return CONSISTENT;
+
+            case AMOTION_EVENT_ACTION_DOWN:
+                return TOLERABLE;
+
+            case AMOTION_EVENT_ACTION_POINTER_DOWN:
+                if (entry->pointerCount == memento.pointerCount + 1) {
+                    memento.setPointers(entry);
+                    return CONSISTENT;
+                }
+                return BROKEN;
+
+            case AMOTION_EVENT_ACTION_POINTER_UP:
+                if (entry->pointerCount == memento.pointerCount - 1) {
+                    memento.setPointers(entry);
+                    return CONSISTENT;
+                }
+                return BROKEN;
+
+            case AMOTION_EVENT_ACTION_MOVE:
+                if (entry->pointerCount == memento.pointerCount) {
+                    return CONSISTENT;
+                }
+                return BROKEN;
+
+            default:
+                return BROKEN;
+            }
+        }
+    }
+
+    switch (action) {
+    case AMOTION_EVENT_ACTION_DOWN: {
+        mMotionMementos.push();
+        MotionMemento& memento = mMotionMementos.editTop();
+        memento.deviceId = entry->deviceId;
+        memento.source = entry->source;
+        memento.xPrecision = entry->xPrecision;
+        memento.yPrecision = entry->yPrecision;
+        memento.downTime = entry->downTime;
+        memento.setPointers(entry);
+        return CONSISTENT;
+    }
+
+    default:
+        return BROKEN;
+    }
+}
+
+void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
+    pointerCount = entry->pointerCount;
+    for (uint32_t i = 0; i < entry->pointerCount; i++) {
+        pointerIds[i] = entry->pointerIds[i];
+        pointerCoords[i] = entry->lastSample->pointerCoords[i];
+    }
+}
+
+void InputDispatcher::InputState::synthesizeCancelationEvents(
+        Allocator* allocator, Vector<EventEntry*>& outEvents) const {
+    for (size_t i = 0; i < mKeyMementos.size(); i++) {
+        const KeyMemento& memento = mKeyMementos.itemAt(i);
+        outEvents.push(allocator->obtainKeyEntry(now(),
+                memento.deviceId, memento.source, 0,
+                AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_CANCELED,
+                memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
+    }
+
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
+        const MotionMemento& memento = mMotionMementos.itemAt(i);
+        outEvents.push(allocator->obtainMotionEntry(now(),
+                memento.deviceId, memento.source, 0,
+                AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0,
+                memento.xPrecision, memento.yPrecision, memento.downTime,
+                memento.pointerCount, memento.pointerIds, memento.pointerCoords));
+    }
+}
+
+void InputDispatcher::InputState::clear() {
+    mKeyMementos.clear();
+    mMotionMementos.clear();
+    mIsOutOfSync = false;
+}
+
+
 // --- InputDispatcher::Connection ---
 
 InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) :
         status(STATUS_NORMAL), inputChannel(inputChannel), inputPublisher(inputChannel),
-        nextTimeoutTime(LONG_LONG_MAX),
-        lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX),
-        lastANRTime(LONG_LONG_MAX) {
+        lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX) {
 }
 
 InputDispatcher::Connection::~Connection() {
@@ -1814,10 +3019,6 @@
     return inputPublisher.initialize();
 }
 
-void InputDispatcher::Connection::setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout) {
-    nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
-}
-
 const char* InputDispatcher::Connection::getStatusLabel() const {
     switch (status) {
     case STATUS_NORMAL:
@@ -1826,9 +3027,6 @@
     case STATUS_BROKEN:
         return "BROKEN";
 
-    case STATUS_NOT_RESPONDING:
-        return "NOT_RESPONDING";
-
     case STATUS_ZOMBIE:
         return "ZOMBIE";
 
@@ -1839,8 +3037,8 @@
 
 InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent(
         const EventEntry* eventEntry) const {
-    for (DispatchEntry* dispatchEntry = outboundQueue.tail.prev;
-            dispatchEntry != & outboundQueue.head; dispatchEntry = dispatchEntry->prev) {
+    for (DispatchEntry* dispatchEntry = outboundQueue.tailSentinel.prev;
+            dispatchEntry != & outboundQueue.headSentinel; dispatchEntry = dispatchEntry->prev) {
         if (dispatchEntry->eventEntry == eventEntry) {
             return dispatchEntry;
         }
@@ -1848,9 +3046,11 @@
     return NULL;
 }
 
+
 // --- InputDispatcher::CommandEntry ---
 
-InputDispatcher::CommandEntry::CommandEntry() {
+InputDispatcher::CommandEntry::CommandEntry() :
+    keyEntry(NULL) {
 }
 
 InputDispatcher::CommandEntry::~CommandEntry() {
diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp
index ed4f07b..09fce38 100644
--- a/libs/ui/InputManager.cpp
+++ b/libs/ui/InputManager.cpp
@@ -72,52 +72,12 @@
     return OK;
 }
 
-status_t InputManager::registerInputChannel(const sp<InputChannel>& inputChannel) {
-    return mDispatcher->registerInputChannel(inputChannel);
+sp<InputReaderInterface> InputManager::getReader() {
+    return mReader;
 }
 
-status_t InputManager::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
-    return mDispatcher->unregisterInputChannel(inputChannel);
-}
-
-int32_t InputManager::injectInputEvent(const InputEvent* event,
-        int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
-    return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis);
-}
-
-void InputManager::preemptInputDispatch() {
-    mDispatcher->preemptInputDispatch();
-}
-
-void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) {
-    mReader->getInputConfiguration(outConfiguration);
-}
-
-status_t InputManager::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
-    return mReader->getInputDeviceInfo(deviceId, outDeviceInfo);
-}
-
-void InputManager::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
-    mReader->getInputDeviceIds(outDeviceIds);
-}
-
-int32_t InputManager::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
-        int32_t scanCode) {
-    return mReader->getScanCodeState(deviceId, sourceMask, scanCode);
-}
-
-int32_t InputManager::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
-        int32_t keyCode) {
-    return mReader->getKeyCodeState(deviceId, sourceMask, keyCode);
-}
-
-int32_t InputManager::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) {
-    return mReader->getSwitchState(deviceId, sourceMask, sw);
-}
-
-bool InputManager::hasKeys(int32_t deviceId, uint32_t sourceMask,
-        size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
-    return mReader->hasKeys(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+sp<InputDispatcherInterface> InputManager::getDispatcher() {
+    return mDispatcher;
 }
 
 } // namespace android
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index d57b38c..783cbc4 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -325,9 +325,6 @@
     if (classes & INPUT_DEVICE_CLASS_DPAD) {
         keyboardSources |= AINPUT_SOURCE_DPAD;
     }
-    if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
-        keyboardSources |= AINPUT_SOURCE_GAMEPAD;
-    }
 
     if (keyboardSources != 0) {
         device->addMapper(new KeyboardInputMapper(device,
@@ -573,6 +570,60 @@
     } // release device registy reader lock
 }
 
+void InputReader::dump(String8& dump) {
+    dumpDeviceInfo(dump);
+}
+
+static void dumpMotionRange(String8& dump,
+        const char* name, const InputDeviceInfo::MotionRange* range) {
+    if (range) {
+        dump.appendFormat("      %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n",
+                name, range->min, range->max, range->flat, range->fuzz);
+    }
+}
+
+#define DUMP_MOTION_RANGE(range) \
+    dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range));
+
+void InputReader::dumpDeviceInfo(String8& dump) {
+    Vector<int32_t> deviceIds;
+    getInputDeviceIds(deviceIds);
+
+    InputDeviceInfo deviceInfo;
+    for (size_t i = 0; i < deviceIds.size(); i++) {
+        int32_t deviceId = deviceIds[i];
+
+        status_t result = getInputDeviceInfo(deviceId, & deviceInfo);
+        if (result == NAME_NOT_FOUND) {
+            continue;
+        } else if (result != OK) {
+            dump.appendFormat("  ** Unexpected error %d getting information about input devices.\n",
+                    result);
+            continue;
+        }
+
+        dump.appendFormat("  Device %d: '%s'\n",
+                deviceInfo.getId(), deviceInfo.getName().string());
+        dump.appendFormat("    sources = 0x%08x\n",
+                deviceInfo.getSources());
+        dump.appendFormat("    keyboardType = %d\n",
+                deviceInfo.getKeyboardType());
+
+        dump.append("    motion ranges:\n");
+        DUMP_MOTION_RANGE(X);
+        DUMP_MOTION_RANGE(Y);
+        DUMP_MOTION_RANGE(PRESSURE);
+        DUMP_MOTION_RANGE(SIZE);
+        DUMP_MOTION_RANGE(TOUCH_MAJOR);
+        DUMP_MOTION_RANGE(TOUCH_MINOR);
+        DUMP_MOTION_RANGE(TOOL_MAJOR);
+        DUMP_MOTION_RANGE(TOOL_MINOR);
+        DUMP_MOTION_RANGE(ORIENTATION);
+    }
+}
+
+#undef DUMP_MOTION_RANGE
+
 
 // --- InputReaderThread ---
 
@@ -740,10 +791,6 @@
 }
 
 bool InputMapper::applyStandardPolicyActions(nsecs_t when, int32_t policyActions) {
-    if (policyActions & InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING) {
-        getDispatcher()->notifyAppSwitchComing(when);
-    }
-
     return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH;
 }
 
@@ -1249,20 +1296,12 @@
     mLocked.orientedRanges.haveOrientation = false;
 }
 
-static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) {
-    if (axis.valid) {
-        LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d",
-                name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
-    } else {
-        LOGI(INDENT "Raw %s axis: unknown range", name);
-    }
-}
-
 void TouchInputMapper::configure() {
     InputMapper::configure();
 
     // Configure basic parameters.
     configureParameters();
+    logParameters();
 
     // Configure absolute axis information.
     configureRawAxes();
@@ -1287,6 +1326,18 @@
     mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
 }
 
+void TouchInputMapper::logParameters() {
+    if (mParameters.useBadTouchFilter) {
+        LOGI(INDENT "Bad touch filter enabled.");
+    }
+    if (mParameters.useAveragingTouchFilter) {
+        LOGI(INDENT "Averaging touch filter enabled.");
+    }
+    if (mParameters.useJumpyTouchFilter) {
+        LOGI(INDENT "Jumpy touch filter enabled.");
+    }
+}
+
 void TouchInputMapper::configureRawAxes() {
     mRawAxes.x.clear();
     mRawAxes.y.clear();
@@ -1298,6 +1349,15 @@
     mRawAxes.orientation.clear();
 }
 
+static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) {
+    if (axis.valid) {
+        LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d",
+                name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
+    } else {
+        LOGI(INDENT "Raw %s axis: unknown range", name);
+    }
+}
+
 void TouchInputMapper::logRawAxes() {
     logAxisInfo(mRawAxes.x, "x");
     logAxisInfo(mRawAxes.y, "y");
@@ -1331,8 +1391,10 @@
 
     bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height;
     if (sizeChanged) {
-        LOGI("Device configured: id=0x%x, name=%s (display size was changed)",
+        LOGI("Device reconfigured (display size changed): id=0x%x, name=%s",
                 getDeviceId(), getDeviceName().string());
+        LOGI(INDENT "Width: %dpx", width);
+        LOGI(INDENT "Height: %dpx", height);
 
         mLocked.surfaceWidth = width;
         mLocked.surfaceHeight = height;
@@ -1500,9 +1562,41 @@
         mLocked.orientedRanges.y.fuzz = orientedYScale;
     }
 
+    if (sizeChanged) {
+        logMotionRangesLocked();
+    }
+
     return true;
 }
 
+static void logMotionRangeInfo(InputDeviceInfo::MotionRange* range, const char* name) {
+    if (range) {
+        LOGI(INDENT "Output %s range: min=%f, max=%f, flat=%f, fuzz=%f",
+                name, range->min, range->max, range->flat, range->fuzz);
+    } else {
+        LOGI(INDENT "Output %s range: unsupported", name);
+    }
+}
+
+void TouchInputMapper::logMotionRangesLocked() {
+    logMotionRangeInfo(& mLocked.orientedRanges.x, "x");
+    logMotionRangeInfo(& mLocked.orientedRanges.y, "y");
+    logMotionRangeInfo(mLocked.orientedRanges.havePressure
+            ? & mLocked.orientedRanges.pressure : NULL, "pressure");
+    logMotionRangeInfo(mLocked.orientedRanges.haveSize
+            ? & mLocked.orientedRanges.size : NULL, "size");
+    logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea
+            ? & mLocked.orientedRanges.touchMajor : NULL, "touchMajor");
+    logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea
+            ? & mLocked.orientedRanges.touchMinor : NULL, "touchMinor");
+    logMotionRangeInfo(mLocked.orientedRanges.haveToolArea
+            ? & mLocked.orientedRanges.toolMajor : NULL, "toolMajor");
+    logMotionRangeInfo(mLocked.orientedRanges.haveToolArea
+            ? & mLocked.orientedRanges.toolMinor : NULL, "toolMinor");
+    logMotionRangeInfo(mLocked.orientedRanges.haveOrientation
+            ? & mLocked.orientedRanges.orientation : NULL, "orientation");
+}
+
 void TouchInputMapper::configureVirtualKeysLocked() {
     assert(mRawAxes.x.valid && mRawAxes.y.valid);
 
@@ -1768,16 +1862,18 @@
 }
 
 void TouchInputMapper::logCalibration() {
+    LOGI(INDENT "Calibration:");
+
     // Touch Area
     switch (mCalibration.touchAreaCalibration) {
     case Calibration::TOUCH_AREA_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.touchArea.calibration: none");
+        LOGI(INDENT INDENT "touch.touchArea.calibration: none");
         break;
     case Calibration::TOUCH_AREA_CALIBRATION_GEOMETRIC:
-        LOGI(INDENT "  touch.touchArea.calibration: geometric");
+        LOGI(INDENT INDENT "touch.touchArea.calibration: geometric");
         break;
     case Calibration::TOUCH_AREA_CALIBRATION_PRESSURE:
-        LOGI(INDENT "  touch.touchArea.calibration: pressure");
+        LOGI(INDENT INDENT "touch.touchArea.calibration: pressure");
         break;
     default:
         assert(false);
@@ -1786,40 +1882,40 @@
     // Tool Area
     switch (mCalibration.toolAreaCalibration) {
     case Calibration::TOOL_AREA_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.toolArea.calibration: none");
+        LOGI(INDENT INDENT "touch.toolArea.calibration: none");
         break;
     case Calibration::TOOL_AREA_CALIBRATION_GEOMETRIC:
-        LOGI(INDENT "  touch.toolArea.calibration: geometric");
+        LOGI(INDENT INDENT "touch.toolArea.calibration: geometric");
         break;
     case Calibration::TOOL_AREA_CALIBRATION_LINEAR:
-        LOGI(INDENT "  touch.toolArea.calibration: linear");
+        LOGI(INDENT INDENT "touch.toolArea.calibration: linear");
         break;
     default:
         assert(false);
     }
 
     if (mCalibration.haveToolAreaLinearScale) {
-        LOGI(INDENT "  touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale);
+        LOGI(INDENT INDENT "touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale);
     }
 
     if (mCalibration.haveToolAreaLinearBias) {
-        LOGI(INDENT "  touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias);
+        LOGI(INDENT INDENT "touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias);
     }
 
     if (mCalibration.haveToolAreaIsSummed) {
-        LOGI(INDENT "  touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed);
+        LOGI(INDENT INDENT "touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed);
     }
 
     // Pressure
     switch (mCalibration.pressureCalibration) {
     case Calibration::PRESSURE_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.pressure.calibration: none");
+        LOGI(INDENT INDENT "touch.pressure.calibration: none");
         break;
     case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
-        LOGI(INDENT "  touch.pressure.calibration: physical");
+        LOGI(INDENT INDENT "touch.pressure.calibration: physical");
         break;
     case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
-        LOGI(INDENT "  touch.pressure.calibration: amplitude");
+        LOGI(INDENT INDENT "touch.pressure.calibration: amplitude");
         break;
     default:
         assert(false);
@@ -1827,10 +1923,10 @@
 
     switch (mCalibration.pressureSource) {
     case Calibration::PRESSURE_SOURCE_PRESSURE:
-        LOGI(INDENT "  touch.pressure.source: pressure");
+        LOGI(INDENT INDENT "touch.pressure.source: pressure");
         break;
     case Calibration::PRESSURE_SOURCE_TOUCH:
-        LOGI(INDENT "  touch.pressure.source: touch");
+        LOGI(INDENT INDENT "touch.pressure.source: touch");
         break;
     case Calibration::PRESSURE_SOURCE_DEFAULT:
         break;
@@ -1839,16 +1935,16 @@
     }
 
     if (mCalibration.havePressureScale) {
-        LOGI(INDENT "  touch.pressure.scale: %f", mCalibration.pressureScale);
+        LOGI(INDENT INDENT "touch.pressure.scale: %f", mCalibration.pressureScale);
     }
 
     // Size
     switch (mCalibration.sizeCalibration) {
     case Calibration::SIZE_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.size.calibration: none");
+        LOGI(INDENT INDENT "touch.size.calibration: none");
         break;
     case Calibration::SIZE_CALIBRATION_NORMALIZED:
-        LOGI(INDENT "  touch.size.calibration: normalized");
+        LOGI(INDENT INDENT "touch.size.calibration: normalized");
         break;
     default:
         assert(false);
@@ -1857,10 +1953,10 @@
     // Orientation
     switch (mCalibration.orientationCalibration) {
     case Calibration::ORIENTATION_CALIBRATION_NONE:
-        LOGI(INDENT "  touch.orientation.calibration: none");
+        LOGI(INDENT INDENT "touch.orientation.calibration: none");
         break;
     case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
-        LOGI(INDENT "  touch.orientation.calibration: interpolated");
+        LOGI(INDENT INDENT "touch.orientation.calibration: interpolated");
         break;
     default:
         assert(false);
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
index 4c402dc..2c6346e 100644
--- a/libs/ui/InputTransport.cpp
+++ b/libs/ui/InputTransport.cpp
@@ -131,7 +131,10 @@
 }
 
 status_t InputChannel::sendSignal(char signal) {
-    ssize_t nWrite = ::write(mSendPipeFd, & signal, 1);
+    ssize_t nWrite;
+    do {
+        nWrite = ::write(mSendPipeFd, & signal, 1);
+    } while (nWrite == -1 && errno == EINTR);
 
     if (nWrite == 1) {
 #if DEBUG_CHANNEL_SIGNALS
@@ -147,7 +150,11 @@
 }
 
 status_t InputChannel::receiveSignal(char* outSignal) {
-    ssize_t nRead = ::read(mReceivePipeFd, outSignal, 1);
+    ssize_t nRead;
+    do {
+        nRead = ::read(mReceivePipeFd, outSignal, 1);
+    } while (nRead == -1 && errno == EINTR);
+
     if (nRead == 1) {
 #if DEBUG_CHANNEL_SIGNALS
         LOGD("channel '%s' ~ received signal '%c'", mName.string(), *outSignal);
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index 2e20268..eb75ed8 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -86,7 +86,7 @@
 	$(commonSources) \
 	BackupData.cpp \
 	BackupHelpers.cpp \
-	PollLoop.cpp
+	Looper.cpp
 
 ifeq ($(TARGET_OS),linux)
 LOCAL_LDLIBS += -lrt -ldl
diff --git a/libs/utils/Looper.cpp b/libs/utils/Looper.cpp
new file mode 100644
index 0000000..4aa50d6
--- /dev/null
+++ b/libs/utils/Looper.cpp
@@ -0,0 +1,374 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// A looper implementation based on epoll().
+//
+#define LOG_TAG "Looper"
+
+//#define LOG_NDEBUG 0
+
+// Debugs poll and wake interactions.
+#define DEBUG_POLL_AND_WAKE 0
+
+// Debugs callback registration and invocation.
+#define DEBUG_CALLBACKS 0
+
+#include <cutils/log.h>
+#include <utils/Looper.h>
+#include <utils/Timers.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+
+
+namespace android {
+
+static pthread_mutex_t gTLSMutex = PTHREAD_MUTEX_INITIALIZER;
+static bool gHaveTLS = false;
+static pthread_key_t gTLS = 0;
+
+// Hint for number of file descriptors to be associated with the epoll instance.
+static const int EPOLL_SIZE_HINT = 8;
+
+// Maximum number of file descriptors for which to retrieve poll events each iteration.
+static const int EPOLL_MAX_EVENTS = 16;
+
+Looper::Looper(bool allowNonCallbacks) :
+        mAllowNonCallbacks(allowNonCallbacks),
+        mResponseIndex(0) {
+    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
+    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
+
+    int wakeFds[2];
+    int result = pipe(wakeFds);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
+
+    mWakeReadPipeFd = wakeFds[0];
+    mWakeWritePipeFd = wakeFds[1];
+
+    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
+            errno);
+
+    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
+            errno);
+
+    struct epoll_event eventItem;
+    eventItem.events = EPOLLIN;
+    eventItem.data.fd = mWakeReadPipeFd;
+    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
+            errno);
+}
+
+Looper::~Looper() {
+    close(mWakeReadPipeFd);
+    close(mWakeWritePipeFd);
+    close(mEpollFd);
+}
+
+void Looper::threadDestructor(void *st) {
+    Looper* const self = static_cast<Looper*>(st);
+    if (self != NULL) {
+        self->decStrong((void*)threadDestructor);
+    }
+}
+
+void Looper::setForThread(const sp<Looper>& looper) {
+    sp<Looper> old = getForThread(); // also has side-effect of initializing TLS
+
+    if (looper != NULL) {
+        looper->incStrong((void*)threadDestructor);
+    }
+
+    pthread_setspecific(gTLS, looper.get());
+
+    if (old != NULL) {
+        old->decStrong((void*)threadDestructor);
+    }
+}
+
+sp<Looper> Looper::getForThread() {
+    if (!gHaveTLS) {
+        pthread_mutex_lock(&gTLSMutex);
+        if (pthread_key_create(&gTLS, threadDestructor) != 0) {
+            pthread_mutex_unlock(&gTLSMutex);
+            return NULL;
+        }
+        gHaveTLS = true;
+        pthread_mutex_unlock(&gTLSMutex);
+    }
+
+    return (Looper*)pthread_getspecific(gTLS);
+}
+
+sp<Looper> Looper::prepare(int opts) {
+    bool allowNonCallbacks = opts & ALOOPER_PREPARE_ALLOW_NON_CALLBACKS;
+    sp<Looper> looper = Looper::getForThread();
+    if (looper == NULL) {
+        looper = new Looper(allowNonCallbacks);
+        Looper::setForThread(looper);
+    }
+    if (looper->getAllowNonCallbacks() != allowNonCallbacks) {
+        LOGW("Looper already prepared for this thread with a different value for the "
+                "ALOOPER_PREPARE_ALLOW_NON_CALLBACKS option.");
+    }
+    return looper;
+}
+
+bool Looper::getAllowNonCallbacks() const {
+    return mAllowNonCallbacks;
+}
+
+int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
+    int result = 0;
+    for (;;) {
+        while (mResponseIndex < mResponses.size()) {
+            const Response& response = mResponses.itemAt(mResponseIndex++);
+            if (! response.request.callback) {
+#if DEBUG_POLL_AND_WAKE
+                LOGD("%p ~ pollOnce - returning signalled identifier %d: "
+                        "fd=%d, events=0x%x, data=%p", this,
+                        response.request.ident, response.request.fd,
+                        response.events, response.request.data);
+#endif
+                if (outFd != NULL) *outFd = response.request.fd;
+                if (outEvents != NULL) *outEvents = response.events;
+                if (outData != NULL) *outData = response.request.data;
+                return response.request.ident;
+            }
+        }
+
+        if (result != 0) {
+#if DEBUG_POLL_AND_WAKE
+            LOGD("%p ~ pollOnce - returning result %d", this, result);
+#endif
+            if (outFd != NULL) *outFd = 0;
+            if (outEvents != NULL) *outEvents = NULL;
+            if (outData != NULL) *outData = NULL;
+            return result;
+        }
+
+        result = pollInner(timeoutMillis);
+    }
+}
+
+int Looper::pollInner(int timeoutMillis) {
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
+#endif
+    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
+    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
+    if (eventCount < 0) {
+        if (errno == EINTR) {
+            return ALOOPER_POLL_WAKE;
+        }
+
+        LOGW("Poll failed with an unexpected error, errno=%d", errno);
+        return ALOOPER_POLL_ERROR;
+    }
+
+    if (eventCount == 0) {
+#if DEBUG_POLL_AND_WAKE
+        LOGD("%p ~ pollOnce - timeout", this);
+#endif
+        return ALOOPER_POLL_TIMEOUT;
+    }
+
+    int result = ALOOPER_POLL_WAKE;
+    mResponses.clear();
+    mResponseIndex = 0;
+
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
+#endif
+    { // acquire lock
+        AutoMutex _l(mLock);
+        for (int i = 0; i < eventCount; i++) {
+            int fd = eventItems[i].data.fd;
+            uint32_t epollEvents = eventItems[i].events;
+            if (fd == mWakeReadPipeFd) {
+                if (epollEvents & EPOLLIN) {
+#if DEBUG_POLL_AND_WAKE
+                    LOGD("%p ~ pollOnce - awoken", this);
+#endif
+                    char buffer[16];
+                    ssize_t nRead;
+                    do {
+                        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
+                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+                } else {
+                    LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
+                }
+            } else {
+                ssize_t requestIndex = mRequests.indexOfKey(fd);
+                if (requestIndex >= 0) {
+                    int events = 0;
+                    if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
+                    if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
+                    if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
+                    if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
+
+                    Response response;
+                    response.events = events;
+                    response.request = mRequests.valueAt(requestIndex);
+                    mResponses.push(response);
+                } else {
+                    LOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
+                            "no longer registered.", epollEvents, fd);
+                }
+            }
+        }
+    }
+
+    for (size_t i = 0; i < mResponses.size(); i++) {
+        const Response& response = mResponses.itemAt(i);
+        if (response.request.callback) {
+#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
+            LOGD("%p ~ pollOnce - invoking callback: fd=%d, events=0x%x, data=%p", this,
+                    response.request.fd, response.events, response.request.data);
+#endif
+            int callbackResult = response.request.callback(
+                    response.request.fd, response.events, response.request.data);
+            if (callbackResult == 0) {
+                removeFd(response.request.fd);
+            }
+
+            result = ALOOPER_POLL_CALLBACK;
+        }
+    }
+    return result;
+}
+
+int Looper::pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
+    if (timeoutMillis <= 0) {
+        int result;
+        do {
+            result = pollOnce(timeoutMillis, outFd, outEvents, outData);
+        } while (result == ALOOPER_POLL_CALLBACK);
+        return result;
+    } else {
+        nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC)
+                + milliseconds_to_nanoseconds(timeoutMillis);
+
+        for (;;) {
+            int result = pollOnce(timeoutMillis, outFd, outEvents, outData);
+            if (result != ALOOPER_POLL_CALLBACK) {
+                return result;
+            }
+
+            nsecs_t timeoutNanos = endTime - systemTime(SYSTEM_TIME_MONOTONIC);
+            if (timeoutNanos <= 0) {
+                return ALOOPER_POLL_TIMEOUT;
+            }
+
+            timeoutMillis = int(nanoseconds_to_milliseconds(timeoutNanos + 999999LL));
+        }
+    }
+}
+
+void Looper::wake() {
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ wake", this);
+#endif
+
+    ssize_t nWrite;
+    do {
+        nWrite = write(mWakeWritePipeFd, "W", 1);
+    } while (nWrite == -1 && errno == EINTR);
+
+    if (nWrite != 1) {
+        if (errno != EAGAIN) {
+            LOGW("Could not write wake signal, errno=%d", errno);
+        }
+    }
+}
+
+int Looper::addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data) {
+#if DEBUG_CALLBACKS
+    LOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
+            events, callback, data);
+#endif
+
+    int epollEvents = 0;
+    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
+    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
+    if (events & ALOOPER_EVENT_ERROR) epollEvents |= EPOLLERR;
+    if (events & ALOOPER_EVENT_HANGUP) epollEvents |= EPOLLHUP;
+
+    if (epollEvents == 0) {
+        LOGE("Invalid attempt to set a callback with no selected poll events.");
+        return -1;
+    }
+
+    if (! callback) {
+        if (! mAllowNonCallbacks) {
+            LOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
+            return -1;
+        }
+
+        if (ident < 0) {
+            LOGE("Invalid attempt to set NULL callback with ident <= 0.");
+            return -1;
+        }
+    }
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        Request request;
+        request.fd = fd;
+        request.ident = ident;
+        request.callback = callback;
+        request.data = data;
+
+        struct epoll_event eventItem;
+        eventItem.events = epollEvents;
+        eventItem.data.fd = fd;
+
+        ssize_t requestIndex = mRequests.indexOfKey(fd);
+        if (requestIndex < 0) {
+            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
+            if (epollResult < 0) {
+                LOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
+                return -1;
+            }
+            mRequests.add(fd, request);
+        } else {
+            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
+            if (epollResult < 0) {
+                LOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
+                return -1;
+            }
+            mRequests.replaceValueAt(requestIndex, request);
+        }
+    } // release lock
+    return 1;
+}
+
+int Looper::removeFd(int fd) {
+#if DEBUG_CALLBACKS
+    LOGD("%p ~ removeFd - fd=%d", this, fd);
+#endif
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+        ssize_t requestIndex = mRequests.indexOfKey(fd);
+        if (requestIndex < 0) {
+            return 0;
+        }
+
+        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, NULL);
+        if (epollResult < 0) {
+            LOGE("Error removing epoll events for fd %d, errno=%d", fd, errno);
+            return -1;
+        }
+
+        mRequests.removeItemsAt(requestIndex);
+    } // request lock
+    return 1;
+}
+
+} // namespace android
diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp
deleted file mode 100644
index 6d3eeee..0000000
--- a/libs/utils/PollLoop.cpp
+++ /dev/null
@@ -1,371 +0,0 @@
-//
-// Copyright 2010 The Android Open Source Project
-//
-// A select loop implementation.
-//
-#define LOG_TAG "PollLoop"
-
-//#define LOG_NDEBUG 0
-
-// Debugs poll and wake interactions.
-#define DEBUG_POLL_AND_WAKE 0
-
-// Debugs callback registration and invocation.
-#define DEBUG_CALLBACKS 0
-
-#include <cutils/log.h>
-#include <utils/PollLoop.h>
-
-#include <unistd.h>
-#include <fcntl.h>
-
-namespace android {
-
-static pthread_mutex_t gTLSMutex = PTHREAD_MUTEX_INITIALIZER;
-static bool gHaveTLS = false;
-static pthread_key_t gTLS = 0;
-
-PollLoop::PollLoop(bool allowNonCallbacks) :
-        mAllowNonCallbacks(allowNonCallbacks), mPolling(false),
-        mWaiters(0), mPendingFdsPos(0) {
-    openWakePipe();
-}
-
-PollLoop::~PollLoop() {
-    closeWakePipe();
-}
-
-void PollLoop::threadDestructor(void *st) {
-    PollLoop* const self = static_cast<PollLoop*>(st);
-    if (self != NULL) {
-        self->decStrong((void*)threadDestructor);
-    }
-}
-
-void PollLoop::setForThread(const sp<PollLoop>& pollLoop) {
-    sp<PollLoop> old = getForThread();
-    
-    if (pollLoop != NULL) {
-        pollLoop->incStrong((void*)threadDestructor);
-    }
-    
-    pthread_setspecific(gTLS, pollLoop.get());
-    
-    if (old != NULL) {
-        old->decStrong((void*)threadDestructor);
-    }
-}
-    
-sp<PollLoop> PollLoop::getForThread() {
-    if (!gHaveTLS) {
-        pthread_mutex_lock(&gTLSMutex);
-        if (pthread_key_create(&gTLS, threadDestructor) != 0) {
-            pthread_mutex_unlock(&gTLSMutex);
-            return NULL;
-        }
-        gHaveTLS = true;
-        pthread_mutex_unlock(&gTLSMutex);
-    }
-    
-    return (PollLoop*)pthread_getspecific(gTLS);
-}
-
-void PollLoop::openWakePipe() {
-    int wakeFds[2];
-    int result = pipe(wakeFds);
-    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
-
-    mWakeReadPipeFd = wakeFds[0];
-    mWakeWritePipeFd = wakeFds[1];
-
-    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
-    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
-            errno);
-
-    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
-    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
-            errno);
-
-    // Add the wake pipe to the head of the request list with a null callback.
-    struct pollfd requestedFd;
-    requestedFd.fd = mWakeReadPipeFd;
-    requestedFd.events = POLLIN;
-    mRequestedFds.insertAt(requestedFd, 0);
-
-    RequestedCallback requestedCallback;
-    requestedCallback.callback = NULL;
-    requestedCallback.looperCallback = NULL;
-    requestedCallback.ident = 0;
-    requestedCallback.data = NULL;
-    mRequestedCallbacks.insertAt(requestedCallback, 0);
-}
-
-void PollLoop::closeWakePipe() {
-    close(mWakeReadPipeFd);
-    close(mWakeWritePipeFd);
-
-    // Note: We don't need to remove the poll structure or callback entry because this
-    //       method is currently only called by the destructor.
-}
-
-int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) {
-    // If there are still pending fds from the last call, dispatch those
-    // first, to avoid an earlier fd from starving later ones.
-    const size_t pendingFdsCount = mPendingFds.size();
-    if (mPendingFdsPos < pendingFdsCount) {
-        const PendingCallback& pending = mPendingFds.itemAt(mPendingFdsPos);
-        mPendingFdsPos++;
-        if (outEvents != NULL) *outEvents = pending.events;
-        if (outData != NULL) *outData = pending.data;
-        return pending.ident;
-    }
-    
-    mLock.lock();
-    while (mWaiters != 0) {
-        mResume.wait(mLock);
-    }
-    mPolling = true;
-    mLock.unlock();
-
-    int32_t result;
-    size_t requestedCount = mRequestedFds.size();
-
-#if DEBUG_POLL_AND_WAKE
-    LOGD("%p ~ pollOnce - waiting on %d fds", this, requestedCount);
-    for (size_t i = 0; i < requestedCount; i++) {
-        LOGD("  fd %d - events %d", mRequestedFds[i].fd, mRequestedFds[i].events);
-    }
-#endif
-
-    int respondedCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis);
-
-    if (respondedCount == 0) {
-        // Timeout
-#if DEBUG_POLL_AND_WAKE
-        LOGD("%p ~ pollOnce - timeout", this);
-#endif
-        result = POLL_TIMEOUT;
-        goto Done;
-    }
-
-    if (respondedCount < 0) {
-        // Error
-#if DEBUG_POLL_AND_WAKE
-        LOGD("%p ~ pollOnce - error, errno=%d", this, errno);
-#endif
-        if (errno != EINTR) {
-            LOGW("Poll failed with an unexpected error, errno=%d", errno);
-        }
-        result = POLL_ERROR;
-        goto Done;
-    }
-
-#if DEBUG_POLL_AND_WAKE
-    LOGD("%p ~ pollOnce - handling responses from %d fds", this, respondedCount);
-    for (size_t i = 0; i < requestedCount; i++) {
-        LOGD("  fd %d - events %d, revents %d", mRequestedFds[i].fd, mRequestedFds[i].events,
-                mRequestedFds[i].revents);
-    }
-#endif
-
-    mPendingCallbacks.clear();
-    mPendingFds.clear();
-    mPendingFdsPos = 0;
-    if (outEvents != NULL) *outEvents = 0;
-    if (outData != NULL) *outData = NULL;
-    
-    result = POLL_CALLBACK;
-    for (size_t i = 0; i < requestedCount; i++) {
-        const struct pollfd& requestedFd = mRequestedFds.itemAt(i);
-
-        short revents = requestedFd.revents;
-        if (revents) {
-            const RequestedCallback& requestedCallback = mRequestedCallbacks.itemAt(i);
-            PendingCallback pending;
-            pending.fd = requestedFd.fd;
-            pending.ident = requestedCallback.ident;
-            pending.events = revents;
-            pending.callback = requestedCallback.callback;
-            pending.looperCallback = requestedCallback.looperCallback;
-            pending.data = requestedCallback.data;
-
-            if (pending.callback || pending.looperCallback) {
-                mPendingCallbacks.push(pending);
-            } else if (pending.fd != mWakeReadPipeFd) {
-                if (result == POLL_CALLBACK) {
-                    result = pending.ident;
-                    if (outEvents != NULL) *outEvents = pending.events;
-                    if (outData != NULL) *outData = pending.data;
-                } else {
-                    mPendingFds.push(pending);
-                }
-            } else {
-#if DEBUG_POLL_AND_WAKE
-                LOGD("%p ~ pollOnce - awoken", this);
-#endif
-                char buffer[16];
-                ssize_t nRead;
-                do {
-                    nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
-                } while (nRead == sizeof(buffer));
-            }
-
-            respondedCount -= 1;
-            if (respondedCount == 0) {
-                break;
-            }
-        }
-    }
-
-Done:
-    mLock.lock();
-    mPolling = false;
-    if (mWaiters != 0) {
-        mAwake.broadcast();
-    }
-    mLock.unlock();
-
-    if (result == POLL_CALLBACK || result >= 0) {
-        size_t pendingCount = mPendingCallbacks.size();
-        for (size_t i = 0; i < pendingCount; i++) {
-            const PendingCallback& pendingCallback = mPendingCallbacks.itemAt(i);
-#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
-            LOGD("%p ~ pollOnce - invoking callback for fd %d", this, pendingCallback.fd);
-#endif
-
-            bool keep = true;
-            if (pendingCallback.callback != NULL) {
-                keep = pendingCallback.callback(pendingCallback.fd, pendingCallback.events,
-                        pendingCallback.data);
-            } else {
-                keep = pendingCallback.looperCallback(pendingCallback.fd, pendingCallback.events,
-                        pendingCallback.data) != 0;
-            }
-            if (! keep) {
-                removeCallback(pendingCallback.fd);
-            }
-        }
-    }
-
-#if DEBUG_POLL_AND_WAKE
-    LOGD("%p ~ pollOnce - done", this);
-#endif
-    return result;
-}
-
-void PollLoop::wake() {
-#if DEBUG_POLL_AND_WAKE
-    LOGD("%p ~ wake", this);
-#endif
-
-    ssize_t nWrite = write(mWakeWritePipeFd, "W", 1);
-    if (nWrite != 1) {
-        if (errno != EAGAIN) {
-            LOGW("Could not write wake signal, errno=%d", errno);
-        }
-    }
-}
-
-bool PollLoop::getAllowNonCallbacks() const {
-    return mAllowNonCallbacks;
-}
-
-void PollLoop::setCallback(int fd, int ident, int events, Callback callback, void* data) {
-    setCallbackCommon(fd, ident, events, callback, NULL, data);
-}
-
-void PollLoop::setCallback(int fd, int events, Callback callback, void* data) {
-    setCallbackCommon(fd, POLL_CALLBACK, events, callback, NULL, data);
-}
-
-void PollLoop::setLooperCallback(int fd, int ident, int events, ALooper_callbackFunc* callback,
-        void* data) {
-    setCallbackCommon(fd, ident, events, NULL, callback, data);
-}
-
-void PollLoop::setCallbackCommon(int fd, int ident, int events, Callback callback,
-        ALooper_callbackFunc* looperCallback, void* data) {
-
-#if DEBUG_CALLBACKS
-    LOGD("%p ~ setCallback - fd=%d, events=%d", this, fd, events);
-#endif
-
-    if (! events) {
-        LOGE("Invalid attempt to set a callback with no selected poll events.");
-        removeCallback(fd);
-        return;
-    }
-
-    if (! callback && ! looperCallback && ! mAllowNonCallbacks) {
-        LOGE("Invalid attempt to set NULL callback but not allowed.");
-        removeCallback(fd);
-        return;
-    }
-    
-    wakeAndLock();
-
-    struct pollfd requestedFd;
-    requestedFd.fd = fd;
-    requestedFd.events = events;
-
-    RequestedCallback requestedCallback;
-    requestedCallback.callback = callback;
-    requestedCallback.looperCallback = looperCallback;
-    requestedCallback.ident = ident;
-    requestedCallback.data = data;
-
-    ssize_t index = getRequestIndexLocked(fd);
-    if (index < 0) {
-        mRequestedFds.push(requestedFd);
-        mRequestedCallbacks.push(requestedCallback);
-    } else {
-        mRequestedFds.replaceAt(requestedFd, size_t(index));
-        mRequestedCallbacks.replaceAt(requestedCallback, size_t(index));
-    }
-
-    mLock.unlock();
-}
-
-bool PollLoop::removeCallback(int fd) {
-#if DEBUG_CALLBACKS
-    LOGD("%p ~ removeCallback - fd=%d", this, fd);
-#endif
-
-    wakeAndLock();
-
-    ssize_t index = getRequestIndexLocked(fd);
-    if (index >= 0) {
-        mRequestedFds.removeAt(size_t(index));
-        mRequestedCallbacks.removeAt(size_t(index));
-    }
-
-    mLock.unlock();
-    return index >= 0;
-}
-
-ssize_t PollLoop::getRequestIndexLocked(int fd) {
-    size_t requestCount = mRequestedFds.size();
-
-    for (size_t i = 0; i < requestCount; i++) {
-        if (mRequestedFds.itemAt(i).fd == fd) {
-            return i;
-        }
-    }
-
-    return -1;
-}
-
-void PollLoop::wakeAndLock() {
-    mLock.lock();
-    mWaiters += 1;
-    while (mPolling) {
-        wake();
-        mAwake.wait(mLock);
-    }
-    mWaiters -= 1;
-    if (mWaiters == 0) {
-        mResume.signal();
-    }
-}
-
-} // namespace android
diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk
index 725de9c..00077ee 100644
--- a/libs/utils/tests/Android.mk
+++ b/libs/utils/tests/Android.mk
@@ -7,7 +7,7 @@
 # Build the unit tests.
 test_src_files := \
 	ObbFile_test.cpp \
-	PollLoop_test.cpp \
+	Looper_test.cpp \
 	String8_test.cpp
 
 shared_libraries := \
diff --git a/libs/utils/tests/Looper_test.cpp b/libs/utils/tests/Looper_test.cpp
new file mode 100644
index 0000000..afc92f8
--- /dev/null
+++ b/libs/utils/tests/Looper_test.cpp
@@ -0,0 +1,433 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include <utils/Looper.h>
+#include <utils/Timers.h>
+#include <utils/StopWatch.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "TestHelpers.h"
+
+// # of milliseconds to fudge stopwatch measurements
+#define TIMING_TOLERANCE_MS 25
+
+namespace android {
+
+class DelayedWake : public DelayedTask {
+    sp<Looper> mLooper;
+
+public:
+    DelayedWake(int delayMillis, const sp<Looper> looper) :
+        DelayedTask(delayMillis), mLooper(looper) {
+    }
+
+protected:
+    virtual void doTask() {
+        mLooper->wake();
+    }
+};
+
+class DelayedWriteSignal : public DelayedTask {
+    Pipe* mPipe;
+
+public:
+    DelayedWriteSignal(int delayMillis, Pipe* pipe) :
+        DelayedTask(delayMillis), mPipe(pipe) {
+    }
+
+protected:
+    virtual void doTask() {
+        mPipe->writeSignal();
+    }
+};
+
+class CallbackHandler {
+public:
+    void setCallback(const sp<Looper>& looper, int fd, int events) {
+        looper->addFd(fd, 0, events, staticHandler, this);
+    }
+
+protected:
+    virtual ~CallbackHandler() { }
+
+    virtual int handler(int fd, int events) = 0;
+
+private:
+    static int staticHandler(int fd, int events, void* data) {
+        return static_cast<CallbackHandler*>(data)->handler(fd, events);
+    }
+};
+
+class StubCallbackHandler : public CallbackHandler {
+public:
+    int nextResult;
+    int callbackCount;
+
+    int fd;
+    int events;
+
+    StubCallbackHandler(int nextResult) : nextResult(nextResult),
+            callbackCount(0), fd(-1), events(-1) {
+    }
+
+protected:
+    virtual int handler(int fd, int events) {
+        callbackCount += 1;
+        this->fd = fd;
+        this->events = events;
+        return nextResult;
+    }
+};
+
+class LooperTest : public testing::Test {
+protected:
+    sp<Looper> mLooper;
+
+    virtual void SetUp() {
+        mLooper = new Looper(true);
+    }
+
+    virtual void TearDown() {
+        mLooper.clear();
+    }
+};
+
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndNotAwoken_WaitsForTimeout) {
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal timeout";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndAwokenBeforeWaiting_ImmediatelyReturns) {
+    mLooper->wake();
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(1000);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. zero because wake() was called before waiting";
+    EXPECT_EQ(ALOOPER_POLL_WAKE, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because loop was awoken";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndAwokenWhileWaiting_PromptlyReturns) {
+    sp<DelayedWake> delayedWake = new DelayedWake(100, mLooper);
+    delayedWake->run();
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(1000);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal wake delay";
+    EXPECT_EQ(ALOOPER_POLL_WAKE, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because loop was awoken";
+}
+
+TEST_F(LooperTest, PollOnce_WhenZeroTimeoutAndNoRegisteredFDs_ImmediatelyReturns) {
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+}
+
+TEST_F(LooperTest, PollOnce_WhenZeroTimeoutAndNoSignalledFDs_ImmediatelyReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+    EXPECT_EQ(0, handler.callbackCount)
+            << "callback should not have been invoked because FD was not signalled";
+}
+
+TEST_F(LooperTest, PollOnce_WhenZeroTimeoutAndSignalledFD_ImmediatelyInvokesCallbackAndReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    ASSERT_EQ(OK, pipe.writeSignal());
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked exactly once";
+    EXPECT_EQ(pipe.receiveFd, handler.fd)
+            << "callback should have received pipe fd as parameter";
+    EXPECT_EQ(ALOOPER_EVENT_INPUT, handler.events)
+            << "callback should have received ALOOPER_EVENT_INPUT as events";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndNoSignalledFDs_WaitsForTimeoutAndReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal timeout";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+    EXPECT_EQ(0, handler.callbackCount)
+            << "callback should not have been invoked because FD was not signalled";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDBeforeWaiting_ImmediatelyInvokesCallbackAndReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    pipe.writeSignal();
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked exactly once";
+    EXPECT_EQ(pipe.receiveFd, handler.fd)
+            << "callback should have received pipe fd as parameter";
+    EXPECT_EQ(ALOOPER_EVENT_INPUT, handler.events)
+            << "callback should have received ALOOPER_EVENT_INPUT as events";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDWhileWaiting_PromptlyInvokesCallbackAndReturns) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+    sp<DelayedWriteSignal> delayedWriteSignal = new DelayedWriteSignal(100, & pipe);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+    delayedWriteSignal->run();
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(1000);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal signal delay";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked exactly once";
+    EXPECT_EQ(pipe.receiveFd, handler.fd)
+            << "callback should have received pipe fd as parameter";
+    EXPECT_EQ(ALOOPER_EVENT_INPUT, handler.events)
+            << "callback should have received ALOOPER_EVENT_INPUT as events";
+}
+
+TEST_F(LooperTest, PollOnce_WhenCallbackAddedThenRemoved_CallbackShouldNotBeInvoked) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+    pipe.writeSignal(); // would cause FD to be considered signalled
+    mLooper->removeFd(pipe.receiveFd);
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal timeout because FD was no longer registered";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+    EXPECT_EQ(0, handler.callbackCount)
+            << "callback should not be invoked";
+}
+
+TEST_F(LooperTest, PollOnce_WhenCallbackReturnsFalse_CallbackShouldNotBeInvokedAgainLater) {
+    Pipe pipe;
+    StubCallbackHandler handler(false);
+
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    // First loop: Callback is registered and FD is signalled.
+    pipe.writeSignal();
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal zero because FD was already signalled";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked";
+
+    // Second loop: Callback is no longer registered and FD is signalled.
+    pipe.writeSignal();
+
+    stopWatch.reset();
+    result = mLooper->pollOnce(0);
+    elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal zero because timeout was zero";
+    EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+            << "pollOnce result should be ALOOPER_POLL_TIMEOUT";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should not be invoked this time";
+}
+
+TEST_F(LooperTest, PollOnce_WhenNonCallbackFdIsSignalled_ReturnsIdent) {
+    const int expectedIdent = 5;
+    void* expectedData = this;
+
+    Pipe pipe;
+
+    pipe.writeSignal();
+    mLooper->addFd(pipe.receiveFd, expectedIdent, ALOOPER_EVENT_INPUT, NULL, expectedData);
+
+    StopWatch stopWatch("pollOnce");
+    int fd;
+    int events;
+    void* data;
+    int result = mLooper->pollOnce(100, &fd, &events, &data);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_EQ(expectedIdent, result)
+            << "pollOnce result should be the ident of the FD that was signalled";
+    EXPECT_EQ(pipe.receiveFd, fd)
+            << "pollOnce should have returned the received pipe fd";
+    EXPECT_EQ(ALOOPER_EVENT_INPUT, events)
+            << "pollOnce should have returned ALOOPER_EVENT_INPUT as events";
+    EXPECT_EQ(expectedData, data)
+            << "pollOnce should have returned the data";
+}
+
+TEST_F(LooperTest, AddFd_WhenCallbackAdded_ReturnsOne) {
+    Pipe pipe;
+    int result = mLooper->addFd(pipe.receiveFd, 0, ALOOPER_EVENT_INPUT, NULL, NULL);
+
+    EXPECT_EQ(1, result)
+            << "addFd should return 1 because FD was added";
+}
+
+TEST_F(LooperTest, AddFd_WhenEventsIsZero_ReturnsError) {
+    Pipe pipe;
+    int result = mLooper->addFd(pipe.receiveFd, 0, 0, NULL, NULL);
+
+    EXPECT_EQ(-1, result)
+            << "addFd should return -1 because arguments were invalid";
+}
+
+TEST_F(LooperTest, AddFd_WhenIdentIsNegativeAndCallbackIsNull_ReturnsError) {
+    Pipe pipe;
+    int result = mLooper->addFd(pipe.receiveFd, -1, ALOOPER_EVENT_INPUT, NULL, NULL);
+
+    EXPECT_EQ(-1, result)
+            << "addFd should return -1 because arguments were invalid";
+}
+
+TEST_F(LooperTest, AddFd_WhenNoCallbackAndAllowNonCallbacksIsFalse_ReturnsError) {
+    Pipe pipe;
+    sp<Looper> looper = new Looper(false /*allowNonCallbacks*/);
+    int result = looper->addFd(pipe.receiveFd, 0, 0, NULL, NULL);
+
+    EXPECT_EQ(-1, result)
+            << "addFd should return -1 because arguments were invalid";
+}
+
+TEST_F(LooperTest, RemoveFd_WhenCallbackNotAdded_ReturnsZero) {
+    int result = mLooper->removeFd(1);
+
+    EXPECT_EQ(0, result)
+            << "removeFd should return 0 because FD not registered";
+}
+
+TEST_F(LooperTest, RemoveFd_WhenCallbackAddedThenRemovedTwice_ReturnsOnceFirstTimeAndReturnsZeroSecondTime) {
+    Pipe pipe;
+    StubCallbackHandler handler(false);
+    handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+
+    // First time.
+    int result = mLooper->removeFd(pipe.receiveFd);
+
+    EXPECT_EQ(1, result)
+            << "removeFd should return 1 first time because FD was registered";
+
+    // Second time.
+    result = mLooper->removeFd(pipe.receiveFd);
+
+    EXPECT_EQ(0, result)
+            << "removeFd should return 0 second time because FD was no longer registered";
+}
+
+TEST_F(LooperTest, PollOnce_WhenCallbackAddedTwice_OnlySecondCallbackShouldBeInvoked) {
+    Pipe pipe;
+    StubCallbackHandler handler1(true);
+    StubCallbackHandler handler2(true);
+
+    handler1.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
+    handler2.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); // replace it
+    pipe.writeSignal(); // would cause FD to be considered signalled
+
+    StopWatch stopWatch("pollOnce");
+    int result = mLooper->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_EQ(OK, pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. zero because FD was already signalled";
+    EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+            << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled";
+    EXPECT_EQ(0, handler1.callbackCount)
+            << "original handler callback should not be invoked because it was replaced";
+    EXPECT_EQ(1, handler2.callbackCount)
+            << "replacement handler callback should be invoked";
+}
+
+
+} // namespace android
diff --git a/libs/utils/tests/PollLoop_test.cpp b/libs/utils/tests/PollLoop_test.cpp
deleted file mode 100644
index 02f1808..0000000
--- a/libs/utils/tests/PollLoop_test.cpp
+++ /dev/null
@@ -1,370 +0,0 @@
-//
-// Copyright 2010 The Android Open Source Project
-//
-
-#include <utils/PollLoop.h>
-#include <utils/Timers.h>
-#include <utils/StopWatch.h>
-#include <gtest/gtest.h>
-#include <unistd.h>
-#include <time.h>
-
-#include "TestHelpers.h"
-
-// # of milliseconds to fudge stopwatch measurements
-#define TIMING_TOLERANCE_MS 25
-
-namespace android {
-
-class DelayedWake : public DelayedTask {
-    sp<PollLoop> mPollLoop;
-
-public:
-    DelayedWake(int delayMillis, const sp<PollLoop> pollLoop) :
-        DelayedTask(delayMillis), mPollLoop(pollLoop) {
-    }
-
-protected:
-    virtual void doTask() {
-        mPollLoop->wake();
-    }
-};
-
-class DelayedWriteSignal : public DelayedTask {
-    Pipe* mPipe;
-
-public:
-    DelayedWriteSignal(int delayMillis, Pipe* pipe) :
-        DelayedTask(delayMillis), mPipe(pipe) {
-    }
-
-protected:
-    virtual void doTask() {
-        mPipe->writeSignal();
-    }
-};
-
-class CallbackHandler {
-public:
-    void setCallback(const sp<PollLoop>& pollLoop, int fd, int events) {
-        pollLoop->setCallback(fd, events, staticHandler, this);
-    }
-
-protected:
-    virtual ~CallbackHandler() { }
-
-    virtual bool handler(int fd, int events) = 0;
-
-private:
-    static bool staticHandler(int fd, int events, void* data) {
-        return static_cast<CallbackHandler*>(data)->handler(fd, events);
-    }
-};
-
-class StubCallbackHandler : public CallbackHandler {
-public:
-    bool nextResult;
-    int callbackCount;
-
-    int fd;
-    int events;
-
-    StubCallbackHandler(bool nextResult) : nextResult(nextResult),
-            callbackCount(0), fd(-1), events(-1) {
-    }
-
-protected:
-    virtual bool handler(int fd, int events) {
-        callbackCount += 1;
-        this->fd = fd;
-        this->events = events;
-        return nextResult;
-    }
-};
-
-class PollLoopTest : public testing::Test {
-protected:
-    sp<PollLoop> mPollLoop;
-
-    virtual void SetUp() {
-        mPollLoop = new PollLoop(false);
-    }
-
-    virtual void TearDown() {
-        mPollLoop.clear();
-    }
-};
-
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNotAwoken_WaitsForTimeoutAndReturnsFalse) {
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal timeout";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenBeforeWaiting_ImmediatelyReturnsTrue) {
-    mPollLoop->wake();
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(1000);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. zero because wake() was called before waiting";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because loop was awoken";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenWhileWaiting_PromptlyReturnsTrue) {
-    sp<DelayedWake> delayedWake = new DelayedWake(100, mPollLoop);
-    delayedWake->run();
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(1000);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal wake delay";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because loop was awoken";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoRegisteredFDs_ImmediatelyReturnsFalse) {
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(0);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should be approx. zero";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoSignalledFDs_ImmediatelyReturnsFalse) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(0);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should be approx. zero";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-    EXPECT_EQ(0, handler.callbackCount)
-            << "callback should not have been invoked because FD was not signalled";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndSignalledFD_ImmediatelyInvokesCallbackAndReturnsTrue) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    ASSERT_EQ(OK, pipe.writeSignal());
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(0);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should be approx. zero";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should be invoked exactly once";
-    EXPECT_EQ(pipe.receiveFd, handler.fd)
-            << "callback should have received pipe fd as parameter";
-    EXPECT_EQ(POLL_IN, handler.events)
-            << "callback should have received POLL_IN as events";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNoSignalledFDs_WaitsForTimeoutAndReturnsFalse) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal timeout";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-    EXPECT_EQ(0, handler.callbackCount)
-            << "callback should not have been invoked because FD was not signalled";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDBeforeWaiting_ImmediatelyInvokesCallbackAndReturnsTrue) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    pipe.writeSignal();
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should be approx. zero";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should be invoked exactly once";
-    EXPECT_EQ(pipe.receiveFd, handler.fd)
-            << "callback should have received pipe fd as parameter";
-    EXPECT_EQ(POLL_IN, handler.events)
-            << "callback should have received POLL_IN as events";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDWhileWaiting_PromptlyInvokesCallbackAndReturnsTrue) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-    sp<DelayedWriteSignal> delayedWriteSignal = new DelayedWriteSignal(100, & pipe);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-    delayedWriteSignal->run();
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(1000);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal signal delay";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should be invoked exactly once";
-    EXPECT_EQ(pipe.receiveFd, handler.fd)
-            << "callback should have received pipe fd as parameter";
-    EXPECT_EQ(POLL_IN, handler.events)
-            << "callback should have received POLL_IN as events";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedThenRemoved_CallbackShouldNotBeInvoked) {
-    Pipe pipe;
-    StubCallbackHandler handler(true);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-    pipe.writeSignal(); // would cause FD to be considered signalled
-    mPollLoop->removeCallback(pipe.receiveFd);
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal timeout because FD was no longer registered";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-    EXPECT_EQ(0, handler.callbackCount)
-            << "callback should not be invoked";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenCallbackReturnsFalse_CallbackShouldNotBeInvokedAgainLater) {
-    Pipe pipe;
-    StubCallbackHandler handler(false);
-
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    // First loop: Callback is registered and FD is signalled.
-    pipe.writeSignal();
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(0);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal zero because FD was already signalled";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should be invoked";
-
-    // Second loop: Callback is no longer registered and FD is signalled.
-    pipe.writeSignal();
-
-    stopWatch.reset();
-    result = mPollLoop->pollOnce(0);
-    elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. equal zero because timeout was zero";
-    EXPECT_EQ(result, PollLoop::POLL_TIMEOUT)
-            << "pollOnce result should be POLL_TIMEOUT";
-    EXPECT_EQ(1, handler.callbackCount)
-            << "callback should not be invoked this time";
-}
-
-TEST_F(PollLoopTest, RemoveCallback_WhenCallbackNotAdded_ReturnsFalse) {
-    bool result = mPollLoop->removeCallback(1);
-
-    EXPECT_FALSE(result)
-            << "removeCallback should return false because FD not registered";
-}
-
-TEST_F(PollLoopTest, RemoveCallback_WhenCallbackAddedThenRemovedTwice_ReturnsTrueFirstTimeAndReturnsFalseSecondTime) {
-    Pipe pipe;
-    StubCallbackHandler handler(false);
-    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-
-    // First time.
-    bool result = mPollLoop->removeCallback(pipe.receiveFd);
-
-    EXPECT_TRUE(result)
-            << "removeCallback should return true first time because FD was registered";
-
-    // Second time.
-    result = mPollLoop->removeCallback(pipe.receiveFd);
-
-    EXPECT_FALSE(result)
-            << "removeCallback should return false second time because FD was no longer registered";
-}
-
-TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedTwice_OnlySecondCallbackShouldBeInvoked) {
-    Pipe pipe;
-    StubCallbackHandler handler1(true);
-    StubCallbackHandler handler2(true);
-
-    handler1.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
-    handler2.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); // replace it
-    pipe.writeSignal(); // would cause FD to be considered signalled
-
-    StopWatch stopWatch("pollOnce");
-    int32_t result = mPollLoop->pollOnce(100);
-    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
-
-    ASSERT_EQ(OK, pipe.readSignal())
-            << "signal should actually have been written";
-    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
-            << "elapsed time should approx. zero because FD was already signalled";
-    EXPECT_EQ(result, PollLoop::POLL_CALLBACK)
-            << "pollOnce result should be POLL_CALLBACK because FD was signalled";
-    EXPECT_EQ(0, handler1.callbackCount)
-            << "original handler callback should not be invoked because it was replaced";
-    EXPECT_EQ(1, handler2.callbackCount)
-            << "replacement handler callback should be invoked";
-}
-
-
-} // namespace android
diff --git a/location/java/android/location/ILocationProvider.aidl b/location/java/android/location/ILocationProvider.aidl
index 2b9782a..ecf6789 100644
--- a/location/java/android/location/ILocationProvider.aidl
+++ b/location/java/android/location/ILocationProvider.aidl
@@ -20,6 +20,7 @@
 import android.location.Location;
 import android.net.NetworkInfo;
 import android.os.Bundle;
+import android.os.WorkSource;
 
 /**
  * Binder interface for services that implement location providers.
@@ -43,7 +44,7 @@
     long getStatusUpdateTime();
     String getInternalState();
     void enableLocationTracking(boolean enable);
-    void setMinTime(long minTime);
+    void setMinTime(long minTime, in WorkSource ws);
     void updateNetworkState(int state, in NetworkInfo info);
     void updateLocation(in Location location);
     boolean sendExtraCommand(String command, inout Bundle extras);
diff --git a/location/java/android/location/provider/LocationProvider.java b/location/java/android/location/provider/LocationProvider.java
index cf939de..95b4425 100644
--- a/location/java/android/location/provider/LocationProvider.java
+++ b/location/java/android/location/provider/LocationProvider.java
@@ -26,6 +26,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.WorkSource;
 import android.util.Log;
 
 /**
@@ -106,8 +107,8 @@
             LocationProvider.this.onEnableLocationTracking(enable);
         }
 
-        public void setMinTime(long minTime) {
-            LocationProvider.this.onSetMinTime(minTime);
+        public void setMinTime(long minTime, WorkSource ws) {
+            LocationProvider.this.onSetMinTime(minTime, ws);
         }
 
         public void updateNetworkState(int state, NetworkInfo info) {
@@ -123,11 +124,11 @@
         }
 
         public void addListener(int uid) {
-            LocationProvider.this.onAddListener(uid);
+            LocationProvider.this.onAddListener(uid, new WorkSource(uid));
         }
 
         public void removeListener(int uid) {
-            LocationProvider.this.onRemoveListener(uid);
+            LocationProvider.this.onRemoveListener(uid, new WorkSource(uid));
         }
     };
 
@@ -303,8 +304,9 @@
      * the frequency of updates to match the requested frequency.
      *
      * @param minTime the smallest minTime value over all listeners for this provider.
+     * @param ws the source this work is coming from.
      */
-    public abstract void onSetMinTime(long minTime);
+    public abstract void onSetMinTime(long minTime, WorkSource ws);
 
     /**
      * Updates the network state for the given provider. This function must
@@ -340,14 +342,16 @@
      * Notifies the location provider when a new client is listening for locations.
      *
      * @param uid user ID of the new client.
+     * @param ws a WorkSource representation of the client.
      */
-    public abstract void onAddListener(int uid);
+    public abstract void onAddListener(int uid, WorkSource ws);
 
     /**
      * Notifies the location provider when a client is no longer listening for locations.
      *
      * @param uid user ID of the client no longer listening.
+     * @param ws a WorkSource representation of the client.
      */
-    public abstract void onRemoveListener(int uid);
+    public abstract void onRemoveListener(int uid, WorkSource ws);
 }
 
diff --git a/media/java/android/media/AudioEffect.java b/media/java/android/media/AudioEffect.java
index 35038fa..ae67114 100644
--- a/media/java/android/media/AudioEffect.java
+++ b/media/java/android/media/AudioEffect.java
@@ -16,12 +16,14 @@
 
 package android.media;
 
-import android.util.Log;
-import java.lang.ref.WeakReference;
-import java.io.IOException;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.Log;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.nio.ByteOrder;
 import java.nio.ByteBuffer;
 import java.util.UUID;
@@ -839,6 +841,128 @@
                 byte[] value);
     }
 
+
+    // -------------------------------------------------------------------------
+    // Audio Effect Control panel intents
+    // -------------------------------------------------------------------------
+
+    /**
+     *  This intent launches an audio effect control panel UI. The goal of this intent is to enable
+     *  separate implementations of music/media player applications and audio effect control
+     *  application or services. This will allow platform vendors to offer more advanced control
+     *  options for standard effects or control for platform specific effects.
+     *  <p>The intent carries a number of extras used by the player application to communicate
+     *  necessary pieces of information to the control panel application.
+     *  <p>The calling application must use the
+     *  {@link android.app.Activity#startActivityForResult(Intent, int)} method to launch the
+     *  control panel so that its package name is indicated and used by the control panel
+     *  application to keep track of changes for this particular application.
+     *  <p>The android.media.EXTRA_AUDIO_SESSION extra will indicate an audio session to which the
+     *  audio effects should be applied. If no audio session is specified, either one of the
+     *  follownig will happen:
+     *  - If an audio session was previously opened by the calling application with
+     *  {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} intent, the effect changes will
+     *  be applied to that session.
+     *  - If no audio session is opened, the changes will be stored in the package specific storage
+     *  area and applied whenever a new audio session is opened by this application.
+     *  <p>The android.media.EXTRA_CONTENT_TYPE extra will help the control panel application
+     *  customize both the UI layout and the default audio effect settings if none are already
+     *  stored for the calling application.
+     *  {@hide} pending API council approval
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL =
+        "android.media.action.DISPLAY_AUDIO_EFFECT_CONTROL_PANEL";
+
+    /**
+     *  This intent indicates to the effect control application or service that a new audio session
+     *  is opened and requires audio effects to be applied. This is different from
+     *  {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL} in that no UI should be displayed in
+     *  this case. Music player applications can broadcast this intent before starting playback
+     *  to make sure that any audio effect settings previously selected by the user are applied.
+     *  <p>The effect control application receiving this intent will look for previously stored
+     *  settings for the calling application, create all required audio effects and apply the
+     *  effect settings to the specified audio session.
+     *  <p>The calling package name is indicated by the {@link #EXTRA_PACKAGE_NAME} extra and the
+     *  audio session ID by the {@link #EXTRA_AUDIO_SESSION} extra. Both extras are mandatory.
+     *  <p>If no stored settings are found for the calling application, default settings for the
+     *  content type indicated by {@link #EXTRA_CONTENT_TYPE} will be applied. The default settings
+     *  for a given content type are platform specific.
+     *  {@hide} pending API council approval
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION =
+        "android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION";
+
+    /**
+     *  This intent indicates to the effect control application or service that an audio session
+     *  is closed and that effects should not be applied anymore.
+     *  <p>The effect control application receiving this intent will delete all effects on this
+     *  session and store current settings in package specific storage.
+     *  <p>The calling package name is indicated by the {@link #EXTRA_PACKAGE_NAME} extra and the
+     *  audio session ID by the {@link #EXTRA_AUDIO_SESSION} extra. Both extras are mandatory.
+     *  <p>It is good practice for applications to broadcast this intent when music playback stops
+     *  and/or when exiting to free system resources consumed by audio effect engines.
+     *  {@hide} pending API council approval
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION =
+        "android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION";
+
+    /**
+     * This extra indicates the ID of the audio session the effects should be applied to.
+     * <p>The extra value is of type int and is the audio session ID.
+     *  @see android.media.MediaPlayer#setAudioSessionId(int) for details on audio sessions.
+     *  {@hide} pending API council approval
+     */
+     public static final String EXTRA_AUDIO_SESSION = "android.media.extra.AUDIO_SESSION";
+
+    /**
+     * This extra indicates the package name of the calling application for
+     * {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
+     * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
+     * <p>The extra value is a string containing the full package name.
+     *  {@hide} pending API council approval
+     */
+    public static final String EXTRA_PACKAGE_NAME = "android.media.extra.PACKAGE_NAME";
+
+    /**
+     * This extra indicates which type of content is played by the application. This information is
+     * used by the effect control application to customize UI and default effect settings.
+     * The content type is one of the following:
+     * <ul>
+     *   <li>{@link #CONTENT_TYPE_MUSIC}</li>
+     *   <li>{@link #CONTENT_TYPE_MOVIE}</li>
+     *   <li>{@link #CONTENT_TYPE_GAME}</li>
+     *   <li>{@link #CONTENT_TYPE_VOICE}</li>
+     * </ul>
+     * If omitted, the content type defaults to {@link #CONTENT_TYPE_MUSIC}.
+     *  {@hide} pending API council approval
+     */
+    public static final String EXTRA_CONTENT_TYPE = "android.media.extra.CONTENT_TYPE";
+
+    /**
+     * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is music
+     *  {@hide} pending API council approval
+     */
+    public static final int  CONTENT_TYPE_MUSIC = 0;
+    /**
+     * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is video of movie
+     *  {@hide} pending API council approval
+     */
+    public static final int  CONTENT_TYPE_MOVIE = 1;
+    /**
+     * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is game audio
+     *  {@hide} pending API council approval
+     */
+    public static final int  CONTENT_TYPE_GAME = 2;
+    /**
+     * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is voice audio
+     *  {@hide} pending API council approval
+     */
+    public static final int  CONTENT_TYPE_VOICE = 3;
+
+
     // ---------------------------------------------------------
     // Inner classes
     // --------------------
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
index daa976f..3e9429d 100644
--- a/media/java/android/media/CamcorderProfile.java
+++ b/media/java/android/media/CamcorderProfile.java
@@ -199,6 +199,10 @@
      * 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
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index fb2480e..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 {
     
@@ -132,16 +141,6 @@
         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", MtpConstants.FORMAT_MP3);
         addFileType("M4A", FILE_TYPE_M4A, "audio/mp4", MtpConstants.FORMAT_MPEG);
@@ -176,10 +175,8 @@
         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", MtpConstants.FORMAT_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", MtpConstants.FORMAT_EXIF_JPEG);
         addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg", MtpConstants.FORMAT_EXIF_JPEG);
@@ -192,53 +189,74 @@
         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);
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index b8403e1..280def9 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1210,7 +1210,6 @@
      * effect which can be applied on any sound source that directs a certain amount of its
      * energy to this effect. This amount is defined by setAuxEffectSendLevel().
      * {@see #setAuxEffectSendLevel(float)}.
-     // TODO when AudioEffect is unhidden
      * <p>After creating an auxiliary effect (e.g. {@link android.media.EnvironmentalReverb}),
      * retrieve its ID with {@link android.media.AudioEffect#getId()} and use it when calling
      * this method to attach the player to the effect.
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 19b0c17..dd19450 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -277,80 +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);
+        }
     }
 
     /**
-     * Enables/Disables time lapse capture and sets its parameters. This method should
-     * be called after setProfile().
+     * 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 enableTimeLapse Pass true to enable time lapse capture, false to disable it.
-     * @param useStillCameraForTimeLapse Pass true to use still camera for capturing time lapse
-     * frames, false to use the video camera.
-     * @param timeBetweenTimeLapseFrameCaptureMs time between two captures of time lapse frames.
-     * @param encoderLevel the video encoder level.
+     * @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 setTimeLapseParameters(boolean enableTimeLapse,
-            boolean useStillCameraForTimeLapse,
-            int timeBetweenTimeLapseFrameCaptureMs, int encoderLevel) {
-        setParameter(String.format("time-lapse-enable=%d",
-                    (enableTimeLapse) ? 1 : 0));
+    public void setCaptureRate(double fps) {
+        double timeBetweenFrameCapture = 1 / fps;
+        int timeBetweenFrameCaptureMs = (int) (1000 * timeBetweenFrameCapture);
         setParameter(String.format("time-between-time-lapse-frame-capture=%d",
-                    timeBetweenTimeLapseFrameCaptureMs));
-        setVideoEncoderLevel(encoderLevel);
-    }
-
-    /**
-     * Enables time lapse capture and sets its parameters. This method should
-     * be called after setProfile().
-     *
-     * @param timeBetweenTimeLapseFrameCaptureMs time between two captures of time lapse frames.
-     * @hide
-     */
-    public void enableTimeLapse(int timeBetweenTimeLapseFrameCaptureMs) {
-        setParameter(String.format("time-lapse-enable=1"));
-        setParameter(String.format("time-between-time-lapse-frame-capture=%d",
-                    timeBetweenTimeLapseFrameCaptureMs));
-    }
-
-    /**
-     * Sets filename and parameters for auxiliary time lapse video.
-     *
-     * @param fd an open file descriptor to be written into.
-     * @param videoFrameWidth width of the auxiliary video.
-     * @param videoFrameHeight height of the auxiliary video.
-     * @param videoBitRate bit rate of the auxiliary video
-     * @hide
-     * */
-    public void setAuxVideoParameters(FileDescriptor fd,
-            int videoFrameWidth, int videoFrameHeight,
-            int videoBitRate) {
-        setAuxiliaryOutputFile(fd);
-        setParameter(String.format("video-aux-param-width=%d", videoFrameWidth));
-        setParameter(String.format("video-aux-param-height=%d", videoFrameHeight));
-        setParameter(String.format("video-aux-param-encoding-bitrate=%d", videoBitRate));
-    }
-
-    /**
-     * Sets filename and parameters for auxiliary time lapse video.
-     *
-     * @param path The pathname to use for the auxiliary video.
-     * @param videoFrameWidth width of the auxiliary video.
-     * @param videoFrameHeight height of the auxiliary video.
-     * @param videoBitRate bit rate of the auxiliary video
-     * @hide
-     * */
-    public void setAuxVideoParameters(String path,
-            int videoFrameWidth, int videoFrameHeight,
-            int videoBitRate) {
-        setAuxiliaryOutputFile(path);
-        setParameter(String.format("video-aux-param-width=%d", videoFrameWidth));
-        setParameter(String.format("video-aux-param-height=%d", videoFrameHeight));
-        setParameter(String.format("video-aux-param-encoding-bitrate=%d", videoBitRate));
+                    timeBetweenFrameCaptureMs));
     }
 
     /**
@@ -530,33 +487,84 @@
     }
 
     /**
-     * Pass in the file descriptor of the auxiliary file to be written. Call this after
-     * setOutputFormat() but before prepare().
+     * 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.
-     * @throws IllegalStateException if it is called before
-     * setOutputFormat() or after prepare()
      */
-    private void setAuxiliaryOutputFile(FileDescriptor fd) throws IllegalStateException
+    public void setAuxiliaryOutputFile(FileDescriptor fd)
     {
         mPrepareAuxiliaryFile = true;
         mPathAux = null;
         mFdAux = fd;
+        setAuxVideoParameters();
     }
 
     /**
-     * Sets the path of the auxiliary output file to be produced. Call this after
-     * setOutputFormat() but before prepare().
+     * 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.
-     * @throws IllegalStateException if it is called before
-     * setOutputFormat() or after prepare()
      */
-    private void setAuxiliaryOutputFile(String path) throws IllegalStateException
+    public void setAuxiliaryOutputFile(String path)
     {
         mPrepareAuxiliaryFile = true;
         mFdAux = null;
         mPathAux = path;
+        setAuxVideoParameters();
     }
 
     /**
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 8ca6237..b2a0a46 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,6 +292,7 @@
     private Uri mThumbsUri;
     private Uri mGenresUri;
     private Uri mPlaylistsUri;
+    private Uri mFilesUri;
     private boolean mProcessPlaylists, mProcessGenres;
     private int mMtpObjectHandle;
 
@@ -340,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;
         }
     }
 
@@ -432,6 +423,9 @@
             }
 
             mMimeType = null;
+            mFileType = 0;
+            mFileSize = fileSize;
+
             // try mimeType first, if it is specified
             if (mimeType != null) {
                 mFileType = MediaFile.getFileTypeForMimeType(mimeType);
@@ -439,7 +433,6 @@
                     mMimeType = mimeType;
                 }
             }
-            mFileSize = fileSize;
 
             // if mimeType was not specified, compute file type based on file extension.
             if (mMimeType == null) {
@@ -456,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;
@@ -501,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 {
@@ -516,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);
                     }
 
@@ -627,9 +633,6 @@
             map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
             map.put(MediaStore.MediaColumns.SIZE, mFileSize);
             map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
-            if (mMtpObjectHandle != 0) {
-                map.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
-            }
 
             if (MediaFile.isVideoFileType(mFileType)) {
                 map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaStore.UNKNOWN_STRING));
@@ -659,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) {
@@ -683,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);
@@ -720,7 +695,7 @@
                 }
             }
             long rowId = entry.mRowId;
-            if (isAudio && rowId == 0) {
+            if (MediaFile.isAudioFileType(mFileType) && rowId == 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
@@ -773,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) {
@@ -890,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;
@@ -906,21 +888,26 @@
             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 {
+                c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
+                        where, selectionArgs, null);
+
+                if (c != null) {
                     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);
+                        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);
 
                         // Only consider entries with absolute path names.
                         // This allows storing URIs in the database without the
@@ -930,114 +917,144 @@
                             if (mCaseInsensitivePaths) {
                                 key = path.toLowerCase();
                             }
+
+                            FileCacheEntry entry = new FileCacheEntry(mFilesUri, rowId, path,
+                                    lastModified, format);
+                            mFileCache.put(key, entry);
+                        }
+                    }
+                    c.close();
+                    c = null;
+                }
+            }
+
+            // Read existing files from the audio table and update FileCacheEntry
+            c = mMediaProvider.query(mAudioUri, MEDIA_PRESCAN_PROJECTION,
+                    where, selectionArgs, null);
+            if (c != null) {
+                while (c.moveToNext()) {
+                    long rowId = c.getLong(MEDIA_PRESCAN_ID_COLUMN_INDEX);
+                    String path = c.getString(MEDIA_PRESCAN_PATH_COLUMN_INDEX);
+                    long lastModified = c.getLong(MEDIA_PRESCAN_DATE_MODIFIED_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();
+                        }
+                        FileCacheEntry entry = mFileCache.get(path);
+                        if (entry == null) {
                             mFileCache.put(key, new FileCacheEntry(mAudioUri, rowId, path,
-                                    lastModified));
+                                    lastModified, 0));
+                        } else {
+                            // update the entry
+                            entry.mTableUri = mAudioUri;
+                            entry.mRowId = rowId;
                         }
                     }
-                } finally {
-                    c.close();
-                    c = null;
                 }
+                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);
-
+            // Read existing files from the video table and update FileCacheEntry
+            c = mMediaProvider.query(mVideoUri, MEDIA_PRESCAN_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);
+                while (c.moveToNext()) {
+                    long rowId = c.getLong(MEDIA_PRESCAN_ID_COLUMN_INDEX);
+                    String path = c.getString(MEDIA_PRESCAN_PATH_COLUMN_INDEX);
+                    long lastModified = c.getLong(MEDIA_PRESCAN_DATE_MODIFIED_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();
-                            }
+                    // 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 = mFileCache.get(path);
+                        if (entry == null) {
                             mFileCache.put(key, new FileCacheEntry(mVideoUri, rowId, path,
-                                    lastModified));
+                                    lastModified, 0));
+                        } else {
+                            // update the entry
+                            entry.mTableUri = mVideoUri;
+                            entry.mRowId = rowId;
                         }
                     }
-                } finally {
-                    c.close();
-                    c = null;
                 }
+                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);
-
+            // Read existing files from the video table and update FileCacheEntry
+            c = mMediaProvider.query(mImagesUri, MEDIA_PRESCAN_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);
+                while (c.moveToNext()) {
+                    long rowId = c.getLong(MEDIA_PRESCAN_ID_COLUMN_INDEX);
+                    String path = c.getString(MEDIA_PRESCAN_PATH_COLUMN_INDEX);
+                    long lastModified = c.getLong(MEDIA_PRESCAN_DATE_MODIFIED_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));
-                       }
+                    // 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 = mFileCache.get(path);
+                        if (entry == null) {
+                            mFileCache.put(key, new FileCacheEntry(mImagesUri, rowId, path,
+                                    lastModified, 0));
+                        } else {
+                            // update the entry
+                            entry.mTableUri = mImagesUri;
+                            entry.mRowId = rowId;
+                        }
                     }
-                } finally {
-                    c.close();
-                    c = null;
                 }
+                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);
-
+                // Read existing files from the playlists table and update FileCacheEntry
+                c = mMediaProvider.query(mPlaylistsUri, MEDIA_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(MEDIA_PRESCAN_ID_COLUMN_INDEX);
+                        String path = c.getString(MEDIA_PRESCAN_PATH_COLUMN_INDEX);
+                        long lastModified = c.getLong(MEDIA_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();
-                                }
+                        // 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 = mFileCache.get(path);
+                            if (entry == null) {
                                 mFileCache.put(key, new FileCacheEntry(mPlaylistsUri, rowId, path,
-                                        lastModified));
+                                        lastModified, 0));
+                            } else {
+                                // update the entry
+                                entry.mTableUri = mPlaylistsUri;
+                                entry.mRowId = rowId;
                             }
                         }
-                    } finally {
-                        c.close();
-                        c = null;
                     }
+                    c.close();
+                    c = null;
                 }
             }
         }
@@ -1112,12 +1129,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()) {
@@ -1137,9 +1156,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();
                 }
             }
@@ -1167,6 +1188,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
@@ -1189,11 +1211,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);
@@ -1220,7 +1242,7 @@
     public Uri scanSingleFile(String path, String volumeName, String mimeType) {
         try {
             initialize(volumeName);
-            prescan(path);
+            prescan(path, true);
 
             File file = new File(path);
 
@@ -1235,12 +1257,34 @@
         }
     }
 
-    public Uri scanMtpFile(String path, String volumeName, int objectHandle, int format) {
-        String mimeType = MediaFile.getMimeTypeForFormatCode(format);
+    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;
-        Uri result = scanSingleFile(path, volumeName, mimeType);
-        mMtpObjectHandle = 0;
-        return result;
+        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
@@ -1522,7 +1566,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/MtpConstants.java b/media/java/android/media/MtpConstants.java
index 153f64f..a7d33ce 100644
--- a/media/java/android/media/MtpConstants.java
+++ b/media/java/android/media/MtpConstants.java
@@ -138,6 +138,28 @@
     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;
diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java
index ad029a6..b64299a 100644
--- a/media/java/android/media/MtpDatabase.java
+++ b/media/java/android/media/MtpDatabase.java
@@ -170,7 +170,7 @@
                     Log.e(TAG, "RemoteException in endSendObject", e);
                 }
             } else {
-                Uri uri = mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
+                mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
             }
         } else {
             deleteFile(handle);
@@ -478,12 +478,26 @@
         }
     }
 
+    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;
-        Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
         try {
-            if (mMediaProvider.delete(uri, null, null) == 1) {
+            if (deleteRecursive(handle) > 0) {
                 return MtpConstants.RESPONSE_OK;
             } else {
                 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
diff --git a/media/java/android/media/videoeditor/AudioTrack.java b/media/java/android/media/videoeditor/AudioTrack.java
new file mode 100755
index 0000000..9d40a78
--- /dev/null
+++ b/media/java/android/media/videoeditor/AudioTrack.java
@@ -0,0 +1,326 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

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

+

+/**

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

+ * audio samples of the MediaItems.

+ * {@hide}

+ */

+public class AudioTrack {

+    // Instance variables

+    private final String mUniqueId;

+    private final String mFilename;

+    private final long mDurationMs;

+    private long mStartTimeMs;

+    private long mTimelineDurationMs;

+    private int mVolumePercent;

+    private long mBeginBoundaryTimeMs;

+    private long mEndBoundaryTimeMs;

+    private boolean mLoop;

+

+    // Ducking variables

+    private int mDuckingThreshold;

+    private int mDuckingLowVolume;

+    private boolean mIsDuckingEnabled;

+

+    // The audio waveform filename

+    private String mAudioWaveformFilename;

+

+    /**

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

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private AudioTrack() throws IOException {

+        this(null, null);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param audioTrackId The AudioTrack 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(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;

+        mTimelineDurationMs = mDurationMs;

+        mVolumePercent = 100;

+

+        // Play the entire audio track

+        mBeginBoundaryTimeMs = 0;

+        mEndBoundaryTimeMs = mDurationMs;

+

+        // By default loop is disabled

+        mLoop = false;

+

+        // Ducking is enabled by default

+        mDuckingThreshold = 0;

+        mDuckingLowVolume = 0;

+        mIsDuckingEnabled = true;

+

+        // The audio waveform file is generated later

+        mAudioWaveformFilename = null;

+    }

+

+    /**

+     * @return The id of the media item

+     */

+    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;

+    }

+

+    /**

+     * 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;

+    }

+

+    /**

+     * 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;

+    }

+

+    /**

+     * 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..177b863
--- /dev/null
+++ b/media/java/android/media/videoeditor/Effect.java
@@ -0,0 +1,119 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

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

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

+ * limitations under the License.

+ */

+

+package android.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;

+    protected long mDurationMs;

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

+    protected long mStartTimeMs;

+

+    /**

+     * Default constructor

+     */

+    @SuppressWarnings("unused")

+    private Effect() {

+        mUniqueId = null;

+        mStartTimeMs = 0;

+        mDurationMs = 0;

+    }

+

+    /**

+     * Constructor

+     *

+     * @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(String effectId, long startTimeMs, long durationMs) {

+        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) {

+        mDurationMs = durationMs;

+    }

+

+    /**

+     * 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 begining

+     *            of the media item in milliseconds

+     */

+    public void setStartTime(long startTimeMs) {

+        mStartTimeMs = startTimeMs;

+    }

+

+    /**

+     * @return The start time in milliseconds

+     */

+    public long getStartTime() {

+        return mStartTimeMs;

+    }

+

+    /*

+     * {@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..7c61627
--- /dev/null
+++ b/media/java/android/media/videoeditor/EffectColor.java
@@ -0,0 +1,100 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

+ * WITHOUT WARRANTIES OR 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, 0, 0, 0, 0);

+    }

+

+    /**

+     * Constructor

+     *

+     * @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(String effectId, long startTimeMs, long durationMs,

+            int type, int param) {

+        super(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..c6d22f4
--- /dev/null
+++ b/media/java/android/media/videoeditor/EffectKenBurns.java
@@ -0,0 +1,90 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

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

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

+ * limitations under the License.

+ */

+

+package android.media.videoeditor;

+

+import java.io.IOException;

+

+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() throws IOException {

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

+    }

+

+    /**

+     * Constructor

+     *

+     * @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(String effectId, Rect startRect, Rect endRect, long startTime,

+            long durationMs)

+            throws IOException {

+        super(effectId, startTime, 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..db7585a
--- /dev/null
+++ b/media/java/android/media/videoeditor/MediaImageItem.java
@@ -0,0 +1,227 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

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

+

+/**

+ * This class represents an image item on the storyboard.

+ * {@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;

+

+    /**

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

+     */

+    @SuppressWarnings("unused")

+    private MediaImageItem() throws IOException {

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

+    }

+

+    /**

+     * Constructor

+     *

+     * @param mediaItemId The MediaItem 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(String mediaItemId, String filename, long durationMs, int renderingMode)

+            throws IOException {

+        super(mediaItemId, filename, renderingMode);

+

+        // Determine the size 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;

+    }

+

+    /*

+     * {@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;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public int getAspectRatio() {

+        return mAspectRatio;

+    }

+

+    /**

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

+     */

+    public void setDuration(long durationMs) {

+        mDurationMs = durationMs;

+        // TODO: Validate/modify the start and the end time of effects and overlays

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public long getDuration() {

+        return mDurationMs;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public long getTimelineDuration() {

+        return mDurationMs;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

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

+        return generateImageThumbnail(mFilename, width, height);

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

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

+            int thumbnailCount) throws IOException {

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

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

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

+            thumbnailArray[i] = thumbnail;

+        }

+        return thumbnailArray;

+    }

+

+    /**

+     * Resize a bitmap within an input stream

+     *

+     * @param filename The filename

+     * @param width The thumbnail width

+     * @param height The thumbnail height

+     *

+     * @return The resized bitmap

+     */

+    private Bitmap generateImageThumbnail(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..9e32744
--- /dev/null
+++ b/media/java/android/media/videoeditor/MediaItem.java
@@ -0,0 +1,434 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

+ * WITHOUT WARRANTIES OR 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

+    private Transition mBeginTransition;

+    private Transition mEndTransition;

+

+    /**

+     * Constructor

+     *

+     * @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(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 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;

+    }

+

+    /**

+     * @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 duration of the media item

+     */

+    public abstract long getDuration();

+

+    /**

+     * @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 (mEffects.contains(effect)) {

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

+        }

+

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

+            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.

+     */

+    public void addOverlay(Overlay overlay) {

+        if (mOverlays.contains(overlay)) {

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

+        }

+

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

+            throw new IllegalArgumentException(

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

+        }

+

+        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);

+                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

+     */

+    private 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() > getDuration()

+                    - mEndTransition.getDuration()) {

+                mEndTransition.invalidate();

+            }

+        }

+    }

+

+    /**

+     * Invalidate the start and end transitions if necessary

+     *

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

+     */

+    private 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() > getDuration()

+                    - mEndTransition.getDuration()) {

+                mEndTransition.invalidate();

+            }

+        }

+    }

+}

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

+

+    // 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..87e9a22
--- /dev/null
+++ b/media/java/android/media/videoeditor/MediaVideoItem.java
@@ -0,0 +1,541 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

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

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

+ * limitations under the License.

+ */

+

+package android.media.videoeditor;

+

+import java.io.IOException;

+

+import android.graphics.Bitmap;

+import android.media.MediaRecorder;

+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 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, 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, RENDERING_MODE_BLACK_BORDER);

+    }

+

+    /**

+     * Constructor

+     *

+     * @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(String mediaItemId, String filename, int renderingMode)

+        throws IOException {

+        this(mediaItemId, filename, renderingMode, null);

+    }

+

+    /**

+     * Constructor

+     *

+     * @param mediaItemId The MediaItem id

+     * @param filename The image file name

+     * @param renderingMode The rendering mode

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

+     *

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

+     */

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

+            String audioWaveformFilename)  throws IOException {

+        super(mediaItemId, filename, renderingMode);

+        // TODO: Set these variables correctly

+        mWidth = 0;

+        mHeight = 0;

+        mAspectRatio = MediaProperties.ASPECT_RATIO_3_2;

+        mFileType = MediaProperties.FILE_MP4;

+        mVideoType = MediaRecorder.VideoEncoder.H264;

+        // 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 = 0;

+        mEndBoundaryTimeMs = mDurationMs;

+        mVolumePercentage = 100;

+        mAudioWaveformFilename = audioWaveformFilename;

+    }

+

+    /**

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

+     *

+     * @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");

+        }

+

+        mBeginBoundaryTimeMs = beginMs;

+        mEndBoundaryTimeMs = endMs;

+        // TODO: Validate/modify the start and the end time of effects and overlays

+    }

+

+    /**

+     * @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;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    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;

+    }

+

+    /**

+     * @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..5065636
--- /dev/null
+++ b/media/java/android/media/videoeditor/Overlay.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.media.videoeditor;

+

+

+/**

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

+ * {@hide}

+ */

+public abstract class Overlay {

+    // Instance variables

+    private final String mUniqueId;

+

+    protected long mStartTimeMs;

+    protected long mDurationMs;

+

+    /**

+     * Default constructor

+     */

+    @SuppressWarnings("unused")

+    private Overlay() {

+        mUniqueId = null;

+        mStartTimeMs = 0;

+        mDurationMs = 0;

+    }

+

+    /**

+     * Constructor

+     *

+     * @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(String overlayId, long startTimeMs, long durationMs) {

+        mUniqueId = overlayId;

+        mStartTimeMs = startTimeMs;

+        mDurationMs = durationMs;

+    }

+

+    /**

+     * @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) {

+        mDurationMs = durationMs;

+    }

+

+    /**

+     * @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) {

+        mStartTimeMs = startTimeMs;

+    }

+

+    /*

+     * {@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..e5d9b81
--- /dev/null
+++ b/media/java/android/media/videoeditor/OverlayFrame.java
@@ -0,0 +1,62 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

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

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

+ * limitations under the License.

+ */

+

+package android.media.videoeditor;

+

+

+/**

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

+ * does not manage deletion of the overlay file so application may use

+ * {@link #getFilename()} for this purpose.

+ * {@hide}

+ */

+public class OverlayFrame extends Overlay {

+    // Instance variables

+    private final String mFilename;

+

+    /**

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

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private OverlayFrame() {

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

+    }

+

+    /**

+     * Constructor for an OverlayFrame

+     *

+     * @param overlayId The overlay id

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

+     *            supported.

+     * @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(String overlayId, String filename, long startTimeMs, long durationMs) {

+        super(overlayId, startTimeMs, durationMs);

+        mFilename = filename;

+    }

+

+    /**

+     * Get the file name of this overlay

+     */

+    public String getFilename() {

+        return mFilename;

+    }

+}

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

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

+ * WITHOUT WARRANTIES OR 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

+    /** 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;

+

+    // 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) {

+        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) {

+        mDurationMs = durationMs;

+    }

+

+    /**

+     * @return the duration of the transition in milliseconds

+     */

+    public long getDuration() {

+        return mDurationMs;

+    }

+

+    /**

+     * @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..0a4a12f
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionAlpha.java
@@ -0,0 +1,112 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

+ * WITHOUT WARRANTIES OR 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

+     * @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/TransitionAtEnd.java b/media/java/android/media/videoeditor/TransitionAtEnd.java
new file mode 100755
index 0000000..7765bd4
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionAtEnd.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.media.videoeditor;

+

+

+/**

+ * TransitionAtEnd is a class useful to manage a predefined transition at the

+ * end of the movie.

+ * {@hide}

+ */

+public class TransitionAtEnd extends Transition {

+    /**

+     * 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.

+     */

+    public static final int TYPE_FADE_TO_BLACK = 0;

+

+    /**

+     * 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.

+     */

+    public static final int TYPE_CURTAIN_CLOSING = 1;

+

+    // The transition type

+    private final int mType;

+

+    /**

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

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionAtEnd() {

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

+    }

+

+    /**

+     * 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 type type of the transition to apply.

+     */

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

+            int type) {

+        super(transitionId, afterMediaItem, null, duration, Transition.BEHAVIOR_LINEAR);

+        mType = type;

+    }

+

+    /**

+     * Get the type of this transition

+     *

+     * @return The type of the transition

+     */

+    public int getType() {

+        return mType;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/TransitionAtStart.java b/media/java/android/media/videoeditor/TransitionAtStart.java
new file mode 100755
index 0000000..65ebd01
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionAtStart.java
@@ -0,0 +1,90 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

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

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

+ * limitations under the License.

+ */

+

+package android.media.videoeditor;

+

+

+/**

+ * TransitionAtStart is a class useful to manage a predefined transition at the

+ * beginning of the movie.

+ * {@hide}

+ */

+public class TransitionAtStart extends Transition {

+    /**

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

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

+     * movie.

+     */

+    public static final int TYPE_FADE_FROM_BLACK = 0;

+

+    /**

+     * 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.

+     */

+    public static final int TYPE_CURTAIN_OPENING = 1;

+

+    // The transition type

+    private final int mType;

+

+    /**

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

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionAtStart() {

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

+    }

+

+    /**

+     * 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 type The type of the transition to apply.

+     */

+    public TransitionAtStart(String transitionId, MediaItem beforeMediaItem, long durationMs,

+            int type) {

+        super(transitionId, null, beforeMediaItem, durationMs,

+                Transition.BEHAVIOR_LINEAR);

+        mType = type;

+    }

+

+    /**

+     * Get the type of this transition

+     *

+     * @return The type of the transition

+     */

+    public int getType() {

+        return mType;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    public void generate() {

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    void invalidate() {

+    }

+}

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/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..cc9f4b28
--- /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/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..2c56fc2
--- /dev/null
+++ b/media/java/android/media/videoeditor/VideoEditorFactory.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 android.media.videoeditor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+
+/**
+ * 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 {
+    /**
+     * 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.
+     *
+     * @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
+     */
+    public static VideoEditor create(String projectPath) throws IOException {
+        // 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);
+            }
+        }
+        return new VideoEditorTestImpl(projectPath);
+    }
+
+    /**
+     * 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 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, boolean generatePreview) throws IOException {
+        final VideoEditorTestImpl videoEditor = new VideoEditorTestImpl(projectPath);
+        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..a23c5c6
--- /dev/null
+++ b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
@@ -0,0 +1,777 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+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 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_BEGIN_TIME = "start_time";
+    private static final String ATTR_END_TIME = "end_time";
+    private static final String ATTR_VOLUME = "volume";
+
+    private static 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;
+        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());
+        }
+
+        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) {
+                final MediaItem mi = mMediaItems.get(0);
+                // Invalidate the transition at the beginning of the timeline
+                removeTransitionBefore(mi);
+            }
+            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(mi);
+                    // 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) {
+                final MediaItem mi = mMediaItems.get(0);
+                // Invalidate adjacent transitions at the insertion point
+                removeTransitionBefore(mi);
+                // 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(mi);
+                    // 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) {
+        // If a transition already exists at the specified position then
+        // invalidate it.
+        final Iterator<Transition> it = mTransitions.iterator();
+        while (it.hasNext()) {
+            final Transition t = it.next();
+            if (t.getAfterMediaItem() == transition.getAfterMediaItem()
+                    || t.getBeforeMediaItem() == transition.getBeforeMediaItem()) {
+                it.remove();
+                t.invalidate();
+                break;
+            }
+        }
+
+        mTransitions.add(transition);
+
+        // Cross reference the transitions
+        final MediaItem afterMediaItem = transition.getAfterMediaItem();
+        if (afterMediaItem != null) {
+            afterMediaItem.setEndTransition(transition);
+        }
+        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
+        if (beforeMediaItem != null) {
+            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) {
+            mTransitions.remove(transition);
+            transition.invalidate();
+            computeTimelineDuration();
+        }
+
+        // Cross reference the transitions
+        final MediaItem afterMediaItem = transition.getAfterMediaItem();
+        if (afterMediaItem != null) {
+            afterMediaItem.setEndTransition(null);
+        }
+        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
+        if (beforeMediaItem != null) {
+            beforeMediaItem.setBeginTransition(null);
+        }
+
+        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) {
+        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 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()));
+                if (mvi.getAudioWaveformFilename() != null) {
+                    serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME, mvi.getAudioWaveformFilename());
+                }
+            } else if (mediaItem instanceof MediaImageItem) {
+                serializer.attribute("", ATTR_DURATION, Long.toString(mediaItem.getDuration()));
+            }
+            serializer.endTag("", TAG_MEDIA_ITEM);
+        }
+        serializer.endTag("", TAG_MEDIA_ITEMS);
+
+        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;
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            switch (eventType) {
+                case XmlPullParser.START_TAG: {
+                    name = parser.getName();
+                    if (name.equals(TAG_PROJECT)) {
+                        mAspectRatio = Integer.parseInt(parser.getAttributeValue("",
+                                ATTR_ASPECT_RATIO));
+                    } else if (name.equals(TAG_MEDIA_ITEM)) {
+                        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));
+                        final MediaItem mediaItem;
+                        if (MediaImageItem.class.getSimpleName().equals(type)) {
+                            final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
+                            mediaItem = new MediaImageItem(mediaItemId, filename, durationMs,
+                                    renderingMode);
+                        }  else if (MediaVideoItem.class.getSimpleName().equals(type)) {
+                            final String audioWaveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
+                            mediaItem = new MediaVideoItem(mediaItemId, filename, renderingMode, audioWaveformFilename);
+
+                            final long beginTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
+                            final long endTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
+                            ((MediaVideoItem)mediaItem).setExtractBoundaries(beginTimeMs, endTimeMs);
+
+                            final int volumePercent = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
+                            ((MediaVideoItem)mediaItem).setVolume(volumePercent);
+                        } else {
+                            Log.e(TAG, "Unknown media item type: " + type);
+                            mediaItem = null;
+                        }
+                        mMediaItems.add(mediaItem);
+                    }
+                    break;
+                }
+
+                default: {
+                    break;
+                }
+            }
+            eventType = parser.next();
+        }
+
+        computeTimelineDuration();
+    }
+
+    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 TransitionAtStart) && !(transition instanceof TransitionAtEnd)) {
+                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 mediaItem The media item
+     */
+    private void removeTransitionBefore(MediaItem mediaItem) {
+        final Iterator<Transition> it = mTransitions.iterator();
+        while (it.hasNext()) {
+            Transition t = it.next();
+            if (t.getBeforeMediaItem() == mediaItem) {
+                it.remove();
+                t.invalidate();
+                mediaItem.setBeginTransition(null);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Remove the transition after this media item
+     *
+     * @param mediaItem The media item
+     */
+    private void removeTransitionAfter(MediaItem mediaItem) {
+        final Iterator<Transition> it = mTransitions.iterator();
+        while (it.hasNext()) {
+            Transition t = it.next();
+            if (t.getAfterMediaItem() == mediaItem) {
+                it.remove();
+                t.invalidate();
+                mediaItem.setEndTransition(null);
+                break;
+            }
+        }
+    }
+}
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/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
index e86ed99..8f40130 100644
--- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
+++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
@@ -65,6 +65,7 @@
 int LvmInitFlag = LVM_FALSE;
 int LvmSessionsActive = 0;
 SessionContext GlobalSessionMemory[LVM_MAX_SESSIONS];
+
 int SessionIndex[LVM_MAX_SESSIONS];
 
 // NXP SW BassBoost UUID
@@ -144,6 +145,7 @@
                                 void          *pParam,
                                 size_t        *pValueSize,
                                 void          *pValue);
+int Effect_setEnabled(EffectContext *pContext, bool enabled);
 
 /* Effect Library Interface Implementation */
 extern "C" int EffectQueryNumberEffects(uint32_t *pNumEffects){
@@ -199,11 +201,6 @@
         return -EINVAL;
     }
 
-    if(sessionId < 0){
-        LOGV("\tLVM_ERROR : EffectCreate sessionId is less than 0");
-        return -EINVAL;
-    }
-
     if(LvmInitFlag == LVM_FALSE){
         LvmInitFlag = LVM_TRUE;
         LOGV("\tEffectCreate - Initializing all global memory");
@@ -214,7 +211,7 @@
 
     // Find next available sessionNo
     for(i=0; i<LVM_MAX_SESSIONS; i++){
-        if((SessionIndex[i] == -1)||(SessionIndex[i] == sessionId)){
+        if((SessionIndex[i] == LVM_UNUSED_SESSION)||(SessionIndex[i] == sessionId)){
             sessionNo       = i;
             SessionIndex[i] = sessionId;
             LOGV("\tEffectCreate: Allocating SessionNo %d for SessionId %d\n", sessionNo,sessionId);
@@ -307,32 +304,34 @@
     }
     LOGV("\tEffectCreate - pBundledContext is %p", pContext->pBundledContext);
 
+    SessionContext *pSessionContext = &GlobalSessionMemory[pContext->pBundledContext->SessionNo];
+
     // Create each Effect
     if (memcmp(uuid, &gBassBoostDescriptor.uuid, sizeof(effect_uuid_t)) == 0){
         // Create Bass Boost
         LOGV("\tEffectCreate - Effect to be created is LVM_BASS_BOOST");
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].bBassInstantiated = LVM_TRUE;
+        pSessionContext->bBassInstantiated = LVM_TRUE;
 
         pContext->itfe       = &gLvmEffectInterface;
         pContext->EffectType = LVM_BASS_BOOST;
     } else if (memcmp(uuid, &gVirtualizerDescriptor.uuid, sizeof(effect_uuid_t)) == 0){
         // Create Virtualizer
         LOGV("\tEffectCreate - Effect to be created is LVM_VIRTUALIZER");
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].bVirtualizerInstantiated=LVM_TRUE;
+        pSessionContext->bVirtualizerInstantiated=LVM_TRUE;
 
         pContext->itfe       = &gLvmEffectInterface;
         pContext->EffectType = LVM_VIRTUALIZER;
     } else if (memcmp(uuid, &gEqualizerDescriptor.uuid, sizeof(effect_uuid_t)) == 0){
         // Create Equalizer
         LOGV("\tEffectCreate - Effect to be created is LVM_EQUALIZER");
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].bEqualizerInstantiated = LVM_TRUE;
+        pSessionContext->bEqualizerInstantiated = LVM_TRUE;
 
         pContext->itfe       = &gLvmEffectInterface;
         pContext->EffectType = LVM_EQUALIZER;
     } else if (memcmp(uuid, &gVolumeDescriptor.uuid, sizeof(effect_uuid_t)) == 0){
         // Create Volume
         LOGV("\tEffectCreate - Effect to be created is LVM_VOLUME");
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].bVolumeInstantiated = LVM_TRUE;
+        pSessionContext->bVolumeInstantiated = LVM_TRUE;
 
         pContext->itfe       = &gLvmEffectInterface;
         pContext->EffectType = LVM_VOLUME;
@@ -357,29 +356,33 @@
         return -EINVAL;
     }
 
+
+    Effect_setEnabled(pContext, LVM_FALSE);
+
+    SessionContext *pSessionContext = &GlobalSessionMemory[pContext->pBundledContext->SessionNo];
+
     // Clear the instantiated flag for the effect
     if(pContext->EffectType == LVM_BASS_BOOST) {
         LOGV("\tEffectRelease LVM_BASS_BOOST Clearing global intstantiated flag");
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].bBassInstantiated = LVM_FALSE;
+        pSessionContext->bBassInstantiated = LVM_FALSE;
     } else if(pContext->EffectType == LVM_VIRTUALIZER) {
         LOGV("\tEffectRelease LVM_VIRTUALIZER Clearing global intstantiated flag");
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].bVirtualizerInstantiated
-            = LVM_FALSE;
+        pSessionContext->bVirtualizerInstantiated = LVM_FALSE;
     } else if(pContext->EffectType == LVM_EQUALIZER) {
         LOGV("\tEffectRelease LVM_EQUALIZER Clearing global intstantiated flag");
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].bEqualizerInstantiated =LVM_FALSE;
+        pSessionContext->bEqualizerInstantiated =LVM_FALSE;
     } else if(pContext->EffectType == LVM_VOLUME) {
         LOGV("\tEffectRelease LVM_VOLUME Clearing global intstantiated flag");
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].bVolumeInstantiated = LVM_FALSE;
+        pSessionContext->bVolumeInstantiated = LVM_FALSE;
     } else {
         LOGV("\tLVM_ERROR : EffectRelease : Unsupported effect\n\n\n\n\n\n\n");
     }
 
     // if all effects are no longer instantiaed free the lvm memory and delete BundledEffectContext
-    if((GlobalSessionMemory[pContext->pBundledContext->SessionNo].bBassInstantiated == LVM_FALSE)&&
-    (GlobalSessionMemory[pContext->pBundledContext->SessionNo].bVolumeInstantiated == LVM_FALSE)&&
-    (GlobalSessionMemory[pContext->pBundledContext->SessionNo].bEqualizerInstantiated ==LVM_FALSE)&&
-    (GlobalSessionMemory[pContext->pBundledContext->SessionNo].bVirtualizerInstantiated==LVM_FALSE))
+    if ((pSessionContext->bBassInstantiated == LVM_FALSE) &&
+            (pSessionContext->bVolumeInstantiated == LVM_FALSE) &&
+            (pSessionContext->bEqualizerInstantiated ==LVM_FALSE) &&
+            (pSessionContext->bVirtualizerInstantiated==LVM_FALSE))
     {
         #ifdef LVM_PCM
         if (pContext->pBundledContext->PcmInPtr != NULL) {
@@ -398,7 +401,7 @@
         // Clear the SessionIndex
         for(int i=0; i<LVM_MAX_SESSIONS; i++){
             if(SessionIndex[i] == pContext->pBundledContext->SessionId){
-                SessionIndex[i] = -1;
+                SessionIndex[i] = LVM_UNUSED_SESSION;
                 LOGV("\tEffectRelease: Clearing SessionIndex SessionNo %d for SessionId %d\n",
                         i, pContext->pBundledContext->SessionId);
                 break;
@@ -406,8 +409,8 @@
         }
 
         LOGV("\tEffectRelease: All effects are no longer instantiated\n");
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].bBundledEffectsEnabled =LVM_FALSE;
-        GlobalSessionMemory[pContext->pBundledContext->SessionNo].pBundledContext = LVM_NULL;
+        pSessionContext->bBundledEffectsEnabled =LVM_FALSE;
+        pSessionContext->pBundledContext = LVM_NULL;
         LOGV("\tEffectRelease: Freeing LVM Bundle memory\n");
         LvmEffect_free(pContext);
         LOGV("\tEffectRelease: Deleting LVM Bundle context %p\n", pContext->pBundledContext);
@@ -432,7 +435,7 @@
         GlobalSessionMemory[i].bVirtualizerInstantiated = LVM_FALSE;
         GlobalSessionMemory[i].pBundledContext          = LVM_NULL;
 
-        SessionIndex[i] = -1;
+        SessionIndex[i] = LVM_UNUSED_SESSION;
     }
     return;
 }
@@ -2381,6 +2384,107 @@
     return db_fix;
 }
 
+//----------------------------------------------------------------------------
+// Effect_setEnabled()
+//----------------------------------------------------------------------------
+// Purpose:
+// Enable or disable effect
+//
+// Inputs:
+//  pContext      - pointer to effect context
+//  enabled       - true if enabling the effect, false otherwise
+//
+// Outputs:
+//
+//----------------------------------------------------------------------------
+
+int Effect_setEnabled(EffectContext *pContext, bool enabled)
+{
+    LOGV("\tEffect_setEnabled() type %d, enabled %d", pContext->EffectType, enabled);
+
+    if (enabled) {
+        switch (pContext->EffectType) {
+            case LVM_BASS_BOOST:
+                if (pContext->pBundledContext->bBassEnabled == LVM_TRUE) {
+                     LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_BASS_BOOST is already enabled");
+                     return -EINVAL;
+                }
+                pContext->pBundledContext->SamplesToExitCountBb =
+                     (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1);
+                pContext->pBundledContext->bBassEnabled = LVM_TRUE;
+                break;
+            case LVM_EQUALIZER:
+                if (pContext->pBundledContext->bEqualizerEnabled == LVM_TRUE) {
+                    LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_EQUALIZER is already enabled");
+                    return -EINVAL;
+                }
+                pContext->pBundledContext->SamplesToExitCountEq =
+                     (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1);
+                pContext->pBundledContext->bEqualizerEnabled = LVM_TRUE;
+                break;
+            case LVM_VIRTUALIZER:
+                if (pContext->pBundledContext->bVirtualizerEnabled == LVM_TRUE) {
+                    LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_VIRTUALIZER is already enabled");
+                    return -EINVAL;
+                }
+                pContext->pBundledContext->SamplesToExitCountVirt =
+                     (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1);
+                pContext->pBundledContext->bVirtualizerEnabled = LVM_TRUE;
+                break;
+            case LVM_VOLUME:
+                if (pContext->pBundledContext->bVolumeEnabled == LVM_TRUE) {
+                    LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_VOLUME is already enabled");
+                    return -EINVAL;
+                }
+                pContext->pBundledContext->bVolumeEnabled = LVM_TRUE;
+                break;
+            default:
+                LOGV("\tLVM_ERROR : Effect_setEnabled() invalid effect type");
+                return -EINVAL;
+        }
+        pContext->pBundledContext->NumberEffectsEnabled++;
+        LvmEffect_enable(pContext);
+    } else {
+        switch (pContext->EffectType) {
+            case LVM_BASS_BOOST:
+                if (pContext->pBundledContext->bBassEnabled == LVM_FALSE) {
+                    LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_BASS_BOOST is already disabled");
+                    return -EINVAL;
+                }
+                pContext->pBundledContext->bBassEnabled = LVM_FALSE;
+                break;
+            case LVM_EQUALIZER:
+                if (pContext->pBundledContext->bEqualizerEnabled == LVM_FALSE) {
+                    LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_EQUALIZER is already disabled");
+                    return -EINVAL;
+                }
+                pContext->pBundledContext->bEqualizerEnabled = LVM_FALSE;
+                break;
+            case LVM_VIRTUALIZER:
+                if (pContext->pBundledContext->bVirtualizerEnabled == LVM_FALSE) {
+                    LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_VIRTUALIZER is already disabled");
+                    return -EINVAL;
+                }
+                pContext->pBundledContext->bVirtualizerEnabled = LVM_FALSE;
+                break;
+            case LVM_VOLUME:
+                if (pContext->pBundledContext->bVolumeEnabled == LVM_FALSE) {
+                    LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_VOLUME is already disabled");
+                    return -EINVAL;
+                }
+                pContext->pBundledContext->bVolumeEnabled = LVM_FALSE;
+                break;
+            default:
+                LOGV("\tLVM_ERROR : Effect_setEnabled() invalid effect type");
+                return -EINVAL;
+        }
+        pContext->pBundledContext->NumberEffectsEnabled--;
+        LvmEffect_disable(pContext);
+    }
+
+    return 0;
+}
+
 } // namespace
 } // namespace
 
@@ -2430,7 +2534,7 @@
             //LOGV("\tEffect_process: Waiting to turn off BASS_BOOST, %d samples left",
             //    pContext->pBundledContext->SamplesToExitCountBb);
         } else {
-        status = -ENODATA;
+            status = -ENODATA;
         }
     }
     if ((pContext->pBundledContext->bVolumeEnabled == LVM_FALSE)&&
@@ -2847,62 +2951,8 @@
                 LOGV("\tLVM_ERROR : Effect_command cmdCode Case: EFFECT_CMD_ENABLE: ERROR");
                 return -EINVAL;
             }
-            switch (pContext->EffectType){
-                case LVM_BASS_BOOST:
-                    if(pContext->pBundledContext->bBassEnabled == LVM_TRUE){
-                         LOGV("\tLVM_ERROR : BassBoost_command cmdCode Case: "
-                                 "EFFECT_CMD_ENABLE: ERROR-Effect is already enabled");
-                         return -EINVAL;
-                    }
-                    pContext->pBundledContext->bBassEnabled = LVM_TRUE;
-                    //LOGV("\tEffect_command cmdCode Case:EFFECT_CMD_ENABLE LVM_BASS_BOOSTenabled");
-                    break;
-                case LVM_EQUALIZER:
-                    if(pContext->pBundledContext->bEqualizerEnabled == LVM_TRUE){
-                         LOGV("\tLVM_ERROR : Equalizer_command cmdCode Case: "
-                                 "EFFECT_CMD_ENABLE: ERROR-Effect is already enabled");
-                         return -EINVAL;
-                    }
-                    pContext->pBundledContext->bEqualizerEnabled = LVM_TRUE;
-                    //LOGV("\tEffect_command cmdCode Case:EFFECT_CMD_ENABLE LVM_EQUALIZER enabled");
-                    break;
-                case LVM_VIRTUALIZER:
-                    if(pContext->pBundledContext->bVirtualizerEnabled == LVM_TRUE){
-                         LOGV("\tLVM_ERROR : Virtualizer_command cmdCode Case: "
-                                 "EFFECT_CMD_ENABLE: ERROR-Effect is already enabled");
-                         return -EINVAL;
-                    }
-                    pContext->pBundledContext->bVirtualizerEnabled = LVM_TRUE;
-                    //LOGV("\tEffect_command cmdCode :EFFECT_CMD_ENABLE LVM_VIRTUALIZER enabled");
-                    break;
-                case LVM_VOLUME:
-                    if(pContext->pBundledContext->bVolumeEnabled == LVM_TRUE){
-                         LOGV("\tLVM_ERROR : Volume_command cmdCode Case: "
-                                 "EFFECT_CMD_ENABLE: ERROR-Effect is already enabled");
-                         return -EINVAL;
-                    }
-                    pContext->pBundledContext->bVolumeEnabled = LVM_TRUE;
-                    LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_ENABLE LVM_VOLUME enabled");
-                    break;
-                default:
-                    LOGV("\tLVM_ERROR : Effect_command cmdCode Case: "
-                        "EFFECT_CMD_ENABLE: ERROR, invalid Effect Type");
-                    return -EINVAL;
-            }
-            *(int *)pReplyData = 0;
-            pContext->pBundledContext->NumberEffectsEnabled++;
-            android::LvmEffect_enable(pContext);
-            pContext->pBundledContext->SamplesToExitCountEq =
-                 (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1); // 0.1 secs Stereo
-            pContext->pBundledContext->SamplesToExitCountBb =
-                 (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1); // 0.1 secs Stereo
-            pContext->pBundledContext->SamplesToExitCountVirt =
-                 (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1); // 0.1 secs Stereo
-            LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_ENABLE Samples to Exit = %d",
-                pContext->pBundledContext->SamplesToExitCountBb);
-            //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_ENABLE NumberEffectsEnabled = %d",
-            //        pContext->pBundledContext->NumberEffectsEnabled);
-            //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_ENABLE end");
+
+            *(int *)pReplyData = android::Effect_setEnabled(pContext, LVM_TRUE);
             break;
 
         case EFFECT_CMD_DISABLE:
@@ -2911,57 +2961,7 @@
                 LOGV("\tLVM_ERROR : Effect_command cmdCode Case: EFFECT_CMD_DISABLE: ERROR");
                 return -EINVAL;
             }
-            switch (pContext->EffectType){
-                case LVM_BASS_BOOST:
-                    if(pContext->pBundledContext->bBassEnabled == LVM_FALSE){
-                         LOGV("\tLVM_ERROR : BassBoost_command cmdCode Case: "
-                                 "EFFECT_CMD_DISABLE: ERROR-Effect is not yet enabled");
-                         return -EINVAL;
-                    }
-                    pContext->pBundledContext->bBassEnabled      = LVM_FALSE;
-                    //LOGV("\tEffect_command cmdCode Case: "
-                    //       "EFFECT_CMD_DISABLE LVM_BASS_BOOST disabled");
-                    break;
-                case LVM_EQUALIZER:
-                    if(pContext->pBundledContext->bEqualizerEnabled == LVM_FALSE){
-                         LOGV("\tLVM_ERROR : Equalizer_command cmdCode Case: "
-                                 "EFFECT_CMD_DISABLE: ERROR-Effect is not yet enabled");
-                         return -EINVAL;
-                    }
-                    pContext->pBundledContext->bEqualizerEnabled = LVM_FALSE;
-                    //LOGV("\tEffect_command cmdCode Case: "
-                    //       "EFFECT_CMD_DISABLE LVM_EQUALIZER disabled");
-                    break;
-                case LVM_VIRTUALIZER:
-                    if(pContext->pBundledContext->bVirtualizerEnabled == LVM_FALSE){
-                         LOGV("\tLVM_ERROR : Virtualizer_command cmdCode Case: "
-                                 "EFFECT_CMD_DISABLE: ERROR-Effect is not yet enabled");
-                         return -EINVAL;
-                    }
-                    pContext->pBundledContext->bVirtualizerEnabled = LVM_FALSE;
-                    //LOGV("\tEffect_command cmdCode Case: "
-                    //     "EFFECT_CMD_DISABLE LVM_VIRTUALIZER disabled");
-                    break;
-                case LVM_VOLUME:
-                    if(pContext->pBundledContext->bVolumeEnabled == LVM_FALSE){
-                         LOGV("\tLVM_ERROR : Volume_command cmdCode Case: "
-                                 "EFFECT_CMD_DISABLE: ERROR-Effect is not yet enabled");
-                         return -EINVAL;
-                    }
-                    pContext->pBundledContext->bVolumeEnabled = LVM_FALSE;
-                    //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_DISABLE LVM_VOLUME disabled");
-                    break;
-                default:
-                    LOGV("\tLVM_ERROR : Effect_command cmdCode Case: "
-                        "EFFECT_CMD_DISABLE: ERROR, invalid Effect Type");
-                    return -EINVAL;
-            }
-            *(int *)pReplyData = 0;
-            pContext->pBundledContext->NumberEffectsEnabled--;
-            android::LvmEffect_disable(pContext);
-            //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_DISABLE NumberEffectsEnabled = %d",
-            //        pContext->pBundledContext->NumberEffectsEnabled);
-            //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_DISABLE end");
+            *(int *)pReplyData = android::Effect_setEnabled(pContext, LVM_FALSE);
             break;
 
         case EFFECT_CMD_SET_DEVICE:
diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h
index 35e1114..91963af 100644
--- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h
+++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h
@@ -21,6 +21,7 @@
 #include <media/EffectBassBoostApi.h>
 #include <media/EffectVirtualizerApi.h>
 #include <LVM.h>
+#include <limits.h>
 
 #if __cplusplus
 extern "C" {
@@ -30,6 +31,7 @@
 #define MAX_NUM_BANDS              5
 #define MAX_CALL_SIZE              256
 #define LVM_MAX_SESSIONS           32
+#define LVM_UNUSED_SESSION         INT_MAX
 #define BASS_BOOST_CUP_LOAD_ARM9E  150    // Expressed in 0.1 MIPS
 #define VIRTUALIZER_CUP_LOAD_ARM9E 120    // Expressed in 0.1 MIPS
 #define EQUALIZER_CUP_LOAD_ARM9E   220    // Expressed in 0.1 MIPS
diff --git a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
index 60f4288..26c5aca 100755
--- a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
+++ b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
@@ -59,17 +59,17 @@
         // REVERB_PRESET_NONE: values are unused
         {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
         // REVERB_PRESET_SMALLROOM
-        {-1000, -600, 1100, 830, -400, 5, 500, 10, 1000, 1000},
+        {-400, -600, 1100, 830, -400, 5, 500, 10, 1000, 1000},
         // REVERB_PRESET_MEDIUMROOM
-        {-1000, -600, 1300, 830, -1000, 20, -200, 20, 1000, 1000},
+        {-400, -600, 1300, 830, -1000, 20, -200, 20, 1000, 1000},
         // REVERB_PRESET_LARGEROOM
-        {-1000, -600, 1500, 830, -1600, 5, -1000, 40, 1000, 1000},
+        {-400, -600, 1500, 830, -1600, 5, -1000, 40, 1000, 1000},
         // REVERB_PRESET_MEDIUMHALL
-        {-1000, -600, 1800, 700, -1300, 15, -800, 30, 1000, 1000},
+        {-400, -600, 1800, 700, -1300, 15, -800, 30, 1000, 1000},
         // REVERB_PRESET_LARGEHALL
-        {-1000, -600, 1800, 700, -2000, 30, -1400, 60, 1000, 1000},
+        {-400, -600, 1800, 700, -2000, 30, -1400, 60, 1000, 1000},
         // REVERB_PRESET_PLATE
-        {-1000, -200, 1300, 900, 0, 2, 0, 10, 1000, 750},
+        {-400, -200, 1300, 900, 0, 2, 0, 10, 1000, 750},
 };
 
 
@@ -90,7 +90,7 @@
         {0xc2e5d5f0, 0x94bd, 0x4763, 0x9cac, {0x4e, 0x23, 0x4d, 0x06, 0x83, 0x9e}},
         {0xc7a511a0, 0xa3bb, 0x11df, 0x860e, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},
         EFFECT_API_VERSION,
-        EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST,
+        EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST | EFFECT_FLAG_VOLUME_CTRL,
         LVREV_CUP_LOAD_ARM9E,
         LVREV_MEM_USAGE,
         "Insert Environmental Reverb",
@@ -114,7 +114,7 @@
         {0x47382d60, 0xddd8, 0x11db, 0xbf3a, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},
         {0x172cdf00, 0xa3bc, 0x11df, 0xa72f, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},
         EFFECT_API_VERSION,
-        EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST,
+        EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST | EFFECT_FLAG_VOLUME_CTRL,
         LVREV_CUP_LOAD_ARM9E,
         LVREV_MEM_USAGE,
         "Insert Preset Reverb",
@@ -153,10 +153,25 @@
     uint16_t                        curPreset;
     uint16_t                        nextPreset;
     int                             SamplesToExitCount;
+    LVM_INT16                       leftVolume;
+    LVM_INT16                       rightVolume;
+    LVM_INT16                       prevLeftVolume;
+    LVM_INT16                       prevRightVolume;
+    int                             volumeMode;
+};
+
+enum {
+    REVERB_VOLUME_OFF,
+    REVERB_VOLUME_FLAT,
+    REVERB_VOLUME_RAMP,
 };
 
 #define REVERB_DEFAULT_PRESET REVERB_PRESET_MEDIUMROOM
 
+
+#define REVERB_SEND_LEVEL   (0x0C00) // 0.75 in 4.12 format
+#define REVERB_UNIT_VOLUME  (0x1000) // 1.0 in 4.12 format
+
 //--- local function prototypes
 int  Reverb_init            (ReverbContext *pContext);
 void Reverb_free            (ReverbContext *pContext);
@@ -426,9 +441,20 @@
     if (pContext->preset && pContext->nextPreset != pContext->curPreset) {
         Reverb_LoadPreset(pContext);
     }
+
+
+
     // Convert to Input 32 bits
-    for(int i=0; i<frameCount*samplesPerFrame; i++){
-        pContext->InFrames32[i] = (LVM_INT32)pIn[i]<<8;
+    if (pContext->auxiliary) {
+        for(int i=0; i<frameCount*samplesPerFrame; i++){
+            pContext->InFrames32[i] = (LVM_INT32)pIn[i]<<8;
+        }
+    } else {
+        // insert reverb input is always stereo
+        for (int i = 0; i < frameCount; i++) {
+            pContext->InFrames32[2*i] = (pIn[2*i] * REVERB_SEND_LEVEL) >> 4; // <<8 + >>12
+            pContext->InFrames32[2*i+1] = (pIn[2*i+1] * REVERB_SEND_LEVEL) >> 4; // <<8 + >>12
+        }
     }
 
     if (pContext->preset && pContext->curPreset == REVERB_PRESET_NONE) {
@@ -458,6 +484,42 @@
         for (int i=0; i < frameCount*2; i++) { //always stereo here
             OutFrames16[i] = clamp16((pContext->OutFrames32[i]>>8) + (LVM_INT32)pIn[i]);
         }
+
+        // apply volume with ramp if needed
+        if ((pContext->leftVolume != pContext->prevLeftVolume ||
+                pContext->rightVolume != pContext->prevRightVolume) &&
+                pContext->volumeMode == REVERB_VOLUME_RAMP) {
+            LVM_INT32 vl = (LVM_INT32)pContext->prevLeftVolume << 16;
+            LVM_INT32 incl = (((LVM_INT32)pContext->leftVolume << 16) - vl) / frameCount;
+            LVM_INT32 vr = (LVM_INT32)pContext->prevRightVolume << 16;
+            LVM_INT32 incr = (((LVM_INT32)pContext->rightVolume << 16) - vr) / frameCount;
+
+            for (int i = 0; i < frameCount; i++) {
+                OutFrames16[2*i] =
+                        clamp16((LVM_INT32)((vl >> 16) * OutFrames16[2*i]) >> 12);
+                OutFrames16[2*i+1] =
+                        clamp16((LVM_INT32)((vr >> 16) * OutFrames16[2*i+1]) >> 12);
+
+                vl += incl;
+                vr += incr;
+            }
+
+            pContext->prevLeftVolume = pContext->leftVolume;
+            pContext->prevRightVolume = pContext->rightVolume;
+        } else if (pContext->volumeMode != REVERB_VOLUME_OFF) {
+            if (pContext->leftVolume != REVERB_UNIT_VOLUME ||
+                pContext->rightVolume != REVERB_UNIT_VOLUME) {
+                for (int i = 0; i < frameCount; i++) {
+                    OutFrames16[2*i] =
+                            clamp16((LVM_INT32)(pContext->leftVolume * OutFrames16[2*i]) >> 12);
+                    OutFrames16[2*i+1] =
+                            clamp16((LVM_INT32)(pContext->rightVolume * OutFrames16[2*i+1]) >> 12);
+                }
+            }
+            pContext->prevLeftVolume = pContext->leftVolume;
+            pContext->prevRightVolume = pContext->rightVolume;
+            pContext->volumeMode = REVERB_VOLUME_RAMP;
+        }
     }
 
     #ifdef LVM_PCM
@@ -658,6 +720,12 @@
     pContext->config.outputCfg.bufferProvider.cookie        = NULL;
     pContext->config.outputCfg.mask                         = EFFECT_CONFIG_ALL;
 
+    pContext->leftVolume = REVERB_UNIT_VOLUME;
+    pContext->rightVolume = REVERB_UNIT_VOLUME;
+    pContext->prevLeftVolume = REVERB_UNIT_VOLUME;
+    pContext->prevRightVolume = REVERB_UNIT_VOLUME;
+    pContext->volumeMode = REVERB_VOLUME_FLAT;
+
     LVREV_ReturnStatus_en     LvmStatus=LVREV_SUCCESS;        /* Function call status */
     LVREV_ControlParams_st    params;                         /* Control Parameters */
     LVREV_InstanceParams_st   InstParams;                     /* Instance parameters */
@@ -1781,15 +1849,6 @@
         LOGV("\tLVM_ERROR : Reverb_process() ERROR NULL INPUT POINTER OR FRAME COUNT IS WRONG");
         return -EINVAL;
     }
-    if (pContext->bEnabled == LVM_FALSE){
-        if( pContext->SamplesToExitCount > 0){
-            pContext->SamplesToExitCount -= outBuffer->frameCount;
-            LOGV("\tReverb_process() Effect is being stopped %d", pContext->SamplesToExitCount);
-        }else{
-            LOGV("\tReverb_process() Effect is being stopped");
-            return -ENODATA;
-        }
-    }
     //LOGV("\tReverb_process() Calling process with %d frames", outBuffer->frameCount);
     /* Process all the available frames, block processing is handled internalLY by the LVM bundle */
     status = process(    (LVM_INT16 *)inBuffer->raw,
@@ -1797,6 +1856,14 @@
                                       outBuffer->frameCount,
                                       pContext);
 
+    if (pContext->bEnabled == LVM_FALSE) {
+        if (pContext->SamplesToExitCount > 0) {
+            pContext->SamplesToExitCount -= outBuffer->frameCount;
+        } else {
+            status = -ENODATA;
+        }
+    }
+
     return status;
 }   /* end Reverb_process */
 
@@ -1943,6 +2010,8 @@
             LVM_ERROR_CHECK(LvmStatus, "LVREV_GetControlParameters", "EFFECT_CMD_ENABLE")
             pContext->SamplesToExitCount =
                     (ActiveParams.T60 * pContext->config.inputCfg.samplingRate)/1000;
+            // force no volume ramp for first buffer processed after enabling the effect
+            pContext->volumeMode = android::REVERB_VOLUME_FLAT;
             //LOGV("\tEFFECT_CMD_ENABLE SamplesToExitCount = %d", pContext->SamplesToExitCount);
             break;
         case EFFECT_CMD_DISABLE:
@@ -1963,8 +2032,34 @@
             pContext->bEnabled = LVM_FALSE;
             break;
 
-        case EFFECT_CMD_SET_DEVICE:
         case EFFECT_CMD_SET_VOLUME:
+            if (pCmdData == NULL ||
+                cmdSize != 2 * sizeof(uint32_t)) {
+                LOGV("\tLVM_ERROR : Reverb_command cmdCode Case: "
+                        "EFFECT_CMD_SET_VOLUME: ERROR");
+                return -EINVAL;
+            }
+
+
+            if (pReplyData != NULL) { // we have volume control
+                pContext->leftVolume = (LVM_INT16)((*(uint32_t *)pCmdData + (1 << 11)) >> 12);
+                pContext->rightVolume = (LVM_INT16)((*((uint32_t *)pCmdData + 1) + (1 << 11)) >> 12);
+                *(uint32_t *)pReplyData = (1 << 24);
+                *((uint32_t *)pReplyData + 1) = (1 << 24);
+                if (pContext->volumeMode == android::REVERB_VOLUME_OFF) {
+                    // force no volume ramp for first buffer processed after getting volume control
+                    pContext->volumeMode = android::REVERB_VOLUME_FLAT;
+                }
+            } else { // we don't have volume control
+                pContext->leftVolume = REVERB_UNIT_VOLUME;
+                pContext->rightVolume = REVERB_UNIT_VOLUME;
+                pContext->volumeMode = android::REVERB_VOLUME_OFF;
+            }
+            LOGV("EFFECT_CMD_SET_VOLUME left %d, right %d mode %d",
+                    pContext->leftVolume, pContext->rightVolume,  pContext->volumeMode);
+            break;
+
+        case EFFECT_CMD_SET_DEVICE:
         case EFFECT_CMD_SET_AUDIO_MODE:
         //LOGV("\tReverb_command cmdCode Case: "
         //        "EFFECT_CMD_SET_DEVICE/EFFECT_CMD_SET_VOLUME/EFFECT_CMD_SET_AUDIO_MODE start");
diff --git a/media/libmedia/MediaProfiles.cpp b/media/libmedia/MediaProfiles.cpp
index 645c163..9ad63f0 100644
--- a/media/libmedia/MediaProfiles.cpp
+++ b/media/libmedia/MediaProfiles.cpp
@@ -424,24 +424,7 @@
 }
 
 /*static*/ MediaProfiles::CamcorderProfile*
-MediaProfiles::createDefaultCamcorderTimeLapseHighProfile()
-{
-    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 = CAMCORDER_QUALITY_TIME_LAPSE_HIGH;
-    profile->mDuration = 60;
-    profile->mVideoCodec = videoCodec;
-    profile->mAudioCodec = audioCodec;
-    return profile;
-}
-
-/*static*/ MediaProfiles::CamcorderProfile*
-MediaProfiles::createDefaultCamcorderTimeLapseLowProfile()
+MediaProfiles::createDefaultCamcorderTimeLapseQcifProfile(camcorder_quality quality)
 {
     MediaProfiles::VideoCodec *videoCodec =
         new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 1000000, 176, 144, 20);
@@ -450,7 +433,7 @@
     CamcorderProfile *profile = new MediaProfiles::CamcorderProfile;
     profile->mCameraId = 0;
     profile->mFileFormat = OUTPUT_FORMAT_THREE_GPP;
-    profile->mQuality = CAMCORDER_QUALITY_TIME_LAPSE_LOW;
+    profile->mQuality = quality;
     profile->mDuration = 60;
     profile->mVideoCodec = videoCodec;
     profile->mAudioCodec = audioCodec;
@@ -458,24 +441,40 @@
 }
 
 /*static*/ MediaProfiles::CamcorderProfile*
-MediaProfiles::createDefaultCamcorderHighProfile()
+MediaProfiles::createDefaultCamcorderTimeLapse480pProfile(camcorder_quality quality)
 {
     MediaProfiles::VideoCodec *videoCodec =
-        new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 360000, 352, 288, 20);
+        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 = CAMCORDER_QUALITY_HIGH;
+    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::createDefaultCamcorderLowProfile()
+MediaProfiles::createDefaultCamcorderQcifProfile(camcorder_quality quality)
 {
     MediaProfiles::VideoCodec *videoCodec =
         new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 192000, 176, 144, 20);
@@ -486,20 +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(createDefaultCamcorderTimeLapseHighProfile());
-    profiles->mCamcorderProfiles.add(createDefaultCamcorderTimeLapseLowProfile());
-    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
diff --git a/media/libmedia/MediaScanner.cpp b/media/libmedia/MediaScanner.cpp
index ba98f04..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;
@@ -163,14 +143,14 @@
                 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/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 77a9cca..bf0c6e17 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1053,10 +1053,13 @@
     status_t err = setupCamera();
     if (err != OK) return err;
 
-    *cameraSource = (mCaptureTimeLapse) ?
-        CameraSourceTimeLapse::CreateFromCamera(mCamera,
-                mTimeBetweenTimeLapseFrameCaptureUs, mVideoWidth, mVideoHeight, mFrameRate):
-        CameraSource::CreateFromCamera(mCamera);
+    if (mCaptureTimeLapse) {
+        mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera(mCamera,
+                mTimeBetweenTimeLapseFrameCaptureUs, mVideoWidth, mVideoHeight, mFrameRate);
+        *cameraSource = mCameraSourceTimeLapse;
+    } else {
+        *cameraSource = CameraSource::CreateFromCamera(mCamera);
+    }
     CHECK(*cameraSource != NULL);
 
     return OK;
@@ -1193,8 +1196,6 @@
         }
         if ((videoWidth != mVideoWidth) || (videoHeight != mVideoHeight)) {
             // Use downsampling from the original source.
-            CHECK(videoWidth <= mVideoWidth);
-            CHECK(videoHeight <= mVideoHeight);
             cameraMediaSource =
                 new VideoSourceDownSampler(cameraMediaSource, videoWidth, videoHeight);
         }
@@ -1244,6 +1245,10 @@
 
 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);
@@ -1278,6 +1283,12 @@
             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,
@@ -1317,6 +1328,11 @@
     LOGV("stop");
     status_t err = OK;
 
+    if (mCaptureTimeLapse && mCameraSourceTimeLapse != NULL) {
+        mCameraSourceTimeLapse->startQuickReadReturns();
+        mCameraSourceTimeLapse = NULL;
+    }
+
     if (mCaptureAuxVideo) {
         if (mWriterAux != NULL) {
             mWriterAux->stop();
@@ -1403,6 +1419,7 @@
     mTimeBetweenTimeLapseFrameCaptureUs = -1;
     mCaptureAuxVideo = false;
     mCameraSourceSplitter = NULL;
+    mCameraSourceTimeLapse = NULL;
     mEncoderProfiles = MediaProfiles::getInstance();
 
     mOutputFd = -1;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index f1dc9e6..02d9a01 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -25,6 +25,7 @@
 
 class Camera;
 class CameraSource;
+class CameraSourceTimeLapse;
 class MediaSourceSplitter;
 struct MediaSource;
 struct MediaWriter;
@@ -101,6 +102,7 @@
     int64_t mTimeBetweenTimeLapseFrameCaptureUs;
     bool mCaptureAuxVideo;
     sp<MediaSourceSplitter> mCameraSourceSplitter;
+    sp<CameraSourceTimeLapse> mCameraSourceTimeLapse;
 
     String8 mParams;
     int mOutputFd, mOutputFdAux;
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 0d11b33..3bf8ae7 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -37,6 +37,7 @@
         ShoutcastSource.cpp               \
         StagefrightMediaScanner.cpp       \
         StagefrightMetadataRetriever.cpp  \
+        ThreadedSource.cpp                \
         ThrottledSource.cpp               \
         TimeSource.cpp                    \
         TimedEventQueue.cpp               \
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index 8d7ada3..29f16d8 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -256,6 +256,8 @@
             if (numLostBytes > kMaxBufferSize) {
                 mPrevLostBytes = numLostBytes - kMaxBufferSize;
                 numLostBytes = kMaxBufferSize;
+            } else {
+                mPrevLostBytes = 0;
             }
 
             CHECK_EQ(numLostBytes & 1, 0);
@@ -275,8 +277,9 @@
             memset(buffer->data(), 0, numLostBytes);
             buffer->set_range(0, numLostBytes);
             if (numFramesRecorded == 0) {
-                buffer->meta_data()->setInt64(kKeyTime, mStartTimeUs);
+                buffer->meta_data()->setInt64(kKeyAnchorTime, mStartTimeUs);
             }
+            buffer->meta_data()->setInt64(kKeyTime, mStartTimeUs + mPrevSampleTimeUs);
             buffer->meta_data()->setInt64(kKeyDriftTime, readTimeUs - mInitialReadTimeUs);
             mPrevSampleTimeUs = timestampUs;
             *out = buffer;
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 9ccd140..0c9eef4 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -82,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;
     }
@@ -98,6 +102,9 @@
        return OMX_COLOR_Format16bitRGB565;
     }
 
+    LOGE("Uknown color format (%s), please add it to "
+         "CameraSource::getColorFormat", colorFormat);
+
     CHECK_EQ(0, "Unknown color format");
 }
 
@@ -203,6 +210,7 @@
 }
 
 void CameraSource::stopCameraRecording() {
+    mCamera->setListener(NULL);
     mCamera->stopRecording();
 }
 
@@ -213,7 +221,6 @@
     mFrameAvailableCondition.signal();
 
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
-    mCamera->setListener(NULL);
     stopCameraRecording();
     releaseQueuedFrames();
     while (!mFramesBeingEncoded.empty()) {
diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp
index 7595a28..c1bc433 100644
--- a/media/libstagefright/CameraSourceTimeLapse.cpp
+++ b/media/libstagefright/CameraSourceTimeLapse.cpp
@@ -89,11 +89,34 @@
         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();
@@ -168,6 +191,53 @@
     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);
@@ -176,17 +246,31 @@
 }
 
 void CameraSourceTimeLapse::threadTimeLapseEntry() {
-    while(mStarted) {
-        if (mCameraIdle) {
-            LOGV("threadTimeLapseEntry: taking picture");
-            CHECK_EQ(OK, mCamera->takePicture());
+    while (mStarted) {
+        {
+            Mutex::Autolock autoLock(mCameraIdleLock);
+            if (!mCameraIdle) {
+                mCameraIdleCondition.wait(mCameraIdleLock);
+            }
+            CHECK(mCameraIdle);
             mCameraIdle = false;
-            usleep(mTimeBetweenTimeLapseFrameCaptureUs);
-        } else {
-            LOGV("threadTimeLapseEntry: camera busy with old takePicture. Sleeping a little.");
-            usleep(1E4);
         }
+
+        // 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() {
@@ -201,6 +285,11 @@
         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;
@@ -219,10 +308,27 @@
     if (mUseStillCameraForTimeLapse) {
         void *dummy;
         pthread_join(mThreadTimeLapse, &dummy);
-        CHECK_EQ(OK, mCamera->startPreview());
+
+        // 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) {
@@ -257,7 +363,9 @@
 
 void CameraSourceTimeLapse::threadStartPreview() {
     CHECK_EQ(OK, mCamera->startPreview());
+    Mutex::Autolock autoLock(mCameraIdleLock);
     mCameraIdle = true;
+    mCameraIdleCondition.signal();
 }
 
 void CameraSourceTimeLapse::restartPreview() {
@@ -279,7 +387,8 @@
     YUVImage::YUVFormat yuvFormat;
     if (srcFormat == OMX_COLOR_FormatYUV420SemiPlanar) {
         yuvFormat = YUVImage::YUV420SemiPlanar;
-    } else if (srcFormat == OMX_COLOR_FormatYUV420Planar) {
+    } else {
+        CHECK_EQ(srcFormat, OMX_COLOR_FormatYUV420Planar);
         yuvFormat = YUVImage::YUV420Planar;
     }
 
@@ -350,7 +459,23 @@
             LOGV("dataCallbackTimestamp timelapse: initial frame");
 
             mLastTimeLapseFrameRealTimestampUs = *timestampUs;
-        } else if (*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.
@@ -366,6 +491,7 @@
 
             mLastTimeLapseFrameRealTimestampUs = *timestampUs;
             *timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs;
+            return false;
         }
     }
     return false;
@@ -375,6 +501,18 @@
             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);
 }
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index de4233d..f0d8943 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -1675,9 +1675,9 @@
 
     }
 
-    if (mSampleSizes.empty()) {
-        err = ERROR_MALFORMED;
-    } else if (OK != checkCodecSpecificData()) {
+    if (mSampleSizes.empty() ||                      // no samples written
+        (!mIsAudio && mNumStssTableEntries == 0) ||  // no sync frames for video
+        (OK != checkCodecSpecificData())) {          // no codec specific data
         err = ERROR_MALFORMED;
     }
     mOwner->trackProgressStatus(this, -1, err);
@@ -1794,13 +1794,13 @@
         !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
         if (!mCodecSpecificData ||
             mCodecSpecificDataSize <= 0) {
-            // Missing codec specific data
+            LOGE("Missing codec specific data");
             return ERROR_MALFORMED;
         }
     } else {
         if (mCodecSpecificData ||
             mCodecSpecificDataSize > 0) {
-            // Unexepected codec specific data found
+            LOGE("Unexepected codec specific data found");
             return ERROR_MALFORMED;
         }
     }
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index f68c08a..2464efd 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -52,6 +52,8 @@
 #include <OMX_Audio.h>
 #include <OMX_Component.h>
 
+#include "include/ThreadedSource.h"
+
 namespace android {
 
 static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
@@ -134,6 +136,10 @@
     for (size_t i = 0;
          i < sizeof(kFactoryInfo) / sizeof(kFactoryInfo[0]); ++i) {
         if (!strcmp(name, kFactoryInfo[i].name)) {
+            if (!strcmp(name, "VPXDecoder")) {
+                return new ThreadedSource(
+                        (*kFactoryInfo[i].CreateFunc)(source));
+            }
             return (*kFactoryInfo[i].CreateFunc)(source);
         }
     }
@@ -826,8 +832,7 @@
         if (OMX_ErrorNone != mOMX->getParameter(
                 mNode, OMX_IndexParamVideoPortFormat,
                 &portFormat, sizeof(portFormat))) {
-
-            return UNKNOWN_ERROR;
+            break;
         }
         // Make sure that omx component does not overwrite
         // the incremented index (bug 2897413).
@@ -847,6 +852,8 @@
             break;
         }
     }
+
+    LOGE("color format %d is not supported", colorFormat);
     return UNKNOWN_ERROR;
 }
 
@@ -1047,7 +1054,7 @@
                 mNode, OMX_IndexParamVideoProfileLevelQuerySupported,
                 &param, sizeof(param));
 
-        if (err != OK) return err;
+        if (err != OK) break;
 
         int32_t supportedProfile = static_cast<int32_t>(param.eProfile);
         int32_t supportedLevel = static_cast<int32_t>(param.eLevel);
@@ -1055,7 +1062,10 @@
             supportedProfile, supportedLevel);
 
         if (profile == supportedProfile &&
-            level == supportedLevel) {
+            level <= supportedLevel) {
+            // We can further check whether the level is a valid
+            // value; but we will leave that to the omx encoder component
+            // via OMX_SetParameter call.
             profileLevel.mProfile = profile;
             profileLevel.mLevel = level;
             return OK;
diff --git a/media/libstagefright/ThreadedSource.cpp b/media/libstagefright/ThreadedSource.cpp
new file mode 100644
index 0000000..5add2a5
--- /dev/null
+++ b/media/libstagefright/ThreadedSource.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "include/ThreadedSource.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+static const size_t kMaxQueueSize = 2;
+
+ThreadedSource::ThreadedSource(const sp<MediaSource> &source)
+    : mSource(source),
+      mReflector(new AHandlerReflector<ThreadedSource>(this)),
+      mLooper(new ALooper),
+      mStarted(false) {
+    mLooper->registerHandler(mReflector);
+}
+
+ThreadedSource::~ThreadedSource() {
+    if (mStarted) {
+        stop();
+    }
+}
+
+status_t ThreadedSource::start(MetaData *params) {
+    CHECK(!mStarted);
+
+    status_t err = mSource->start(params);
+
+    if (err != OK) {
+        return err;
+    }
+
+    mFinalResult = OK;
+    mSeekTimeUs = -1;
+    mDecodePending = false;
+
+    Mutex::Autolock autoLock(mLock);
+    postDecodeMore_l();
+
+    CHECK_EQ(mLooper->start(), (status_t)OK);
+
+    mStarted = true;
+
+    return OK;
+}
+
+status_t ThreadedSource::stop() {
+    CHECK(mStarted);
+
+    CHECK_EQ(mLooper->stop(), (status_t)OK);
+
+    Mutex::Autolock autoLock(mLock);
+    clearQueue_l();
+
+    status_t err = mSource->stop();
+
+    mStarted = false;
+
+    return err;
+}
+
+sp<MetaData> ThreadedSource::getFormat() {
+    return mSource->getFormat();
+}
+
+status_t ThreadedSource::read(
+        MediaBuffer **buffer, const ReadOptions *options) {
+    *buffer = NULL;
+
+    Mutex::Autolock autoLock(mLock);
+
+    int64_t seekTimeUs;
+    ReadOptions::SeekMode seekMode;
+    if (options && options->getSeekTo(&seekTimeUs, &seekMode)) {
+        int32_t seekComplete = 0;
+
+        sp<AMessage> msg = new AMessage(kWhatSeek, mReflector->id());
+        msg->setInt64("timeUs", seekTimeUs);
+        msg->setInt32("mode", seekMode);
+        msg->setPointer("complete", &seekComplete);
+        msg->post();
+
+        while (!seekComplete) {
+            mCondition.wait(mLock);
+        }
+    }
+
+    while (mQueue.empty() && mFinalResult == OK) {
+        mCondition.wait(mLock);
+    }
+
+    if (!mQueue.empty()) {
+        *buffer = *mQueue.begin();
+        mQueue.erase(mQueue.begin());
+
+        if (mFinalResult == OK) {
+            postDecodeMore_l();
+        }
+
+        return OK;
+    }
+
+    return mFinalResult;
+}
+
+void ThreadedSource::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatSeek:
+        {
+            CHECK(msg->findInt64("timeUs", &mSeekTimeUs));
+            CHECK_GE(mSeekTimeUs, 0ll);
+
+            int32_t x;
+            CHECK(msg->findInt32("mode", &x));
+            mSeekMode = (ReadOptions::SeekMode)x;
+
+            int32_t *seekComplete;
+            CHECK(msg->findPointer("complete", (void **)&seekComplete));
+
+            Mutex::Autolock autoLock(mLock);
+            clearQueue_l();
+            mFinalResult = OK;
+
+            *seekComplete = 1;
+            mCondition.signal();
+
+            postDecodeMore_l();
+            break;
+        }
+
+        case kWhatDecodeMore:
+        {
+            {
+                Mutex::Autolock autoLock(mLock);
+                mDecodePending = false;
+
+                if (mQueue.size() == kMaxQueueSize) {
+                    break;
+                }
+            }
+
+            MediaBuffer *buffer;
+            ReadOptions options;
+            if (mSeekTimeUs >= 0) {
+                options.setSeekTo(mSeekTimeUs, mSeekMode);
+                mSeekTimeUs = -1ll;
+            }
+            status_t err = mSource->read(&buffer, &options);
+
+            Mutex::Autolock autoLock(mLock);
+
+            if (err != OK) {
+                mFinalResult = err;
+            } else {
+                mQueue.push_back(buffer);
+
+                if (mQueue.size() < kMaxQueueSize) {
+                    postDecodeMore_l();
+                }
+            }
+
+            mCondition.signal();
+            break;
+        }
+
+        default:
+            TRESPASS();
+            break;
+    }
+}
+
+void ThreadedSource::postDecodeMore_l() {
+    if (mDecodePending) {
+        return;
+    }
+
+    mDecodePending = true;
+    (new AMessage(kWhatDecodeMore, mReflector->id()))->post();
+}
+
+void ThreadedSource::clearQueue_l() {
+    while (!mQueue.empty()) {
+        MediaBuffer *buffer = *mQueue.begin();
+        mQueue.erase(mQueue.begin());
+
+        buffer->release();
+        buffer = NULL;
+    }
+}
+
+}  // namespace android
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
index 57c1075..aff06bc 100644
--- a/media/libstagefright/WAVExtractor.cpp
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -331,9 +331,20 @@
         return err;
     }
 
+    size_t maxBytesToRead =
+        mBitsPerSample == 8 ? kMaxFrameSize / 2 : kMaxFrameSize;
+
+    size_t maxBytesAvailable =
+        (mCurrentPos - mOffset >= (off_t)mSize)
+            ? 0 : mSize - (mCurrentPos - mOffset);
+
+    if (maxBytesToRead > maxBytesAvailable) {
+        maxBytesToRead = maxBytesAvailable;
+    }
+
     ssize_t n = mDataSource->readAt(
             mCurrentPos, buffer->data(),
-            mBitsPerSample == 8 ? kMaxFrameSize / 2 : kMaxFrameSize);
+            maxBytesToRead);
 
     if (n <= 0) {
         buffer->release();
diff --git a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
index 7154ba5..7483d60 100644
--- a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
+++ b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
@@ -317,7 +317,7 @@
                 &nalType, &nalRefIdc);
 
     if (res != AVCDEC_SUCCESS) {
-        LOGE("cannot determine nal type");
+        LOGV("cannot determine nal type");
     } else if (nalType == AVC_NALTYPE_SPS || nalType == AVC_NALTYPE_PPS
                 || (mSPSSeen && mPPSSeen)) {
         switch (nalType) {
@@ -330,6 +330,7 @@
                         fragSize);
 
                 if (res != AVCDEC_SUCCESS) {
+                    LOGV("PVAVCDecSeqParamSet returned error %d", res);
                     break;
                 }
 
@@ -396,6 +397,7 @@
                         fragSize);
 
                 if (res != AVCDEC_SUCCESS) {
+                    LOGV("PVAVCDecPicParamSet returned error %d", res);
                     break;
                 }
 
@@ -418,8 +420,13 @@
                     AVCFrameIO Output;
                     Output.YCbCr[0] = Output.YCbCr[1] = Output.YCbCr[2] = NULL;
 
-                    CHECK_EQ(PVAVCDecGetOutput(mHandle, &index, &Release, &Output),
-                             AVCDEC_SUCCESS);
+                    AVCDec_Status status =
+                        PVAVCDecGetOutput(mHandle, &index, &Release, &Output);
+
+                    if (status != AVCDEC_SUCCESS) {
+                        LOGV("PVAVCDecGetOutput returned error %d", status);
+                        break;
+                    }
 
                     CHECK(index >= 0);
                     CHECK(index < (int32_t)mFrames.size());
@@ -466,7 +473,7 @@
 
                     err = OK;
                 } else {
-                    LOGV("failed to decode frame (res = %d)", res);
+                    LOGV("PVAVCDecodeSlice returned error %d", res);
                 }
                 break;
             }
diff --git a/media/libstagefright/codecs/on2/dec/VPXDecoder.cpp b/media/libstagefright/codecs/on2/dec/VPXDecoder.cpp
index fbc97f4..9433178 100644
--- a/media/libstagefright/codecs/on2/dec/VPXDecoder.cpp
+++ b/media/libstagefright/codecs/on2/dec/VPXDecoder.cpp
@@ -29,8 +29,9 @@
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/Utils.h>
 
-#include "vpx_codec/vpx_decoder.h"
-#include "vp8/vp8dx.h"
+#include "vpx/vpx_decoder.h"
+#include "vpx/vpx_codec.h"
+#include "vpx/vp8dx.h"
 
 namespace android {
 
@@ -82,9 +83,10 @@
     }
 
     mCtx = new vpx_codec_ctx_t;
-    if (vpx_codec_dec_init(
-                (vpx_codec_ctx_t *)mCtx, &vpx_codec_vp8_dx_algo, NULL, 0)) {
-        LOGE("on2 decoder failed to initialize.");
+    vpx_codec_err_t vpx_err;
+    if ((vpx_err = vpx_codec_dec_init(
+                (vpx_codec_ctx_t *)mCtx, &vpx_codec_vp8_dx_algo, NULL, 0))) {
+        LOGE("on2 decoder failed to initialize. (%d)", vpx_err);
 
         mSource->stop();
 
diff --git a/media/libstagefright/include/ThreadedSource.h b/media/libstagefright/include/ThreadedSource.h
new file mode 100644
index 0000000..c67295c
--- /dev/null
+++ b/media/libstagefright/include/ThreadedSource.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 THREADED_SOURCE_H_
+
+#define THREADED_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/threads.h>
+
+namespace android {
+
+struct ThreadedSource : public MediaSource {
+    ThreadedSource(const sp<MediaSource> &source);
+
+    virtual status_t start(MetaData *params);
+    virtual status_t stop();
+
+    virtual sp<MetaData> getFormat();
+
+    virtual status_t read(
+            MediaBuffer **buffer, const ReadOptions *options);
+
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+protected:
+    virtual ~ThreadedSource();
+
+private:
+    enum {
+        kWhatDecodeMore = 'deco',
+        kWhatSeek       = 'seek',
+    };
+
+    sp<MediaSource> mSource;
+    sp<AHandlerReflector<ThreadedSource> > mReflector;
+    sp<ALooper> mLooper;
+
+    Mutex mLock;
+    Condition mCondition;
+    List<MediaBuffer *> mQueue;
+    status_t mFinalResult;
+    bool mDecodePending;
+    bool mStarted;
+
+    int64_t mSeekTimeUs;
+    ReadOptions::SeekMode mSeekMode;
+
+    void postDecodeMore_l();
+    void clearQueue_l();
+
+    DISALLOW_EVIL_CONSTRUCTORS(ThreadedSource);
+};
+
+}  // namespace android
+
+#endif  // THREADED_SOURCE_H_
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
index b63798f..b3e86eb 100644
--- a/media/libstagefright/rtsp/APacketSource.cpp
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -764,7 +764,11 @@
     int64_t lastTimeUs;
     CHECK(last->meta()->findInt64("timeUs", &lastTimeUs));
 
-    CHECK_GE(lastTimeUs, firstTimeUs);
+    if (lastTimeUs < firstTimeUs) {
+        LOG(ERROR) << "Huh? Time moving backwards? "
+                   << firstTimeUs << " > " << lastTimeUs;
+        return 0;
+    }
 
     return lastTimeUs - firstTimeUs;
 }
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index fd6aa62..dcff64c 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -175,19 +175,38 @@
 
     mState = CONNECTING;
 
-    mSocket = socket(AF_INET, SOCK_STREAM, 0);
-
-    MakeSocketBlocking(mSocket, false);
-
     AString url;
     CHECK(msg->findString("url", &url));
 
+    sp<AMessage> reply;
+    CHECK(msg->findMessage("reply", &reply));
+
     AString host, path;
     unsigned port;
-    CHECK(ParseURL(url.c_str(), &host, &port, &path));
+    if (!ParseURL(url.c_str(), &host, &port, &path)) {
+        LOG(ERROR) << "Malformed rtsp url " << url;
+
+        reply->setInt32("result", ERROR_MALFORMED);
+        reply->post();
+
+        mState = DISCONNECTED;
+        return;
+    }
 
     struct hostent *ent = gethostbyname(host.c_str());
-    CHECK(ent != NULL);
+    if (ent == NULL) {
+        LOG(ERROR) << "Unknown host " << host;
+
+        reply->setInt32("result", -ENOENT);
+        reply->post();
+
+        mState = DISCONNECTED;
+        return;
+    }
+
+    mSocket = socket(AF_INET, SOCK_STREAM, 0);
+
+    MakeSocketBlocking(mSocket, false);
 
     struct sockaddr_in remote;
     memset(remote.sin_zero, 0, sizeof(remote.sin_zero));
@@ -198,9 +217,6 @@
     int err = ::connect(
             mSocket, (const struct sockaddr *)&remote, sizeof(remote));
 
-    sp<AMessage> reply;
-    CHECK(msg->findMessage("reply", &reply));
-
     reply->setInt32("server-ip", ntohl(remote.sin_addr.s_addr));
 
     if (err < 0) {
@@ -337,13 +353,20 @@
 
         if (n == 0) {
             // Server closed the connection.
-            TRESPASS();
+            LOG(ERROR) << "Server unexpectedly closed the connection.";
+
+            reply->setInt32("result", ERROR_IO);
+            reply->post();
+            return;
         } else if (n < 0) {
             if (errno == EINTR) {
                 continue;
             }
 
-            TRESPASS();
+            LOG(ERROR) << "Error sending rtsp request.";
+            reply->setInt32("result", -errno);
+            reply->post();
+            return;
         }
 
         numBytesSent += (size_t)n;
@@ -415,13 +438,15 @@
         ssize_t n = recv(mSocket, (uint8_t *)data + offset, size - offset, 0);
         if (n == 0) {
             // Server closed the connection.
+            LOG(ERROR) << "Server unexpectedly closed the connection.";
             return ERROR_IO;
         } else if (n < 0) {
             if (errno == EINTR) {
                 continue;
             }
 
-            TRESPASS();
+            LOG(ERROR) << "Error reading rtsp response.";
+            return -errno;
         }
 
         offset += (size_t)n;
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 0aefd58..a3864f1 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -209,22 +209,24 @@
                                 response->mContent->data(),
                                 response->mContent->size());
 
-                        CHECK(mSessionDesc->isValid());
-
-                        ssize_t i = response->mHeaders.indexOfKey("content-base");
-                        if (i >= 0) {
-                            mBaseURL = response->mHeaders.valueAt(i);
+                        if (!mSessionDesc->isValid()) {
+                            result = ERROR_MALFORMED;
                         } else {
-                            i = response->mHeaders.indexOfKey("content-location");
+                            ssize_t i = response->mHeaders.indexOfKey("content-base");
                             if (i >= 0) {
                                 mBaseURL = response->mHeaders.valueAt(i);
                             } else {
-                                mBaseURL = mSessionURL;
+                                i = response->mHeaders.indexOfKey("content-location");
+                                if (i >= 0) {
+                                    mBaseURL = response->mHeaders.valueAt(i);
+                                } else {
+                                    mBaseURL = mSessionURL;
+                                }
                             }
-                        }
 
-                        CHECK_GT(mSessionDesc->countTracks(), 1u);
-                        setupTrack(1);
+                            CHECK_GT(mSessionDesc->countTracks(), 1u);
+                            setupTrack(1);
+                        }
                     }
                 }
 
@@ -333,13 +335,17 @@
                     sp<ARTSPResponse> response =
                         static_cast<ARTSPResponse *>(obj.get());
 
-                    CHECK_EQ(response->mStatusCode, 200u);
+                    if (response->mStatusCode != 200) {
+                        result = UNKNOWN_ERROR;
+                    } else {
+                        parsePlayResponse(response);
 
-                    parsePlayResponse(response);
+                        sp<AMessage> timeout = new AMessage('tiou', id());
+                        timeout->post(kStartupTimeoutUs);
+                    }
+                }
 
-                    sp<AMessage> timeout = new AMessage('tiou', id());
-                    timeout->post(kStartupTimeoutUs);
-                } else {
+                if (result != OK) {
                     sp<AMessage> reply = new AMessage('disc', id());
                     mConn->disconnect(reply);
                 }
@@ -477,6 +483,11 @@
 
                 uint32_t seqNum = (uint32_t)accessUnit->int32Data();
 
+                if (mSeekPending) {
+                    LOG(INFO) << "we're seeking, dropping stale packet.";
+                    break;
+                }
+
                 if (seqNum < track->mFirstSeqNumInSegment) {
                     LOG(INFO) << "dropping stale access-unit "
                               << "(" << seqNum << " < "
@@ -600,18 +611,26 @@
                 LOG(INFO) << "PLAY completed with result "
                      << result << " (" << strerror(-result) << ")";
 
-                CHECK_EQ(result, (status_t)OK);
+                if (result == OK) {
+                    sp<RefBase> obj;
+                    CHECK(msg->findObject("response", &obj));
+                    sp<ARTSPResponse> response =
+                        static_cast<ARTSPResponse *>(obj.get());
 
-                sp<RefBase> obj;
-                CHECK(msg->findObject("response", &obj));
-                sp<ARTSPResponse> response =
-                    static_cast<ARTSPResponse *>(obj.get());
+                    if (response->mStatusCode != 200) {
+                        result = UNKNOWN_ERROR;
+                    } else {
+                        parsePlayResponse(response);
 
-                CHECK_EQ(response->mStatusCode, 200u);
+                        LOG(INFO) << "seek completed.";
+                    }
+                }
 
-                parsePlayResponse(response);
+                if (result != OK) {
+                    LOG(ERROR) << "seek failed, aborting.";
+                    (new AMessage('abor', id()))->post();
+                }
 
-                LOG(INFO) << "seek completed.";
                 mSeekPending = false;
                 break;
             }
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 3d3bd62..6332b4e 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -21,6 +21,8 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <sys/stat.h>
+#include <dirent.h>
 
 #include <cutils/properties.h>
 
@@ -686,21 +688,77 @@
     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(1);
+    MtpObjectFormat format = mRequest.getParameter(2);
     // FIXME - support deleting all objects if handle is 0xFFFFFFFF
     // FIXME - implement deleting objects by format
-    // FIXME - handle non-empty directories
 
     MtpString filePath;
     int64_t fileLength;
     int result = mDatabase->getObjectFilePath(handle, filePath, fileLength);
     if (result == MTP_RESPONSE_OK) {
         LOGV("deleting %s", (const char *)filePath);
-        // one of these should work
-        rmdir((const char *)filePath);
-        unlink((const char *)filePath);
+        deletePath((const char *)filePath);
         return mDatabase->deleteFile(handle);
     } else {
         return result;
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 57f0072..c753aa5 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -20,7 +20,7 @@
 #include <android/input.h>
 #include <ui/Input.h>
 #include <ui/InputTransport.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 #include <utils/RefBase.h>
 #include <utils/Vector.h>
 
@@ -250,7 +250,7 @@
 
 
 void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
-        int ident, ALooper_callbackFunc* callback, void* data) {
+        int ident, ALooper_callbackFunc callback, void* data) {
     queue->attachLooper(looper, ident, callback, data);
 }
 
diff --git a/native/android/looper.cpp b/native/android/looper.cpp
index 0aeed77..9f5cda9 100644
--- a/native/android/looper.cpp
+++ b/native/android/looper.cpp
@@ -18,65 +18,56 @@
 #include <utils/Log.h>
 
 #include <android/looper.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 
-using android::PollLoop;
+using android::Looper;
 using android::sp;
 
 ALooper* ALooper_forThread() {
-    return PollLoop::getForThread().get();
+    return Looper::getForThread().get();
 }
 
-ALooper* ALooper_prepare(int32_t opts) {
-    bool allowFds = (opts&ALOOPER_PREPARE_ALLOW_NON_CALLBACKS) != 0;
-    sp<PollLoop> loop = PollLoop::getForThread();
-    if (loop == NULL) {
-        loop = new PollLoop(allowFds);
-        PollLoop::setForThread(loop);
-    }
-    if (loop->getAllowNonCallbacks() != allowFds) {
-        LOGW("ALooper_prepare again with different ALOOPER_PREPARE_ALLOW_NON_CALLBACKS");
-    }
-    return loop.get();
-}
-
-int32_t ALooper_pollOnce(int timeoutMillis, int* outEvents, void** outData) {
-    sp<PollLoop> loop = PollLoop::getForThread();
-    if (loop == NULL) {
-        LOGW("ALooper_pollOnce: No looper for this thread!");
-        return -1;
-    }
-    return loop->pollOnce(timeoutMillis, outEvents, outData);
-}
-
-int32_t ALooper_pollAll(int timeoutMillis, int* outEvents, void** outData) {
-    sp<PollLoop> loop = PollLoop::getForThread();
-    if (loop == NULL) {
-        LOGW("ALooper_pollOnce: No looper for this thread!");
-        return -1;
-    }
-    
-    int32_t result;
-    while ((result = loop->pollOnce(timeoutMillis, outEvents, outData)) == ALOOPER_POLL_CALLBACK) {
-        ;
-    }
-    
-    return result;
+ALooper* ALooper_prepare(int opts) {
+    return Looper::prepare(opts).get();
 }
 
 void ALooper_acquire(ALooper* looper) {
-    static_cast<PollLoop*>(looper)->incStrong((void*)ALooper_acquire);
+    static_cast<Looper*>(looper)->incStrong((void*)ALooper_acquire);
 }
 
 void ALooper_release(ALooper* looper) {
-    static_cast<PollLoop*>(looper)->decStrong((void*)ALooper_acquire);
+    static_cast<Looper*>(looper)->decStrong((void*)ALooper_acquire);
 }
 
-void ALooper_addFd(ALooper* looper, int fd, int ident, int events,
-        ALooper_callbackFunc* callback, void* data) {
-    static_cast<PollLoop*>(looper)->setLooperCallback(fd, ident, events, callback, data);
+int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
+    sp<Looper> looper = Looper::getForThread();
+    if (looper == NULL) {
+        LOGE("ALooper_pollOnce: No looper for this thread!");
+        return ALOOPER_POLL_ERROR;
+    }
+
+    return looper->pollOnce(timeoutMillis, outFd, outEvents, outData);
 }
 
-int32_t ALooper_removeFd(ALooper* looper, int fd) {
-    return static_cast<PollLoop*>(looper)->removeCallback(fd) ? 1 : 0;
+int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
+    sp<Looper> looper = Looper::getForThread();
+    if (looper == NULL) {
+        LOGE("ALooper_pollAll: No looper for this thread!");
+        return ALOOPER_POLL_ERROR;
+    }
+    
+    return looper->pollAll(timeoutMillis, outFd, outEvents, outData);
+}
+
+void ALooper_wake(ALooper* looper) {
+    static_cast<Looper*>(looper)->wake();
+}
+
+int ALooper_addFd(ALooper* looper, int fd, int ident, int events,
+        ALooper_callbackFunc callback, void* data) {
+    return static_cast<Looper*>(looper)->addFd(fd, ident, events, callback, data);
+}
+
+int ALooper_removeFd(ALooper* looper, int fd) {
+    return static_cast<Looper*>(looper)->removeFd(fd);
 }
diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp
index cf7635d..76c6eda 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -21,7 +21,7 @@
 #include <android/sensor.h>
 
 #include <utils/RefBase.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 #include <utils/Timers.h>
 
 #include <gui/Sensor.h>
@@ -60,12 +60,12 @@
 }
 
 ASensorEventQueue* ASensorManager_createEventQueue(ASensorManager* manager,
-        ALooper* looper, int ident, ALooper_callbackFunc* callback, void* data)
+        ALooper* looper, int ident, ALooper_callbackFunc callback, void* data)
 {
     sp<SensorEventQueue> queue =
             static_cast<SensorManager*>(manager)->createEventQueue();
     if (queue != 0) {
-        ALooper_addFd(looper, queue->getFd(), ident, POLLIN, callback, data);
+        ALooper_addFd(looper, queue->getFd(), ident, ALOOPER_EVENT_INPUT, callback, data);
         queue->looper = looper;
         queue->incStrong(manager);
     }
diff --git a/native/include/android/input.h b/native/include/android/input.h
index 9da122b..5580700 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -295,7 +295,6 @@
     AINPUT_SOURCE_CLASS_POINTER = 0x00000002,
     AINPUT_SOURCE_CLASS_NAVIGATION = 0x00000004,
     AINPUT_SOURCE_CLASS_POSITION = 0x00000008,
-    AINPUT_SOURCE_CLASS_JOYSTICK = 0x00000010,
 };
 
 enum {
@@ -303,13 +302,10 @@
 
     AINPUT_SOURCE_KEYBOARD = 0x00000100 | AINPUT_SOURCE_CLASS_BUTTON,
     AINPUT_SOURCE_DPAD = 0x00000200 | AINPUT_SOURCE_CLASS_BUTTON,
-    AINPUT_SOURCE_GAMEPAD = 0x00000400 | AINPUT_SOURCE_CLASS_BUTTON,
     AINPUT_SOURCE_TOUCHSCREEN = 0x00001000 | AINPUT_SOURCE_CLASS_POINTER,
     AINPUT_SOURCE_MOUSE = 0x00002000 | AINPUT_SOURCE_CLASS_POINTER,
     AINPUT_SOURCE_TRACKBALL = 0x00010000 | AINPUT_SOURCE_CLASS_NAVIGATION,
     AINPUT_SOURCE_TOUCHPAD = 0x00100000 | AINPUT_SOURCE_CLASS_POSITION,
-    AINPUT_SOURCE_JOYSTICK_LEFT = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
-    AINPUT_SOURCE_JOYSTICK_RIGHT = 0x02000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
 };
 
 /*
@@ -645,7 +641,7 @@
  * ALooper_addFd() for information on the ident, callback, and data params.
  */
 void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
-        int ident, ALooper_callbackFunc* callback, void* data);
+        int ident, ALooper_callbackFunc callback, void* data);
 
 /*
  * Remove the input queue from the looper it is currently attached to.
diff --git a/native/include/android/looper.h b/native/include/android/looper.h
index 287bcd5..a63b744 100644
--- a/native/include/android/looper.h
+++ b/native/include/android/looper.h
@@ -18,8 +18,6 @@
 #ifndef ANDROID_LOOPER_H
 #define ANDROID_LOOPER_H
 
-#include <poll.h>
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -41,25 +39,14 @@
 typedef struct ALooper ALooper;
 
 /**
- * For callback-based event loops, this is the prototype of the function
- * that is called.  It is given the file descriptor it is associated with,
- * a bitmask of the poll events that were triggered (typically POLLIN), and
- * the data pointer that was originally supplied.
- *
- * Implementations should return 1 to continue receiving callbacks, or 0
- * to have this file descriptor and callback unregistered from the looper.
- */
-typedef int ALooper_callbackFunc(int fd, int events, void* data);
-
-/**
- * Return the ALooper associated with the calling thread, or NULL if
+ * Returns the looper associated with the calling thread, or NULL if
  * there is not one.
  */
 ALooper* ALooper_forThread();
 
 enum {
     /**
-     * Option for ALooper_prepare: this ALooper will accept calls to
+     * Option for ALooper_prepare: this looper will accept calls to
      * ALooper_addFd() that do not have a callback (that is provide NULL
      * for the callback).  In this case the caller of ALooper_pollOnce()
      * or ALooper_pollAll() MUST check the return from these functions to
@@ -69,66 +56,42 @@
 };
 
 /**
- * Prepare an ALooper associated with the calling thread, and return it.
- * If the thread already has an ALooper, it is returned.  Otherwise, a new
+ * Prepares a looper associated with the calling thread, and returns it.
+ * If the thread already has a looper, it is returned.  Otherwise, a new
  * one is created, associated with the thread, and returned.
  *
  * The opts may be ALOOPER_PREPARE_ALLOW_NON_CALLBACKS or 0.
  */
-ALooper* ALooper_prepare(int32_t opts);
+ALooper* ALooper_prepare(int opts);
 
 enum {
     /**
-     * Result from ALooper_pollOnce() and ALooper_pollAll(): one or
-     * more callbacks were executed.
+     * Result from ALooper_pollOnce() and ALooper_pollAll():
+     * The poll was awoken using wake() before the timeout expired
+     * and no callbacks were executed and no other file descriptors were ready.
      */
-    ALOOPER_POLL_CALLBACK = -1,
-    
+    ALOOPER_POLL_WAKE = -1,
+
     /**
-     * Result from ALooper_pollOnce() and ALooper_pollAll(): the
-     * timeout expired.
+     * Result from ALooper_pollOnce() and ALooper_pollAll():
+     * One or more callbacks were executed.
      */
-    ALOOPER_POLL_TIMEOUT = -2,
-    
+    ALOOPER_POLL_CALLBACK = -2,
+
     /**
-     * Result from ALooper_pollOnce() and ALooper_pollAll(): an error
-     * occurred.
+     * Result from ALooper_pollOnce() and ALooper_pollAll():
+     * The timeout expired.
      */
-    ALOOPER_POLL_ERROR = -3,
+    ALOOPER_POLL_TIMEOUT = -3,
+
+    /**
+     * Result from ALooper_pollOnce() and ALooper_pollAll():
+     * An error occurred.
+     */
+    ALOOPER_POLL_ERROR = -4,
 };
 
 /**
- * Wait for events to be available, with optional timeout in milliseconds.
- * Invokes callbacks for all file descriptors on which an event occurred.
- *
- * If the timeout is zero, returns immediately without blocking.
- * If the timeout is negative, waits indefinitely until an event appears.
- *
- * Returns ALOOPER_POLL_CALLBACK if a callback was invoked.
- *
- * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
- * timeout expired.
- *
- * Returns ALOPER_POLL_ERROR if an error occurred.
- *
- * Returns a value >= 0 containing an identifier if its file descriptor has data
- * and it has no callback function (requiring the caller here to handle it).
- * In this (and only this) case outEvents and outData will contain the poll
- * events and data associated with the fd.
- *
- * This method does not return until it has finished invoking the appropriate callbacks
- * for all file descriptors that were signalled.
- */
-int32_t ALooper_pollOnce(int timeoutMillis, int* outEvents, void** outData);
-
-/**
- * Like ALooper_pollOnce(), but performs all pending callbacks until all
- * data has been consumed or a file descriptor is available with no callback.
- * This function will never return ALOOPER_POLL_CALLBACK.
- */
-int32_t ALooper_pollAll(int timeoutMillis, int* outEvents, void** outData);
-
-/**
  * Acquire a reference on the given ALooper object.  This prevents the object
  * from being deleted until the reference is removed.  This is only needed
  * to safely hand an ALooper from one thread to another.
@@ -141,36 +104,140 @@
 void ALooper_release(ALooper* looper);
 
 /**
- * Add a new file descriptor to be polled by the looper.  If the same file
- * descriptor was previously added, it is replaced.
+ * Flags for file descriptor events that a looper can monitor.
+ *
+ * These flag bits can be combined to monitor multiple events at once.
+ */
+enum {
+    /**
+     * The file descriptor is available for read operations.
+     */
+    ALOOPER_EVENT_INPUT = 1 << 0,
+
+    /**
+     * The file descriptor is available for write operations.
+     */
+    ALOOPER_EVENT_OUTPUT = 1 << 1,
+
+    /**
+     * The file descriptor has encountered an error condition.
+     *
+     * The looper always sends notifications about errors; it is not necessary
+     * to specify this event flag in the requested event set.
+     */
+    ALOOPER_EVENT_ERROR = 1 << 2,
+
+    /**
+     * The file descriptor was hung up.
+     * For example, indicates that the remote end of a pipe or socket was closed.
+     *
+     * The looper always sends notifications about hangups; it is not necessary
+     * to specify this event flag in the requested event set.
+     */
+    ALOOPER_EVENT_HANGUP = 1 << 3,
+};
+
+/**
+ * For callback-based event loops, this is the prototype of the function
+ * that is called.  It is given the file descriptor it is associated with,
+ * a bitmask of the poll events that were triggered (typically ALOOPER_EVENT_INPUT),
+ * and the data pointer that was originally supplied.
+ *
+ * Implementations should return 1 to continue receiving callbacks, or 0
+ * to have this file descriptor and callback unregistered from the looper.
+ */
+typedef int (*ALooper_callbackFunc)(int fd, int events, void* data);
+
+/**
+ * Waits for events to be available, with optional timeout in milliseconds.
+ * Invokes callbacks for all file descriptors on which an event occurred.
+ *
+ * If the timeout is zero, returns immediately without blocking.
+ * If the timeout is negative, waits indefinitely until an event appears.
+ *
+ * Returns ALOOPER_POLL_WAKE if the poll was awoken using wake() before
+ * the timeout expired and no callbacks were invoked and no other file
+ * descriptors were ready.
+ *
+ * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked.
+ *
+ * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
+ * timeout expired.
+ *
+ * Returns ALOOPER_POLL_ERROR if an error occurred.
+ *
+ * Returns a value >= 0 containing an identifier if its file descriptor has data
+ * and it has no callback function (requiring the caller here to handle it).
+ * In this (and only this) case outFd, outEvents and outData will contain the poll
+ * events and data associated with the fd, otherwise they will be set to NULL.
+ *
+ * This method does not return until it has finished invoking the appropriate callbacks
+ * for all file descriptors that were signalled.
+ */
+int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
+
+/**
+ * Like ALooper_pollOnce(), but performs all pending callbacks until all
+ * data has been consumed or a file descriptor is available with no callback.
+ * This function will never return ALOOPER_POLL_CALLBACK.
+ */
+int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData);
+
+/**
+ * Wakes the poll asynchronously.
+ *
+ * This method can be called on any thread.
+ * This method returns immediately.
+ */
+void ALooper_wake(ALooper* looper);
+
+/**
+ * Adds a new file descriptor to be polled by the looper.
+ * If the same file descriptor was previously added, it is replaced.
  *
  * "fd" is the file descriptor to be added.
- * "ident" is an identifier for this event, which is returned from
- * ALooper_pollOnce().  Must be >= 0, or ALOOPER_POLL_CALLBACK if
- * providing a non-NULL callback.
- * "events" are the poll events to wake up on.  Typically this is POLLIN.
- * "callback" is the function to call when there is an event on the file
- * descriptor.
+ * "ident" is an identifier for this event, which is returned from ALooper_pollOnce().
+ * The identifier must be >= 0, or ALOOPER_POLL_CALLBACK if providing a non-NULL callback.
+ * "events" are the poll events to wake up on.  Typically this is ALOOPER_EVENT_INPUT.
+ * "callback" is the function to call when there is an event on the file descriptor.
  * "data" is a private data pointer to supply to the callback.
  *
  * There are two main uses of this function:
  *
- * (1) If "callback" is non-NULL, then
- * this function will be called when there is data on the file descriptor.  It
- * should execute any events it has pending, appropriately reading from the
- * file descriptor.  The 'ident' is ignored in this case.
+ * (1) If "callback" is non-NULL, then this function will be called when there is
+ * data on the file descriptor.  It should execute any events it has pending,
+ * appropriately reading from the file descriptor.  The 'ident' is ignored in this case.
  *
  * (2) If "callback" is NULL, the 'ident' will be returned by ALooper_pollOnce
  * when its file descriptor has data available, requiring the caller to take
  * care of processing it.
+ *
+ * Returns 1 if the file descriptor was added or -1 if an error occurred.
+ *
+ * This method can be called on any thread.
+ * This method may block briefly if it needs to wake the poll.
  */
-void ALooper_addFd(ALooper* looper, int fd, int ident, int events,
-        ALooper_callbackFunc* callback, void* data);
+int ALooper_addFd(ALooper* looper, int fd, int ident, int events,
+        ALooper_callbackFunc callback, void* data);
 
 /**
- * Remove a previously added file descriptor from the looper.
+ * Removes a previously added file descriptor from the looper.
+ *
+ * When this method returns, it is safe to close the file descriptor since the looper
+ * will no longer have a reference to it.  However, it is possible for the callback to
+ * already be running or for it to run one last time if the file descriptor was already
+ * signalled.  Calling code is responsible for ensuring that this case is safely handled.
+ * For example, if the callback takes care of removing itself during its own execution either
+ * by returning 0 or by calling this method, then it can be guaranteed to not be invoked
+ * again at any later time unless registered anew.
+ *
+ * Returns 1 if the file descriptor was removed, 0 if none was previously registered
+ * or -1 if an error occurred.
+ *
+ * This method can be called on any thread.
+ * This method may block briefly if it needs to wake the poll.
  */
-int32_t ALooper_removeFd(ALooper* looper, int fd);
+int ALooper_removeFd(ALooper* looper, int fd);
 
 #ifdef __cplusplus
 };
diff --git a/native/include/android/sensor.h b/native/include/android/sensor.h
index a102d43..f163f18 100644
--- a/native/include/android/sensor.h
+++ b/native/include/android/sensor.h
@@ -166,7 +166,7 @@
  * Creates a new sensor event queue and associate it with a looper.
  */
 ASensorEventQueue* ASensorManager_createEventQueue(ASensorManager* manager,
-        ALooper* looper, int ident, ALooper_callbackFunc* callback, void* data);
+        ALooper* looper, int ident, ALooper_callbackFunc callback, void* data);
 
 /*
  * Destroys the event queue and free all resources associated to it.
diff --git a/opengl/include/GLES/glext.h b/opengl/include/GLES/glext.h
index a5b3ead..65ab5e4 100644
--- a/opengl/include/GLES/glext.h
+++ b/opengl/include/GLES/glext.h
@@ -211,9 +211,12 @@
 #define GL_VERTEX_ARRAY_BINDING_OES                             0x85B5
 #endif
 
-/* GL_OES_texture_external */
-#ifndef GL_TEXTURE_EXTERNAL_OES
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
 #define GL_TEXTURE_EXTERNAL_OES                                 0x8D65
+#define GL_SAMPLER_EXTERNAL_OES                                 0x8D66
+#define GL_TEXTURE_BINDING_EXTERNAL_OES                         0x8D67
+#define GL_REQUIRED_TEXTURE_IMAGE_UNITS_OES                     0x8D68
 #endif
 
 /*------------------------------------------------------------------------*
@@ -782,9 +785,9 @@
 typedef GLboolean (GL_APIENTRYP PFNGLISVERTEXARRAYOESPROC) (GLuint array);
 #endif
 
-/* GL_OES_texture_external */
-#ifndef GL_OES_texture_external
-#define GL_OES_texture_external 1
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
+#define GL_OES_EGL_image_external 1
 #endif
 
 /*------------------------------------------------------------------------*
diff --git a/opengl/include/GLES2/gl2ext.h b/opengl/include/GLES2/gl2ext.h
index de5d65a..9db4e252c 100644
--- a/opengl/include/GLES2/gl2ext.h
+++ b/opengl/include/GLES2/gl2ext.h
@@ -146,9 +146,12 @@
 #define GL_INT_10_10_10_2_OES                                   0x8DF7
 #endif
 
-/* GL_OES_texture_external */
-#ifndef GL_TEXTURE_EXTERNAL_OES
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
 #define GL_TEXTURE_EXTERNAL_OES                                 0x8D65
+#define GL_SAMPLER_EXTERNAL_OES                                 0x8D66
+#define GL_TEXTURE_BINDING_EXTERNAL_OES                         0x8D67
+#define GL_REQUIRED_TEXTURE_IMAGE_UNITS_OES                     0x8D68
 #endif
 
 /*------------------------------------------------------------------------*
@@ -546,9 +549,9 @@
 #define GL_OES_vertex_type_10_10_10_2 1
 #endif
 
-/* GL_OES_texture_external */
-#ifndef GL_OES_texture_external
-#define GL_OES_texture_external 1
+/* GL_OES_EGL_image_external */
+#ifndef GL_OES_EGL_image_external
+#define GL_OES_EGL_image_external 1
 #endif
 
 /*------------------------------------------------------------------------*
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 2ae34e3..43bb26a 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -74,4 +74,32 @@
     <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">
+            <!-- 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 c1ad1ca..8eb3fe6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -21,7 +21,6 @@
 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,10 +34,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;
@@ -64,7 +60,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 = 58;
 
     private Context mContext;
 
@@ -734,6 +730,33 @@
             }
             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;
+        }
+
         // *** Remember to update DATABASE_VERSION above!
 
         if (upgradeVersion != currentVersion) {
@@ -876,7 +899,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);
                 }
@@ -1187,6 +1210,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/res/drawable-hdpi/stat_sys_r_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_0_fully.png
new file mode 100644
index 0000000..95ba181
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_r_signal_0_fully.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
new file mode 100644
index 0000000..adf668d
--- /dev/null
+++ 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
new file mode 100644
index 0000000..7bf6b51
--- /dev/null
+++ 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
new file mode 100644
index 0000000..78738ac
--- /dev/null
+++ 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
new file mode 100644
index 0000000..ac88143
--- /dev/null
+++ 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_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
new file mode 100644
index 0000000..3e317dd
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
new file mode 100644
index 0000000..72329f8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
new file mode 100644
index 0000000..558c49c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
new file mode 100644
index 0000000..6440bdd
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
new file mode 100644
index 0000000..fe20423
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_0.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_0.png
deleted file mode 100644
index bfbf18e..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_0.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_1.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_1.png
deleted file mode 100644
index 896ba4d..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_1.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_2.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_2.png
deleted file mode 100644
index af79eff..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_2.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_3.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_3.png
deleted file mode 100644
index 92c09c8..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_3.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_4.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_4.png
deleted file mode 100644
index f04fb11..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_r_signal_4.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png
deleted file mode 100644
index cb7b7b3..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png
deleted file mode 100644
index 5376e92..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png
deleted file mode 100644
index fd54363..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png
deleted file mode 100644
index 6c4873a..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png
deleted file mode 100644
index a3320cb..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png
deleted file mode 100755
index 2f4fd4f..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png
deleted file mode 100644
index 5aa23f6..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
index 6838dda..9cc24be 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
@@ -163,6 +163,18 @@
                 });
             }
         }
+
+        public void onInvalidateDetailTexture(int n) {
+
+        }
+
+        public void onRequestDetailTexture(int n) {
+
+        }
+
+        public void onReportFirstCardPosition(int n) {
+
+        }
     };
 
     private final IThumbnailReceiver mThumbnailReceiver = new IThumbnailReceiver.Stub() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
index b4e0d3a..7ccf210 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
@@ -120,19 +120,29 @@
 
     //***** Signal strength icons
     //GSM/UMTS
-    private static final int[] sSignalImages = new int[] {
-        R.drawable.stat_sys_signal_0,
-        R.drawable.stat_sys_signal_1,
-        R.drawable.stat_sys_signal_2,
-        R.drawable.stat_sys_signal_3,
-        R.drawable.stat_sys_signal_4
+    private static final int[][] sSignalImages = {
+        { R.drawable.stat_sys_signal_0,
+          R.drawable.stat_sys_signal_1,
+          R.drawable.stat_sys_signal_2,
+          R.drawable.stat_sys_signal_3,
+          R.drawable.stat_sys_signal_4 },
+        { R.drawable.stat_sys_signal_0_fully,
+          R.drawable.stat_sys_signal_1_fully,
+          R.drawable.stat_sys_signal_2_fully,
+          R.drawable.stat_sys_signal_3_fully,
+          R.drawable.stat_sys_signal_4_fully }
     };
-    private static final int[] sSignalImages_r = new int[] {
-        R.drawable.stat_sys_r_signal_0,
-        R.drawable.stat_sys_r_signal_1,
-        R.drawable.stat_sys_r_signal_2,
-        R.drawable.stat_sys_r_signal_3,
-        R.drawable.stat_sys_r_signal_4
+    private static final int[][] sSignalImages_r = {
+        { R.drawable.stat_sys_r_signal_0,
+          R.drawable.stat_sys_r_signal_1,
+          R.drawable.stat_sys_r_signal_2,
+          R.drawable.stat_sys_r_signal_3,
+          R.drawable.stat_sys_r_signal_4 },
+        { R.drawable.stat_sys_r_signal_0_fully,
+          R.drawable.stat_sys_r_signal_1_fully,
+          R.drawable.stat_sys_r_signal_2_fully,
+          R.drawable.stat_sys_r_signal_3_fully,
+          R.drawable.stat_sys_r_signal_4_fully }
     };
     private static final int[] sRoamingIndicatorImages_cdma = new int[] {
         R.drawable.stat_sys_roaming_cdma_0, //Standard Roaming Indicator
@@ -330,7 +340,9 @@
 
     private int mLastWifiSignalLevel = -1;
     private boolean mIsWifiConnected = false;
-    private int mLastWifiInetConnectivityState = 0;
+
+    // state of inet connection - 0 not connected, 100 connected
+    private int mInetCondition = 0;
 
     // sync state
     // If sync is active the SyncActive icon is displayed. If sync is not active but
@@ -381,7 +393,8 @@
             else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) {
                 updateTTY(intent);
             }
-            else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+            else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
+                     action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
                 // TODO - stop using other means to get wifi/mobile info
                 updateConnectivity(intent);
             }
@@ -489,6 +502,7 @@
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
 
         // load config to determine if to distinguish Hspa data icon
@@ -704,34 +718,34 @@
             if (info.isConnected()) {
                 updateDataNetType(info.getSubtype(), connectionStatus);
                 updateDataIcon();
+                updateSignalStrength(); // apply any change in connectionStatus
             }
             break;
         case ConnectivityManager.TYPE_WIFI:
             if (info.isConnected()) {
                 mIsWifiConnected = true;
-                mLastWifiInetConnectivityState =
+                mInetCondition =
                         (connectionStatus > INET_CONDITION_THRESHOLD ? 1 : 0);
                 int iconId;
                 if (mLastWifiSignalLevel == -1) {
-                    iconId = sWifiSignalImages[mLastWifiInetConnectivityState][0];
+                    iconId = sWifiSignalImages[mInetCondition][0];
                 } else {
-                    iconId = sWifiSignalImages[mLastWifiInetConnectivityState]
-                            [mLastWifiSignalLevel];
+                    iconId = sWifiSignalImages[mInetCondition][mLastWifiSignalLevel];
                 }
-
                 mService.setIcon("wifi", iconId, 0);
                 // Show the icon since wi-fi is connected
                 mService.setIconVisibility("wifi", true);
             } else {
                 mLastWifiSignalLevel = -1;
                 mIsWifiConnected = false;
-                mLastWifiInetConnectivityState = 0;
+                mInetCondition = 0;
                 int iconId = sWifiSignalImages[0][0];
 
                 mService.setIcon("wifi", iconId, 0);
                 // Hide the icon since we're not connected
                 mService.setIconVisibility("wifi", false);
             }
+            updateSignalStrength(); // apply any change in mInetCondition
             break;
         }
     }
@@ -765,6 +779,7 @@
             mDataState = state;
             updateDataNetType(networkType, 0);
             updateDataIcon();
+            updateSignalStrength(); // apply the change in connection status
         }
 
         @Override
@@ -859,12 +874,12 @@
 
             // Though mPhone is a Manager, this call is not an IPC
             if (mPhone.isNetworkRoaming()) {
-                iconList = sSignalImages_r;
+                iconList = sSignalImages_r[mInetCondition];
             } else {
-                iconList = sSignalImages;
+                iconList = sSignalImages[mInetCondition];
             }
         } else {
-            iconList = this.sSignalImages;
+            iconList = sSignalImages[mInetCondition];
 
             // If 3G(EV) and 1x network are available than 3G should be
             // displayed, displayed RSSI should be from the EV side.
@@ -926,37 +941,37 @@
     }
 
     private final void updateDataNetType(int net, int inetCondition) {
-        int connected = (inetCondition > INET_CONDITION_THRESHOLD ? 1 : 0);
+        mInetCondition = (inetCondition > INET_CONDITION_THRESHOLD ? 1 : 0);
         switch (net) {
         case TelephonyManager.NETWORK_TYPE_EDGE:
-            mDataIconList = sDataNetType_e[connected];
+            mDataIconList = sDataNetType_e[mInetCondition];
             break;
         case TelephonyManager.NETWORK_TYPE_UMTS:
-            mDataIconList = sDataNetType_3g[connected];
+            mDataIconList = sDataNetType_3g[mInetCondition];
             break;
         case TelephonyManager.NETWORK_TYPE_HSDPA:
         case TelephonyManager.NETWORK_TYPE_HSUPA:
         case TelephonyManager.NETWORK_TYPE_HSPA:
             if (mHspaDataDistinguishable) {
-                mDataIconList = sDataNetType_h[connected];
+                mDataIconList = sDataNetType_h[mInetCondition];
             } else {
-                mDataIconList = sDataNetType_3g[connected];
+                mDataIconList = sDataNetType_3g[mInetCondition];
             }
             break;
         case TelephonyManager.NETWORK_TYPE_CDMA:
             // display 1xRTT for IS95A/B
-            mDataIconList = sDataNetType_1x[connected];
+            mDataIconList = sDataNetType_1x[mInetCondition];
             break;
         case TelephonyManager.NETWORK_TYPE_1xRTT:
-            mDataIconList = sDataNetType_1x[connected];
+            mDataIconList = sDataNetType_1x[mInetCondition];
             break;
         case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through
         case TelephonyManager.NETWORK_TYPE_EVDO_A:
         case TelephonyManager.NETWORK_TYPE_EVDO_B:
-            mDataIconList = sDataNetType_3g[connected];
+            mDataIconList = sDataNetType_3g[mInetCondition];
             break;
         default:
-            mDataIconList = sDataNetType_g[connected];
+            mDataIconList = sDataNetType_g[mInetCondition];
         break;
         }
     }
@@ -1101,11 +1116,11 @@
             int iconId;
             final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
             int newSignalLevel = WifiManager.calculateSignalLevel(newRssi,
-                                                                  sWifiSignalImages.length);
+                                                                  sWifiSignalImages[0].length);
             if (newSignalLevel != mLastWifiSignalLevel) {
                 mLastWifiSignalLevel = newSignalLevel;
                 if (mIsWifiConnected) {
-                    iconId = sWifiSignalImages[mLastWifiInetConnectivityState][newSignalLevel];
+                    iconId = sWifiSignalImages[mInetCondition][newSignalLevel];
                 } else {
                     iconId = sWifiTemporarilyNotConnectedImage;
                 }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index ca9a484..c25df1d 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1407,18 +1407,20 @@
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN: {
-                AudioManager audioManager = (AudioManager) getContext().getSystemService(
-                        Context.AUDIO_SERVICE);
-                if (audioManager != null) {
-                    /*
-                     * Play a sound. This is done on key up since we don't want the
-                     * sound to play when a user holds down volume down to mute.
-                     */
-                    audioManager.adjustSuggestedStreamVolume(
-                            AudioManager.ADJUST_SAME,
-                            mVolumeControlStreamType,
-                            AudioManager.FLAG_PLAY_SOUND);
-                    mVolumeKeyUpTime = SystemClock.uptimeMillis();
+                if (!event.isCanceled()) {
+                    AudioManager audioManager = (AudioManager) getContext().getSystemService(
+                            Context.AUDIO_SERVICE);
+                    if (audioManager != null) {
+                        /*
+                         * Play a sound. This is done on key up since we don't want the
+                         * sound to play when a user holds down volume down to mute.
+                         */
+                        audioManager.adjustSuggestedStreamVolume(
+                                AudioManager.ADJUST_SAME,
+                                mVolumeControlStreamType,
+                                AudioManager.FLAG_PLAY_SOUND);
+                        mVolumeKeyUpTime = SystemClock.uptimeMillis();
+                    }
                 }
                 return true;
             }
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 3770b55..886c25b 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1752,14 +1752,15 @@
             }
 
             // compute volume for this track
-            int16_t left, right, aux;
+            uint32_t vl, vr, va;
             if (track->isMuted() || track->isPausing() ||
                 mStreamTypes[track->type()].mute) {
-                left = right = aux = 0;
+                vl = vr = va = 0;
                 if (track->isPausing()) {
                     track->setPaused();
                 }
             } else {
+
                 // read original volumes with volume control
                 float typeVolume = mStreamTypes[track->type()].volume;
 #ifdef LVMX
@@ -1774,35 +1775,36 @@
                 }
 #endif
                 float v = masterVolume * typeVolume;
-                uint32_t vl = (uint32_t)(v * cblk->volume[0]) << 12;
-                uint32_t vr = (uint32_t)(v * cblk->volume[1]) << 12;
+                vl = (uint32_t)(v * cblk->volume[0]) << 12;
+                vr = (uint32_t)(v * cblk->volume[1]) << 12;
 
-                // Delegate volume control to effect in track effect chain if needed
-                if (chain != 0 && chain->setVolume_l(&vl, &vr)) {
-                    // Do not ramp volume is volume is controlled by effect
-                    param = AudioMixer::VOLUME;
-                    track->mHasVolumeController = true;
-                } else {
-                    // force no volume ramp when volume controller was just disabled or removed
-                    // from effect chain to avoid volume spike
-                    if (track->mHasVolumeController) {
-                        param = AudioMixer::VOLUME;
-                    }
-                    track->mHasVolumeController = false;
-                }
-
-                // Convert volumes from 8.24 to 4.12 format
-                uint32_t v_clamped = (vl + (1 << 11)) >> 12;
-                if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT;
-                left = int16_t(v_clamped);
-                v_clamped = (vr + (1 << 11)) >> 12;
-                if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT;
-                right = int16_t(v_clamped);
-
-                v_clamped = (uint32_t)(v * cblk->sendLevel);
-                if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT;
-                aux = int16_t(v_clamped);
+                va = (uint32_t)(v * cblk->sendLevel);
             }
+            // Delegate volume control to effect in track effect chain if needed
+            if (chain != 0 && chain->setVolume_l(&vl, &vr)) {
+                // Do not ramp volume if volume is controlled by effect
+                param = AudioMixer::VOLUME;
+                track->mHasVolumeController = true;
+            } else {
+                // force no volume ramp when volume controller was just disabled or removed
+                // from effect chain to avoid volume spike
+                if (track->mHasVolumeController) {
+                    param = AudioMixer::VOLUME;
+                }
+                track->mHasVolumeController = false;
+            }
+
+            // Convert volumes from 8.24 to 4.12 format
+            int16_t left, right, aux;
+            uint32_t v_clamped = (vl + (1 << 11)) >> 12;
+            if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT;
+            left = int16_t(v_clamped);
+            v_clamped = (vr + (1 << 11)) >> 12;
+            if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT;
+            right = int16_t(v_clamped);
+
+            if (va > MAX_GAIN_INT) va = MAX_GAIN_INT;
+            aux = int16_t(va);
 
 #ifdef LVMX
             if ( tracksConnectedChanged || stateChanged )
@@ -2283,7 +2285,7 @@
                         // only one effect chain can be present on DirectOutputThread, so if
                         // there is one, the track is connected to it
                         if (!effectChains.isEmpty()) {
-                            // Do not ramp volume is volume is controlled by effect
+                            // Do not ramp volume if volume is controlled by effect
                             if(effectChains[0]->setVolume_l(&vl, &vr)) {
                                 rampVolume = false;
                             }
@@ -4728,7 +4730,7 @@
         // Session AudioSystem::SESSION_OUTPUT_STAGE is reserved for output stage effects
         // that can only be created by audio policy manager (running in same process)
         if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE &&
-                getpid() != IPCThreadState::self()->getCallingPid()) {
+                getpid() != pid) {
             lStatus = INVALID_OPERATION;
             goto Exit;
         }
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index e5889bf..f943a10 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>
@@ -319,6 +320,7 @@
     // Callback is disabled by default
     mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP;
     mOrientation = 0;
+    mPlayShutterSound = true;
     cameraService->setCameraBusy(cameraId);
     cameraService->loadSound();
     LOG1("Client::Client X (pid %d)", callingPid);
@@ -769,6 +771,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());
     Mutex::Autolock lock(mLock);
@@ -797,6 +828,20 @@
                 return BAD_VALUE;
         }
         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);
@@ -957,7 +1002,9 @@
 // "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);
+    if (mPlayShutterSound) {
+        mCameraService->playSound(SOUND_SHUTTER);
+    }
 
     sp<ICameraClient> c = mCameraClient;
     if (c != 0) {
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 7ed192e..2b5c511 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -138,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);
@@ -171,6 +174,7 @@
         int                             mOverlayH;
         int                             mPreviewCallbackFlag;
         int                             mOrientation;
+        bool                            mPlayShutterSound;
 
         // Ensures atomicity among the public methods
         mutable Mutex                   mLock;
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index e454c08..aa87f29 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -54,6 +54,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Slog;
@@ -504,7 +505,7 @@
         parseLeftoverJournals();
 
         // Power management
-        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "backup");
+        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
 
         // Start the backup passes going
         setBackupEnabled(areEnabled);
@@ -1363,6 +1364,7 @@
                         ? IApplicationThread.BACKUP_MODE_FULL
                         : IApplicationThread.BACKUP_MODE_INCREMENTAL;
                 try {
+                    mWakelock.setWorkSource(new WorkSource(request.appInfo.uid));
                     agent = bindToAgentSynchronous(request.appInfo, mode);
                     if (agent != null) {
                         int result = processOneBackup(request, agent, transport);
@@ -1378,6 +1380,8 @@
                 }
             }
 
+            mWakelock.setWorkSource(null);
+
             return BackupConstants.TRANSPORT_OK;
         }
 
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 96a239d..6935e92 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -1069,7 +1069,15 @@
     }
 
     private void sendConnectedBroadcast(NetworkInfo info) {
-        Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+        sendGeneralBroadcast(info, ConnectivityManager.CONNECTIVITY_ACTION);
+    }
+
+    private void sendInetConditionBroadcast(NetworkInfo info) {
+        sendGeneralBroadcast(info, ConnectivityManager.INET_CONDITION_ACTION);
+    }
+
+    private void sendGeneralBroadcast(NetworkInfo info, String bcastType) {
+        Intent intent = new Intent(bcastType);
         intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
         if (info.isFailover()) {
             intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
@@ -1713,7 +1721,7 @@
                         break;
                     }
                     mDefaultInetConditionPublished = mDefaultInetCondition;
-                    sendConnectedBroadcast(networkInfo);
+                    sendInetConditionBroadcast(networkInfo);
                     break;
             }
         }
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index 314dd8a..024aec5 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -24,18 +24,13 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.os.Environment;
-import android.os.LocalPowerManager;
-import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.util.Slog;
 import android.util.Xml;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.Surface;
-import android.view.WindowManagerPolicy;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -83,7 +78,6 @@
     private static native void nativeSetInputWindows(InputWindow[] windows);
     private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen);
     private static native void nativeSetFocusedApplication(InputApplication application);
-    private static native void nativePreemptInputDispatch();
     private static native InputDevice nativeGetInputDevice(int deviceId);
     private static native int[] nativeGetInputDeviceIds();
     private static native String nativeDump();
@@ -331,10 +325,6 @@
         nativeSetFocusedApplication(application);
     }
     
-    public void preemptInputDispatch() {
-        nativePreemptInputDispatch();
-    }
-    
     public void setInputDispatchMode(boolean enabled, boolean frozen) {
         nativeSetInputDispatchMode(enabled, frozen);
     }
@@ -395,20 +385,10 @@
         public void notifyInputChannelBroken(InputChannel inputChannel) {
             mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputChannel);
         }
-
-        @SuppressWarnings("unused")
-        public long notifyInputChannelANR(InputChannel inputChannel) {
-            return mWindowManagerService.mInputMonitor.notifyInputChannelANR(inputChannel);
-        }
-
-        @SuppressWarnings("unused")
-        public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) {
-            mWindowManagerService.mInputMonitor.notifyInputChannelRecoveredFromANR(inputChannel);
-        }
         
         @SuppressWarnings("unused")
-        public long notifyANR(Object token) {
-            return mWindowManagerService.mInputMonitor.notifyANR(token);
+        public long notifyANR(Object token, InputChannel inputChannel) {
+            return mWindowManagerService.mInputMonitor.notifyANR(token, inputChannel);
         }
         
         @SuppressWarnings("unused")
@@ -433,11 +413,6 @@
         }
         
         @SuppressWarnings("unused")
-        public void notifyAppSwitchComing() {
-            mWindowManagerService.mInputMonitor.notifyAppSwitchComing();
-        }
-        
-        @SuppressWarnings("unused")
         public boolean filterTouchEvents() {
             return mContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_filterTouchEvents);
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index c61baad..9efc708 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1751,24 +1751,28 @@
             p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
         }
 
+        p.println(" ");
         if (client != null) {
-            p.println(" ");
             pw.flush();
             try {
                 client.client.asBinder().dump(fd, args);
             } catch (RemoteException e) {
                 p.println("Input method client dead: " + e);
             }
+        } else {
+            p.println("No input method client.");
         }
 
+        p.println(" ");
         if (method != null) {
-            p.println(" ");
             pw.flush();
             try {
                 method.asBinder().dump(fd, args);
             } catch (RemoteException e) {
                 p.println("Input method service dead: " + e);
             }
+        } else {
+            p.println("No input method service.");
         }
     }
 }
diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/InputWindow.java
index dbc59ef..befc770 100644
--- a/services/java/com/android/server/InputWindow.java
+++ b/services/java/com/android/server/InputWindow.java
@@ -27,6 +27,9 @@
     // The input channel associated with the window.
     public InputChannel inputChannel;
     
+    // The window name.
+    public String name;
+    
     // Window layout params attributes.  (WindowManager.LayoutParams)
     public int layoutParamsFlags;
     public int layoutParamsType;
@@ -55,6 +58,9 @@
     // Window is visible.
     public boolean visible;
     
+    // Window can receive keys.
+    public boolean canReceiveKeys;
+    
     // Window has focus.
     public boolean hasFocus;
     
@@ -64,6 +70,9 @@
     // Input event dispatching is paused.
     public boolean paused;
     
+    // Window layer.
+    public int layer;
+    
     // Id of process and user that owns the window.
     public int ownerPid;
     public int ownerUid;
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index a343c59..8452a9f 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -52,6 +52,7 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Slog;
@@ -157,6 +158,12 @@
     private final HashMap<String,ArrayList<UpdateRecord>> mRecordsByProvider =
         new HashMap<String,ArrayList<UpdateRecord>>();
 
+    /**
+     * Temporary filled in when computing min time for a provider.  Access is
+     * protected by global lock mLock.
+     */
+    private final WorkSource mTmpWorkSource = new WorkSource();
+
     // Proximity listeners
     private Receiver mProximityReceiver = null;
     private ILocationListener mProximityListener = null;
@@ -913,7 +920,7 @@
         if (enabled) {
             p.enable();
             if (listeners > 0) {
-                p.setMinTime(getMinTimeLocked(provider));
+                p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource);
                 p.enableLocationTracking(true);
             }
         } else {
@@ -925,9 +932,21 @@
     private long getMinTimeLocked(String provider) {
         long minTime = Long.MAX_VALUE;
         ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+        mTmpWorkSource.clear();
         if (records != null) {
             for (int i=records.size()-1; i>=0; i--) {
-                minTime = Math.min(minTime, records.get(i).mMinTime);
+                UpdateRecord ur = records.get(i);
+                long curTime = ur.mMinTime;
+                if (curTime < minTime) {
+                    minTime = curTime;
+                }
+            }
+            long inclTime = (minTime*3)/2;
+            for (int i=records.size()-1; i>=0; i--) {
+                UpdateRecord ur = records.get(i);
+                if (ur.mMinTime <= inclTime) {
+                    mTmpWorkSource.add(ur.mUid);
+                }
             }
         }
         return minTime;
@@ -1125,7 +1144,7 @@
             boolean isProviderEnabled = isAllowedBySettingsLocked(provider);
             if (isProviderEnabled) {
                 long minTimeForProvider = getMinTimeLocked(provider);
-                p.setMinTime(minTimeForProvider);
+                p.setMinTime(minTimeForProvider, mTmpWorkSource);
                 // try requesting single shot if singleShot is true, and fall back to
                 // regular location tracking if requestSingleShotFix() is not supported
                 if (!singleShot || !p.requestSingleShotFix()) {
@@ -1223,7 +1242,7 @@
                 LocationProviderInterface p = mProvidersByName.get(provider);
                 if (p != null) {
                     if (hasOtherListener) {
-                        p.setMinTime(getMinTimeLocked(provider));
+                        p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource);
                     } else {
                         p.enableLocationTracking(false);
                     }
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index c07007a..32f5e73 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -832,6 +832,15 @@
         if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
             return VoldResponseCode.OpFailedVolNotMounted;
         }
+
+        /*
+         * Force a GC to make sure AssetManagers in other threads of the
+         * system_server are cleaned up. We have to do this since AssetManager
+         * instances are kept as a WeakReference and it's possible we have files
+         * open on the external storage.
+         */
+        Runtime.getRuntime().gc();
+
         // Redundant probably. But no harm in updating state again.
         mPms.updateExternalMediaStatus(false, false);
         try {
@@ -1290,6 +1299,14 @@
         waitForReady();
         warnOnNotMounted();
 
+        /*
+         * Force a GC to make sure AssetManagers in other threads of the
+         * system_server are cleaned up. We have to do this since AssetManager
+         * instances are kept as a WeakReference and it's possible we have files
+         * open on the external storage.
+         */
+        Runtime.getRuntime().gc();
+
         int rc = StorageResultCode.OperationSucceeded;
         try {
             mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : "")));
@@ -1354,6 +1371,14 @@
             }
          }
 
+        /*
+         * Force a GC to make sure AssetManagers in other threads of the
+         * system_server are cleaned up. We have to do this since AssetManager
+         * instances are kept as a WeakReference and it's possible we have files
+         * open on the external storage.
+         */
+        Runtime.getRuntime().gc();
+
         int rc = StorageResultCode.OperationSucceeded;
         String cmd = String.format("asec unmount %s%s", id, (force ? " force" : ""));
         try {
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/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index f6d92b5..1069216 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -2546,7 +2546,7 @@
         if (GET_CERTIFICATES) {
             if (ps != null
                     && ps.codePath.equals(srcFile)
-                    && ps.getTimeStamp() == srcFile.lastModified()) {
+                    && ps.timeStamp == srcFile.lastModified()) {
                 if (ps.signatures.mSignatures != null
                         && ps.signatures.mSignatures.length != 0) {
                     // Optimization: reuse the existing cached certificates
@@ -3026,7 +3026,8 @@
             // Just create the setting, don't add it yet. For already existing packages
             // the PkgSetting exists already and doesn't have to be created.
             pkgSetting = mSettings.getPackageLP(pkg, origPackage, realName, suid, destCodeFile,
-                            destResourceFile, pkg.applicationInfo.flags, true, false);
+                    destResourceFile, pkg.applicationInfo.nativeLibraryDir,
+                    pkg.applicationInfo.flags, true, false);
             if (pkgSetting == null) {
                 Slog.w(TAG, "Creating application package " + pkg.packageName + " failed");
                 mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
@@ -3138,7 +3139,7 @@
         
         long scanFileTime = scanFile.lastModified();
         final boolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0;
-        final boolean scanFileNewer = forceDex || scanFileTime != pkgSetting.getTimeStamp();
+        final boolean scanFileNewer = forceDex || scanFileTime != pkgSetting.timeStamp;
         pkg.applicationInfo.processName = fixProcessName(
                 pkg.applicationInfo.packageName,
                 pkg.applicationInfo.processName,
@@ -3244,14 +3245,21 @@
                 }
             }
 
+            pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString;
+
             /*
              * Set the data dir to the default "/data/data/<package name>/lib"
              * if we got here without anyone telling us different (e.g., apps
              * stored on SD card have their native libraries stored in the ASEC
              * container with the APK).
+             *
+             * This happens during an upgrade from a package settings file that
+             * doesn't have a native library path attribute at all.
              */
-            if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir != null) {
-                pkg.applicationInfo.nativeLibraryDir = new File(dataPath, LIB_DIR_NAME).getPath();
+            if (pkgSetting.nativeLibraryPathString == null && pkg.applicationInfo.dataDir != null) {
+                final String nativeLibraryPath = new File(dataPath, LIB_DIR_NAME).getPath();
+                pkg.applicationInfo.nativeLibraryDir = nativeLibraryPath;
+                pkgSetting.nativeLibraryPathString = nativeLibraryPath;
             }
 
             pkgSetting.uidError = uidError;
@@ -3273,7 +3281,6 @@
             if ((!isSystemApp(pkg) || isUpdatedSystemApp(pkg)) && !isExternal(pkg)) {
                 Log.i(TAG, path + " changed; unpacking");
                 File sharedLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);
-                sharedLibraryDir.mkdir();
                 NativeLibraryHelper.copyNativeBinariesLI(scanFile, sharedLibraryDir);
             }
             pkg.mScanPath = path;
@@ -3591,40 +3598,6 @@
         }
     }
 
-    // Convenience call for removeNativeBinariesLI(File)
-    private void removeNativeBinariesLI(PackageParser.Package pkg) {
-        File nativeLibraryDir = getNativeBinaryDirForPackage(pkg);
-        removeNativeBinariesLI(nativeLibraryDir);
-    }
-
-    // Remove the native binaries of a given package. This simply
-    // gets rid of the files in the 'lib' sub-directory.
-    public void removeNativeBinariesLI(File binaryDir) {
-        if (DEBUG_NATIVE) {
-            Slog.w(TAG, "Deleting native binaries from: " + binaryDir.getPath());
-        }
-
-        // Just remove any file in the directory. Since the directory
-        // is owned by the 'system' UID, the application is not supposed
-        // to have written anything there.
-        //
-        if (binaryDir.exists()) {
-            File[] binaries = binaryDir.listFiles();
-            if (binaries != null) {
-                for (int nn = 0; nn < binaries.length; nn++) {
-                    if (DEBUG_NATIVE) {
-                        Slog.d(TAG, "    Deleting " + binaries[nn].getName());
-                    }
-                    if (!binaries[nn].delete()) {
-                        Slog.w(TAG, "Could not delete native binary: " + binaries[nn].getPath());
-                    }
-                }
-            }
-            // Do not delete 'lib' directory itself, or this will prevent
-            // installation of future updates.
-        }
-    }
-
     void removePackageLI(PackageParser.Package pkg, boolean chatty) {
         if (chatty && Config.LOGD) Log.d(
             TAG, "Removing package " + pkg.applicationInfo.packageName );
@@ -5128,7 +5101,7 @@
                 }
             }
             if (libraryPath != null) {
-                removeNativeBinariesLI(new File(libraryPath));
+                NativeLibraryHelper.removeNativeBinariesLI(libraryPath);
             }
         }
 
@@ -6202,8 +6175,8 @@
         synchronized (mPackages) {
             // Reinstate the old system package
             mSettings.enableSystemPackageLP(p.packageName);
-            // Remove any native libraries. XXX needed?
-            removeNativeBinariesLI(p);
+            // Remove any native libraries from the upgraded package.
+            NativeLibraryHelper.removeNativeBinariesLI(p.applicationInfo.nativeLibraryDir);
         }
         // Install the system package
         PackageParser.Package newPkg = scanPackageLI(ps.codePath,
@@ -7034,7 +7007,7 @@
                         }
                     }
                     pw.println("]");
-                    pw.print("    timeStamp="); pw.println(ps.getTimeStampStr());
+                    pw.print("    timeStamp="); pw.println(String.valueOf(ps.timeStamp));
                     pw.print("    signatures="); pw.println(ps.signatures);
                     pw.print("    permissionsFixed="); pw.print(ps.permissionsFixed);
                             pw.print(" haveGids="); pw.println(ps.haveGids);
@@ -7559,8 +7532,7 @@
         String resourcePathString;
         String nativeLibraryPathString;
         String obbPathString;
-        private long timeStamp;
-        private String timeStampString = "0";
+        long timeStamp;
         int versionCode;
 
         boolean uidError;
@@ -7583,18 +7555,20 @@
         String installerPackageName;
 
         PackageSettingBase(String name, String realName, File codePath, File resourcePath,
-                int pVersionCode, int pkgFlags) {
+                String nativeLibraryPathString, int pVersionCode, int pkgFlags) {
             super(pkgFlags);
             this.name = name;
             this.realName = realName;
-            init(codePath, resourcePath, pVersionCode);
+            init(codePath, resourcePath, nativeLibraryPathString, pVersionCode);
         }
 
-        void init(File codePath, File resourcePath, int pVersionCode) {
+        void init(File codePath, File resourcePath, String nativeLibraryPathString,
+                int pVersionCode) {
             this.codePath = codePath;
             this.codePathString = codePath.toString();
             this.resourcePath = resourcePath;
             this.resourcePathString = resourcePath.toString();
+            this.nativeLibraryPathString = nativeLibraryPathString;
             this.versionCode = pVersionCode;
         }
 
@@ -7615,23 +7589,7 @@
         }
 
         public void setTimeStamp(long newStamp) {
-            if (newStamp != timeStamp) {
-                timeStamp = newStamp;
-                timeStampString = Long.toString(newStamp);
-            }
-        }
-
-        public void setTimeStamp(long newStamp, String newStampStr) {
             timeStamp = newStamp;
-            timeStampString = newStampStr;
-        }
-
-        public long getTimeStamp() {
-            return timeStamp;
-        }
-
-        public String getTimeStampStr() {
-            return timeStampString;
         }
 
         public void copyFrom(PackageSettingBase base) {
@@ -7639,7 +7597,6 @@
             gids = base.gids;
 
             timeStamp = base.timeStamp;
-            timeStampString = base.timeStampString;
             signatures = base.signatures;
             permissionsFixed = base.permissionsFixed;
             haveGids = base.haveGids;
@@ -7687,8 +7644,9 @@
         SharedUserSetting sharedUser;
 
         PackageSetting(String name, String realName, File codePath, File resourcePath,
-                int pVersionCode, int pkgFlags) {
-            super(name, realName, codePath, resourcePath, pVersionCode, pkgFlags);
+                String nativeLibraryPathString, int pVersionCode, int pkgFlags) {
+            super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode,
+                    pkgFlags);
         }
 
         @Override
@@ -7800,8 +7758,9 @@
             final int sharedId;
 
             PendingPackage(String name, String realName, File codePath, File resourcePath,
-                    int sharedId, int pVersionCode, int pkgFlags) {
-                super(name, realName, codePath, resourcePath, pVersionCode, pkgFlags);
+                    String nativeLibraryPathString, int sharedId, int pVersionCode, int pkgFlags) {
+                super(name, realName, codePath, resourcePath, nativeLibraryPathString,
+                        pVersionCode, pkgFlags);
                 this.sharedId = sharedId;
             }
         }
@@ -7830,10 +7789,10 @@
 
         PackageSetting getPackageLP(PackageParser.Package pkg, PackageSetting origPackage,
                 String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
-                int pkgFlags, boolean create, boolean add) {
+                String nativeLibraryPathString, int pkgFlags, boolean create, boolean add) {
             final String name = pkg.packageName;
             PackageSetting p = getPackageLP(name, origPackage, realName, sharedUser, codePath,
-                    resourcePath, pkg.mVersionCode, pkgFlags, create, add);
+                    resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags, create, add);
             return p;
         }
 
@@ -7929,14 +7888,14 @@
             if((p.pkg != null) && (p.pkg.applicationInfo != null)) {
                 p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
             }
-            PackageSetting ret = addPackageLP(name, p.realName, p.codePath,
-                    p.resourcePath, p.userId, p.versionCode, p.pkgFlags);
+            PackageSetting ret = addPackageLP(name, p.realName, p.codePath, p.resourcePath,
+                    p.nativeLibraryPathString, p.userId, p.versionCode, p.pkgFlags);
             mDisabledSysPackages.remove(name);
             return ret;
         }
 
-        PackageSetting addPackageLP(String name, String realName, File codePath,
-                File resourcePath, int uid, int vc, int pkgFlags) {
+        PackageSetting addPackageLP(String name, String realName, File codePath, File resourcePath,
+                String nativeLibraryPathString, int uid, int vc, int pkgFlags) {
             PackageSetting p = mPackages.get(name);
             if (p != null) {
                 if (p.userId == uid) {
@@ -7946,7 +7905,8 @@
                         "Adding duplicate package, keeping first: " + name);
                 return null;
             }
-            p = new PackageSetting(name, realName, codePath, resourcePath, vc, pkgFlags);
+            p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString,
+                    vc, pkgFlags);
             p.userId = uid;
             if (addUserIdLP(uid, p, name)) {
                 mPackages.put(name, p);
@@ -8001,7 +7961,7 @@
         
         private PackageSetting getPackageLP(String name, PackageSetting origPackage,
                 String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
-                int vc, int pkgFlags, boolean create, boolean add) {
+                String nativeLibraryPathString, int vc, int pkgFlags, boolean create, boolean add) {
             PackageSetting p = mPackages.get(name);
             if (p != null) {
                 if (!p.codePath.equals(codePath)) {
@@ -8044,8 +8004,8 @@
                 }
                 if (origPackage != null) {
                     // We are consuming the data from an existing package.
-                    p = new PackageSetting(origPackage.name, name, codePath,
-                            resourcePath, vc, pkgFlags);
+                    p = new PackageSetting(origPackage.name, name, codePath, resourcePath,
+                            nativeLibraryPathString, vc, pkgFlags);
                     if (DEBUG_UPGRADE) Log.v(TAG, "Package " + name
                             + " is adopting original package " + origPackage.name);
                     // Note that we will retain the new package's signature so
@@ -8061,7 +8021,8 @@
                     // Update new package state.
                     p.setTimeStamp(codePath.lastModified());
                 } else {
-                    p = new PackageSetting(name, realName, codePath, resourcePath, vc, pkgFlags);
+                    p = new PackageSetting(name, realName, codePath, resourcePath,
+                            nativeLibraryPathString, vc, pkgFlags);
                     p.setTimeStamp(codePath.lastModified());
                     p.sharedUser = sharedUser;
                     if (sharedUser != null) {
@@ -8497,7 +8458,7 @@
                 serializer.attribute(null, "realName", pkg.realName);
             }
             serializer.attribute(null, "codePath", pkg.codePathString);
-            serializer.attribute(null, "ts", pkg.getTimeStampStr());
+            serializer.attribute(null, "ts", String.valueOf(pkg.timeStamp));
             serializer.attribute(null, "version", String.valueOf(pkg.versionCode));
             if (!pkg.resourcePathString.equals(pkg.codePathString)) {
                 serializer.attribute(null, "resourcePath", pkg.resourcePathString);
@@ -8549,7 +8510,7 @@
             }
             serializer.attribute(null, "flags",
                     Integer.toString(pkg.pkgFlags));
-            serializer.attribute(null, "ts", pkg.getTimeStampStr());
+            serializer.attribute(null, "ts", String.valueOf(pkg.timeStamp));
             serializer.attribute(null, "version", String.valueOf(pkg.versionCode));
             if (pkg.sharedUser == null) {
                 serializer.attribute(null, "userId",
@@ -8782,8 +8743,8 @@
                 Object idObj = getUserIdLP(pp.sharedId);
                 if (idObj != null && idObj instanceof SharedUserSetting) {
                     PackageSetting p = getPackageLP(pp.name, null, pp.realName,
-                            (SharedUserSetting)idObj, pp.codePath, pp.resourcePath,
-                            pp.versionCode, pp.pkgFlags, true, true);
+                            (SharedUserSetting) idObj, pp.codePath, pp.resourcePath,
+                            pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags, true, true);
                     if (p == null) {
                         Slog.w(TAG, "Unable to create application package for "
                                 + pp.name);
@@ -8888,6 +8849,7 @@
             String realName = parser.getAttributeValue(null, "realName");
             String codePathStr = parser.getAttributeValue(null, "codePath");
             String resourcePathStr = parser.getAttributeValue(null, "resourcePath");
+            String nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath");
             if (resourcePathStr == null) {
                 resourcePathStr = codePathStr;
             }
@@ -8902,14 +8864,13 @@
 
             int pkgFlags = 0;
             pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
-            PackageSetting ps = new PackageSetting(name, realName,
-                    new File(codePathStr),
-                    new File(resourcePathStr), versionCode, pkgFlags);
+            PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
+                    new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags);
             String timeStampStr = parser.getAttributeValue(null, "ts");
             if (timeStampStr != null) {
                 try {
                     long timeStamp = Long.parseLong(timeStampStr);
-                    ps.setTimeStamp(timeStamp, timeStampStr);
+                    ps.setTimeStamp(timeStamp);
                 } catch (NumberFormatException e) {
                 }
             }
@@ -8957,7 +8918,6 @@
             String installerPackageName = null;
             String uidError = null;
             int pkgFlags = 0;
-            String timeStampStr;
             long timeStamp = 0;
             PackageSettingBase packageSetting = null;
             String version = null;
@@ -8998,7 +8958,7 @@
                         pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
                     }
                 }
-                timeStampStr = parser.getAttributeValue(null, "ts");
+                final String timeStampStr = parser.getAttributeValue(null, "ts");
                 if (timeStampStr != null) {
                     try {
                         timeStamp = Long.parseLong(timeStampStr);
@@ -9023,9 +8983,9 @@
                             "Error in package manager settings: <package> has no codePath at "
                             + parser.getPositionDescription());
                 } else if (userId > 0) {
-                    packageSetting = addPackageLP(name.intern(), realName,
-                            new File(codePathStr), new File(resourcePathStr),
-                            userId, versionCode, pkgFlags);
+                    packageSetting = addPackageLP(name.intern(), realName, new File(codePathStr),
+                            new File(resourcePathStr), nativeLibraryPathStr, userId, versionCode,
+                            pkgFlags);
                     if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name
                             + ": userId=" + userId + " pkg=" + packageSetting);
                     if (packageSetting == null) {
@@ -9034,7 +8994,7 @@
                                 + " while parsing settings at "
                                 + parser.getPositionDescription());
                     } else {
-                        packageSetting.setTimeStamp(timeStamp, timeStampStr);
+                        packageSetting.setTimeStamp(timeStamp);
                     }
                 } else if (sharedIdStr != null) {
                     userId = sharedIdStr != null
@@ -9042,8 +9002,8 @@
                     if (userId > 0) {
                         packageSetting = new PendingPackage(name.intern(), realName,
                                 new File(codePathStr), new File(resourcePathStr),
-                                userId, versionCode, pkgFlags);
-                        packageSetting.setTimeStamp(timeStamp, timeStampStr);
+                                nativeLibraryPathStr, userId, versionCode, pkgFlags);
+                        packageSetting.setTimeStamp(timeStamp);
                         mPendingPackages.add((PendingPackage) packageSetting);
                         if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name
                                 + ": sharedUserId=" + userId + " pkg="
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 2e32e60..023da46 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -50,6 +50,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
 import android.provider.Settings;
 import android.util.EventLog;
@@ -310,7 +311,7 @@
                 long ident = Binder.clearCallingIdentity();
                 try {
                     PowerManagerService.this.acquireWakeLockLocked(mFlags, mToken,
-                            MY_UID, MY_PID, mTag);
+                            MY_UID, MY_PID, mTag, null);
                     mHeld = true;
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -607,6 +608,7 @@
         final int uid;
         final int pid;
         final int monitorType;
+        WorkSource ws;
         boolean activated = true;
         int minState;
     }
@@ -630,35 +632,84 @@
                 || n == PowerManager.SCREEN_DIM_WAKE_LOCK;
     }
 
-    public void acquireWakeLock(int flags, IBinder lock, String tag) {
+    void enforceWakeSourcePermission(int uid, int pid) {
+        if (uid == Process.myUid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+                pid, uid, null);
+    }
+
+    public void acquireWakeLock(int flags, IBinder lock, String tag, WorkSource ws) {
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
         if (uid != Process.myUid()) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
         }
+        if (ws != null) {
+            enforceWakeSourcePermission(uid, pid);
+        }
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLocks) {
-                acquireWakeLockLocked(flags, lock, uid, pid, tag);
+                acquireWakeLockLocked(flags, lock, uid, pid, tag, ws);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
-    public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag) {
-        int acquireUid = -1;
-        int acquirePid = -1;
-        String acquireName = null;
-        int acquireType = -1;
+    void noteStartWakeLocked(WakeLock wl, WorkSource ws) {
+        if (wl.monitorType >= 0) {
+            long origId = Binder.clearCallingIdentity();
+            try {
+                if (ws != null) {
+                    mBatteryStats.noteStartWakelockFromSource(ws, wl.pid, wl.tag,
+                            wl.monitorType);
+                } else {
+                    mBatteryStats.noteStartWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType);
+                }
+            } catch (RemoteException e) {
+                // Ignore
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
 
+    void noteStopWakeLocked(WakeLock wl, WorkSource ws) {
+        if (wl.monitorType >= 0) {
+            long origId = Binder.clearCallingIdentity();
+            try {
+                if (ws != null) {
+                    mBatteryStats.noteStopWakelockFromSource(ws, wl.pid, wl.tag,
+                            wl.monitorType);
+                } else {
+                    mBatteryStats.noteStopWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType);
+                }
+            } catch (RemoteException e) {
+                // Ignore
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag,
+            WorkSource ws) {
         if (mSpew) {
             Slog.d(TAG, "acquireWakeLock flags=0x" + Integer.toHexString(flags) + " tag=" + tag);
         }
 
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+
         int index = mLocks.getIndex(lock);
         WakeLock wl;
         boolean newlock;
+        boolean diffsource;
+        WorkSource oldsource;
         if (index < 0) {
             wl = new WakeLock(flags, lock, tag, uid, pid);
             switch (wl.flags & LOCK_MASK)
@@ -687,10 +738,31 @@
                     return;
             }
             mLocks.addLock(wl);
+            if (ws != null) {
+                wl.ws = new WorkSource(ws);
+            }
             newlock = true;
+            diffsource = false;
+            oldsource = null;
         } else {
             wl = mLocks.get(index);
             newlock = false;
+            oldsource = wl.ws;
+            if (oldsource != null) {
+                if (ws == null) {
+                    wl.ws = null;
+                    diffsource = true;
+                } else {
+                    diffsource = oldsource.diff(ws);
+                }
+            } else if (ws != null) {
+                diffsource = true;
+            } else {
+                diffsource = false;
+            }
+            if (diffsource) {
+                wl.ws = new WorkSource(ws);
+            }
         }
         if (isScreenLock(flags)) {
             // if this causes a wakeup, we reactivate all of the locks and
@@ -731,19 +803,36 @@
                 enableProximityLockLocked();
             }
         }
-        if (newlock) {
-            acquireUid = wl.uid;
-            acquirePid = wl.pid;
-            acquireName = wl.tag;
-            acquireType = wl.monitorType;
-        }
 
-        if (acquireType >= 0) {
-            try {
-                mBatteryStats.noteStartWakelock(acquireUid, acquirePid, acquireName, acquireType);
-            } catch (RemoteException e) {
-                // Ignore
+        if (diffsource) {
+            // If the lock sources have changed, need to first release the
+            // old ones.
+            noteStopWakeLocked(wl, oldsource);
+        }
+        if (newlock || diffsource) {
+            noteStartWakeLocked(wl, ws);
+        }
+    }
+
+    public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) {
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+        if (ws != null) {
+            enforceWakeSourcePermission(uid, pid);
+        }
+        synchronized (mLocks) {
+            int index = mLocks.getIndex(lock);
+            if (index < 0) {
+                throw new IllegalArgumentException("Wake lock not active");
             }
+            WakeLock wl = mLocks.get(index);
+            WorkSource oldsource = wl.ws;
+            wl.ws = ws != null ? new WorkSource(ws) : null;
+            noteStopWakeLocked(wl, oldsource);
+            noteStartWakeLocked(wl, ws);
         }
     }
 
@@ -759,11 +848,6 @@
     }
 
     private void releaseWakeLockLocked(IBinder lock, int flags, boolean death) {
-        int releaseUid;
-        int releasePid;
-        String releaseName;
-        int releaseType;
-
         WakeLock wl = mLocks.removeLock(lock);
         if (wl == null) {
             return;
@@ -804,21 +888,8 @@
         }
         // Unlink the lock from the binder.
         wl.binder.unlinkToDeath(wl, 0);
-        releaseUid = wl.uid;
-        releasePid = wl.pid;
-        releaseName = wl.tag;
-        releaseType = wl.monitorType;
 
-        if (releaseType >= 0) {
-            long origId = Binder.clearCallingIdentity();
-            try {
-                mBatteryStats.noteStopWakelock(releaseUid, releasePid, releaseName, releaseType);
-            } catch (RemoteException e) {
-                // Ignore
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
+        noteStopWakeLocked(wl, wl.ws);
     }
 
     private class PokeLock implements IBinder.DeathRecipient
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3abac0d..4f05007 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -101,6 +101,7 @@
         UiModeManagerService uiMode = null;
         RecognitionManagerService recognition = null;
         ThrottleService throttle = null;
+        NetworkTimeUpdateService networkTimeUpdater = null;
 
         // Critical services...
         try {
@@ -441,6 +442,13 @@
             } catch (Throwable e) {
                 Slog.e(TAG, "Failure starting SIP 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
@@ -502,6 +510,7 @@
         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
@@ -531,18 +540,13 @@
                 if (locationF != null) locationF.systemReady();
                 if (countryDetectorF != null) countryDetectorF.systemReady();
                 if (throttleF != null) throttleF.systemReady();
+                if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady();
             }
         });
 
         // For debug builds, log event loop stalls to dropbox for analysis.
-        // Similar logic also appears in ActivityThread.java for system apps.
-        if (!"user".equals(Build.TYPE)) {
-            Slog.i(TAG, "Enabling StrictMode for system server.");
-            StrictMode.setThreadPolicy(
-                StrictMode.DISALLOW_DISK_WRITE |
-                StrictMode.DISALLOW_DISK_READ |
-                StrictMode.DISALLOW_NETWORK |
-                StrictMode.PENALTY_DROPBOX);
+        if (StrictMode.conditionallyEnableDebugLogging()) {
+            Slog.i(TAG, "Enabled StrictMode for system server main thread.");
         }
 
         Looper.loop();
diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java
index 0a90a4c..a33b7c294 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.os.Binder;
 import android.os.Bundle;
@@ -92,7 +93,9 @@
 
     private ArrayList<String> mConnectedApns;
 
-    private LinkProperties mDataConnectionProperties;
+    private LinkProperties mDataConnectionLinkProperties;
+
+    private LinkCapabilities mDataConnectionLinkCapabilities;
 
     private Bundle mCellLocation = new Bundle();
 
@@ -356,7 +359,7 @@
 
     public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
             String reason, String apn, String apnType, LinkProperties linkProperties,
-            int networkType) {
+            LinkCapabilities linkCapabilities, int networkType) {
         if (!checkNotifyPermission("notifyDataConnection()" )) {
             return;
         }
@@ -382,8 +385,8 @@
             }
             mDataConnectionPossible = isDataConnectivityPossible;
             mDataConnectionReason = reason;
-            mDataConnectionApn = apn;
-            mDataConnectionProperties = linkProperties;
+            mDataConnectionLinkProperties = linkProperties;
+            mDataConnectionLinkCapabilities = linkCapabilities;
             if (mDataConnectionNetworkType != networkType) {
                 mDataConnectionNetworkType = networkType;
                 modified = true;
@@ -403,7 +406,7 @@
             }
         }
         broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
-                apnType, linkProperties);
+                apnType, linkProperties, linkCapabilities);
     }
 
     public void notifyDataConnectionFailed(String reason, String apnType) {
@@ -490,7 +493,8 @@
             pw.println("  mDataConnectionPossible=" + mDataConnectionPossible);
             pw.println("  mDataConnectionReason=" + mDataConnectionReason);
             pw.println("  mDataConnectionApn=" + mDataConnectionApn);
-            pw.println("  mDataConnectionProperties=" + mDataConnectionProperties);
+            pw.println("  mDataConnectionLinkProperties=" + mDataConnectionLinkProperties);
+            pw.println("  mDataConnectionLinkCapabilities=" + mDataConnectionLinkCapabilities);
             pw.println("  mCellLocation=" + mCellLocation);
             pw.println("registrations: count=" + recordCount);
             for (Record r : mRecords) {
@@ -564,7 +568,8 @@
 
     private void broadcastDataConnectionStateChanged(int state,
             boolean isDataConnectivityPossible,
-            String reason, String apn, String apnType, LinkProperties linkProperties) {
+            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.
@@ -584,6 +589,9 @@
                 intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface.getName());
             }
         }
+        if (linkCapabilities != null) {
+            intent.putExtra(Phone.DATA_LINK_CAPABILITIES_KEY, linkCapabilities);
+        }
         intent.putExtra(Phone.DATA_APN_KEY, apn);
         intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType);
         mContext.sendStickyBroadcast(intent);
diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java
index 2e7e3e1..f0b5955 100755
--- a/services/java/com/android/server/VibratorService.java
+++ b/services/java/com/android/server/VibratorService.java
@@ -29,6 +29,7 @@
 import android.os.IBinder;
 import android.os.Binder;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.util.Slog;
 
 import java.util.LinkedList;
@@ -39,6 +40,7 @@
 
     private final LinkedList<Vibration> mVibrations;
     private Vibration mCurrentVibration;
+    private final WorkSource mTmpWorkSource = new WorkSource();
 
     private class Vibration implements IBinder.DeathRecipient {
         private final IBinder mToken;
@@ -46,22 +48,24 @@
         private final long    mStartTime;
         private final long[]  mPattern;
         private final int     mRepeat;
+        private final int     mUid;
 
-        Vibration(IBinder token, long millis) {
-            this(token, millis, null, 0);
+        Vibration(IBinder token, long millis, int uid) {
+            this(token, millis, null, 0, uid);
         }
 
-        Vibration(IBinder token, long[] pattern, int repeat) {
-            this(token, 0, pattern, repeat);
+        Vibration(IBinder token, long[] pattern, int repeat, int uid) {
+            this(token, 0, pattern, repeat, uid);
         }
 
         private Vibration(IBinder token, long millis, long[] pattern,
-                int repeat) {
+                int repeat, int uid) {
             mToken = token;
             mTimeout = millis;
             mStartTime = SystemClock.uptimeMillis();
             mPattern = pattern;
             mRepeat = repeat;
+            mUid = uid;
         }
 
         public void binderDied() {
@@ -98,7 +102,7 @@
         mContext = context;
         PowerManager pm = (PowerManager)context.getSystemService(
                 Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
         mWakeLock.setReferenceCounted(true);
 
         mVibrations = new LinkedList<Vibration>();
@@ -113,6 +117,7 @@
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
         }
+        int uid = Binder.getCallingUid();
         // We're running in the system server so we cannot crash. Check for a
         // timeout of 0 or negative. This will ensure that a vibration has
         // either a timeout of > 0 or a non-null pattern.
@@ -122,7 +127,7 @@
             // longer than milliseconds.
             return;
         }
-        Vibration vib = new Vibration(token, milliseconds);
+        Vibration vib = new Vibration(token, milliseconds, uid);
         synchronized (mVibrations) {
             removeVibrationLocked(token);
             doCancelVibrateLocked();
@@ -146,6 +151,7 @@
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
         }
+        int uid = Binder.getCallingUid();
         // so wakelock calls will succeed
         long identity = Binder.clearCallingIdentity();
         try {
@@ -165,7 +171,7 @@
                 return;
             }
 
-            Vibration vib = new Vibration(token, pattern, repeat);
+            Vibration vib = new Vibration(token, pattern, repeat, uid);
             try {
                 token.linkToDeath(vib, 0);
             } catch (RemoteException e) {
@@ -280,6 +286,8 @@
 
         VibrateThread(Vibration vib) {
             mVibration = vib;
+            mTmpWorkSource.set(vib.mUid);
+            mWakeLock.setWorkSource(mTmpWorkSource);
             mWakeLock.acquire();
         }
 
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index 124da4e..c837a3a 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -727,9 +727,10 @@
         }
     }
 
+    // Called by SystemBackupAgent after files are restored to disk.
     void settingsRestored() {
         if (DEBUG) Slog.v(TAG, "settingsRestored");
-        
+
         boolean success = false;
         synchronized (mLock) {
             loadSettingsLocked();
@@ -766,7 +767,10 @@
             mName = "";
             WALLPAPER_FILE.delete();
         }
-        saveSettingsLocked();
+
+        synchronized (mLock) {
+            saveSettingsLocked();
+        }
     }
 
     boolean restoreNamedResourceLocked() {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 6ecc511..e8502fa 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -50,6 +50,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -1035,8 +1036,8 @@
     }
 
     private class WifiLock extends DeathRecipient {
-        WifiLock(int lockMode, String tag, IBinder binder) {
-            super(lockMode, tag, binder);
+        WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
+            super(lockMode, tag, binder, ws);
         }
 
         public void binderDied() {
@@ -1106,33 +1107,70 @@
         }
     }
 
-    public boolean acquireWifiLock(IBinder binder, int lockMode, String tag) {
+    void enforceWakeSourcePermission(int uid, int pid) {
+        if (uid == android.os.Process.myUid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+                pid, uid, null);
+    }
+
+    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) {
             return false;
         }
-        WifiLock wifiLock = new WifiLock(lockMode, tag, binder);
+        if (ws != null) {
+            enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
+        }
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+        if (ws == null) {
+            ws = new WorkSource(Binder.getCallingUid());
+        }
+        WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws);
         synchronized (mLocks) {
             return acquireWifiLockLocked(wifiLock);
         }
     }
 
+    private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException {
+        switch(wifiLock.mMode) {
+            case WifiManager.WIFI_MODE_FULL:
+                mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
+                break;
+            case WifiManager.WIFI_MODE_SCAN_ONLY:
+                mBatteryStats.noteScanWifiLockAcquiredFromSource(wifiLock.mWorkSource);
+                break;
+        }
+    }
+
+    private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException {
+        switch(wifiLock.mMode) {
+            case WifiManager.WIFI_MODE_FULL:
+                mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
+                break;
+            case WifiManager.WIFI_MODE_SCAN_ONLY:
+                mBatteryStats.noteScanWifiLockReleasedFromSource(wifiLock.mWorkSource);
+                break;
+        }
+    }
+
     private boolean acquireWifiLockLocked(WifiLock wifiLock) {
         Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock);
 
         mLocks.addLock(wifiLock);
 
-        int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
+            noteAcquireWifiLock(wifiLock);
             switch(wifiLock.mMode) {
             case WifiManager.WIFI_MODE_FULL:
                 ++mFullLocksAcquired;
-                mBatteryStats.noteFullWifiLockAcquired(uid);
                 break;
             case WifiManager.WIFI_MODE_SCAN_ONLY:
                 ++mScanLocksAcquired;
-                mBatteryStats.noteScanWifiLockAcquired(uid);
                 break;
             }
         } catch (RemoteException e) {
@@ -1144,6 +1182,33 @@
         return true;
     }
 
+    public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+        if (ws != null) {
+            enforceWakeSourcePermission(uid, pid);
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLocks) {
+                int index = mLocks.findLockByBinder(lock);
+                if (index < 0) {
+                    throw new IllegalArgumentException("Wifi lock not active");
+                }
+                WifiLock wl = mLocks.mList.get(index);
+                noteReleaseWifiLock(wl);
+                wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid);
+                noteAcquireWifiLock(wl);
+            }
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     public boolean releaseWifiLock(IBinder lock) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
         synchronized (mLocks) {
@@ -1161,17 +1226,15 @@
         hadLock = (wifiLock != null);
 
         if (hadLock) {
-            int uid = Binder.getCallingUid();
             long ident = Binder.clearCallingIdentity();
             try {
+                noteAcquireWifiLock(wifiLock);
                 switch(wifiLock.mMode) {
                     case WifiManager.WIFI_MODE_FULL:
                         ++mFullLocksReleased;
-                        mBatteryStats.noteFullWifiLockReleased(uid);
                         break;
                     case WifiManager.WIFI_MODE_SCAN_ONLY:
                         ++mScanLocksReleased;
-                        mBatteryStats.noteScanWifiLockReleased(uid);
                         break;
                 }
             } catch (RemoteException e) {
@@ -1189,12 +1252,14 @@
         String mTag;
         int mMode;
         IBinder mBinder;
+        WorkSource mWorkSource;
 
-        DeathRecipient(int mode, String tag, IBinder binder) {
+        DeathRecipient(int mode, String tag, IBinder binder, WorkSource ws) {
             super();
             mTag = tag;
             mMode = mode;
             mBinder = binder;
+            mWorkSource = ws;
             try {
                 mBinder.linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -1209,7 +1274,7 @@
 
     private class Multicaster extends DeathRecipient {
         Multicaster(String tag, IBinder binder) {
-            super(Binder.getCallingUid(), tag, binder);
+            super(Binder.getCallingUid(), tag, binder, null);
         }
 
         public void binderDied() {
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 94b010b..fb8e888 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -5088,68 +5088,44 @@
             }
         }
         
-        /* Notifies the window manager about an input channel that is not responding.
-         * The method can either cause dispatching to be aborted by returning -2 or
-         * return a new timeout in nanoseconds.
+        /* Notifies the window manager about an application that is not responding.
+         * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
          * 
          * Called by the InputManager.
          */
-        public long notifyInputChannelANR(InputChannel inputChannel) {
-            AppWindowToken token;
-            synchronized (mWindowMap) {
-                WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
-                if (windowState == null) {
-                    return -2; // irrelevant, abort dispatching (-2)
+        public long notifyANR(Object token, InputChannel inputChannel) {
+            AppWindowToken appWindowToken = null;
+            if (inputChannel != null) {
+                synchronized (mWindowMap) {
+                    WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
+                    if (windowState != null) {
+                        Slog.i(TAG, "Input event dispatching timed out sending to "
+                                + windowState.mAttrs.getTitle());
+                        appWindowToken = windowState.mAppToken;
+                    }
                 }
-                
-                Slog.i(TAG, "Input event dispatching timed out sending to "
-                        + windowState.mAttrs.getTitle());
-                token = windowState.mAppToken;
             }
             
-            return notifyANRInternal(token);
-        }
-    
-        /* Notifies the window manager about an input channel spontaneously recovering from ANR
-         * by successfully delivering the event that originally timed out.
-         * 
-         * Called by the InputManager.
-         */
-        public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) {
-            // Nothing to do just now.
-            // Just wait for the user to dismiss the ANR dialog.
-        }
-        
-        /* Notifies the window manager about an application that is not responding
-         * in general rather than with respect to a particular input channel.
-         * The method can either cause dispatching to be aborted by returning -2 or
-         * return a new timeout in nanoseconds.
-         * 
-         * Called by the InputManager.
-         */
-        public long notifyANR(Object token) {
-            AppWindowToken appWindowToken = (AppWindowToken) token;
+            if (appWindowToken == null && token != null) {
+                appWindowToken = (AppWindowToken) token;
+                Slog.i(TAG, "Input event dispatching timed out sending to application "
+                        + appWindowToken.stringName);
+            }
 
-            Slog.i(TAG, "Input event dispatching timed out sending to application "
-                    + appWindowToken.stringName);
-            return notifyANRInternal(appWindowToken);
-        }
-        
-        private long notifyANRInternal(AppWindowToken token) {
-            if (token != null && token.appToken != null) {
+            if (appWindowToken != null && appWindowToken.appToken != null) {
                 try {
                     // Notify the activity manager about the timeout and let it decide whether
                     // to abort dispatching or keep waiting.
-                    boolean abort = token.appToken.keyDispatchingTimedOut();
+                    boolean abort = appWindowToken.appToken.keyDispatchingTimedOut();
                     if (! abort) {
                         // The activity manager declined to abort dispatching.
                         // Wait a bit longer and timeout again later.
-                        return token.inputDispatchingTimeoutNanos;
+                        return appWindowToken.inputDispatchingTimeoutNanos;
                     }
                 } catch (RemoteException ex) {
                 }
             }
-            return -2; // abort dispatching
+            return 0; // abort dispatching
         }
         
         private WindowState getWindowStateForInputChannel(InputChannel inputChannel) {
@@ -5197,13 +5173,16 @@
                 // Add a window to our list of input windows.
                 final InputWindow inputWindow = mTempInputWindows.add();
                 inputWindow.inputChannel = child.mInputChannel;
+                inputWindow.name = child.toString();
                 inputWindow.layoutParamsFlags = flags;
                 inputWindow.layoutParamsType = type;
                 inputWindow.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
                 inputWindow.visible = isVisible;
+                inputWindow.canReceiveKeys = child.canReceiveKeys();
                 inputWindow.hasFocus = hasFocus;
                 inputWindow.hasWallpaper = hasWallpaper;
                 inputWindow.paused = child.mAppToken != null ? child.mAppToken.paused : false;
+                inputWindow.layer = child.mLayer;
                 inputWindow.ownerPid = child.mSession.mPid;
                 inputWindow.ownerUid = child.mSession.mUid;
                 
@@ -5263,15 +5242,6 @@
             }
         }
         
-        /* Notifies that an app switch key (BACK / HOME) has just been pressed.
-         * This essentially starts a .5 second timeout for the application to process
-         * subsequent input events while waiting for the app switch to occur.  If it takes longer
-         * than this, the pending events will be dropped.
-         */
-        public void notifyAppSwitchComing() {
-            // TODO Not implemented yet.  Should go in the native side.
-        }
-        
         /* Notifies that the lid switch changed state. */
         public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
             mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen);
@@ -5305,23 +5275,6 @@
 
             if (newWindow != mInputFocus) {
                 if (newWindow != null && newWindow.canReceiveKeys()) {
-                    // If the new input focus is an error window or appears above the current
-                    // input focus, preempt any pending synchronous dispatch so that we can
-                    // start delivering events to the new input focus as soon as possible.
-                    if ((newWindow.mAttrs.flags & FLAG_SYSTEM_ERROR) != 0) {
-                        if (DEBUG_INPUT) {
-                            Slog.v(TAG, "New SYSTEM_ERROR window; resetting state");
-                        }
-                        preemptInputDispatchLw();
-                    } else if (mInputFocus != null && newWindow.mLayer > mInputFocus.mLayer) {
-                        if (DEBUG_INPUT) {
-                            Slog.v(TAG, "Transferring focus to new window at higher layer: "
-                                    + "old win layer=" + mInputFocus.mLayer
-                                    + ", new win layer=" + newWindow.mLayer);
-                        }
-                        preemptInputDispatchLw();
-                    }
-                    
                     // Displaying a window implicitly causes dispatching to be unpaused.
                     // This is to protect against bugs if someone pauses dispatching but
                     // forgets to resume.
@@ -5333,14 +5286,6 @@
             }
         }
         
-        /* Tells the dispatcher to stop waiting for its current synchronous event targets.
-         * Essentially, just makes those dispatches asynchronous so a new dispatch cycle
-         * can begin.
-         */
-        private void preemptInputDispatchLw() {
-            mInputManager.preemptInputDispatch();
-        }
-        
         public void setFocusedAppLw(AppWindowToken newApp) {
             // Focused app has changed.
             if (newApp == null) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 73c1790..5432890 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -6021,12 +6021,18 @@
                             finisher = new IIntentReceiver.Stub() {
                                 public void performReceive(Intent intent, int resultCode,
                                         String data, Bundle extras, boolean ordered,
-                                        boolean sticky)
-                                        throws RemoteException {
-                                    synchronized (ActivityManagerService.this) {
-                                        mDidUpdate = true;
-                                    }
-                                    systemReady(goingCallback);
+                                        boolean sticky) {
+                                    // The raw IIntentReceiver interface is called
+                                    // with the AM lock held, so redispatch to
+                                    // execute our code without the lock.
+                                    mHandler.post(new Runnable() {
+                                        public void run() {
+                                            synchronized (ActivityManagerService.this) {
+                                                mDidUpdate = true;
+                                            }
+                                            systemReady(goingCallback);
+                                        }
+                                    });
                                 }
                             };
                         }
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index a0c21dd..4fc8020 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -1104,7 +1104,7 @@
         // Okay we are now going to start a switch, to 'next'.  We may first
         // have to pause the current activity, but this is an important point
         // where we have decided to go to 'next' so keep track of that.
-        if (mLastStartedActivity != null) {
+        if (mLastStartedActivity != null && !mLastStartedActivity.finishing) {
             long now = SystemClock.uptimeMillis();
             final boolean inTime = mLastStartedActivity.startTime != 0
                     && (mLastStartedActivity.startTime + START_WARN_TIME) >= now;
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 7314e04..bb40967 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -23,6 +23,7 @@
 import android.os.Parcel;
 import android.os.Process;
 import android.os.ServiceManager;
+import android.os.WorkSource;
 import android.telephony.SignalStrength;
 import android.util.Slog;
 
@@ -107,6 +108,20 @@
         }
     }
 
+    public void noteStartWakelockFromSource(WorkSource ws, int pid, String name, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStartWakeFromSourceLocked(ws, pid, name, type);
+        }
+    }
+
+    public void noteStopWakelockFromSource(WorkSource ws, int pid, String name, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStopWakeFromSourceLocked(ws, pid, name, type);
+        }
+    }
+
     public void noteStartSensor(int uid, int sensor) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -317,6 +332,48 @@
         }
     }
 
+    public void noteFullWifiLockAcquiredFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteFullWifiLockAcquiredFromSourceLocked(ws);
+        }
+    }
+
+    public void noteFullWifiLockReleasedFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteFullWifiLockReleasedFromSourceLocked(ws);
+        }
+    }
+
+    public void noteScanWifiLockAcquiredFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteScanWifiLockAcquiredFromSourceLocked(ws);
+        }
+    }
+
+    public void noteScanWifiLockReleasedFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteScanWifiLockReleasedFromSourceLocked(ws);
+        }
+    }
+
+    public void noteWifiMulticastEnabledFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiMulticastEnabledFromSourceLocked(ws);
+        }
+    }
+
+    public void noteWifiMulticastDisabledFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiMulticastDisabledFromSourceLocked(ws);
+        }
+    }
+
     public boolean isOnBattery() {
         return mStats.isOnBattery();
     }
diff --git a/services/java/com/android/server/am/UriPermission.java b/services/java/com/android/server/am/UriPermission.java
index c95546e..0cb6943 100644
--- a/services/java/com/android/server/am/UriPermission.java
+++ b/services/java/com/android/server/am/UriPermission.java
@@ -45,8 +45,8 @@
         uri = _uri;
     }
     
-    void clearModes(int modeFlags) {
-        if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+    void clearModes(int modeFlagsToClear) {
+        if ((modeFlagsToClear&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
             globalModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
             modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
             if (readOwners.size() > 0) {
@@ -56,7 +56,7 @@
                 readOwners.clear();
             }
         }
-        if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+        if ((modeFlagsToClear&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
             globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
             modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
             if (readOwners.size() > 0) {
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 53ce5c0..bfac346 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -89,15 +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.9";
+    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";
@@ -165,13 +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[6];
+            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
 
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index c1165c7..3bf6ee4 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -44,6 +44,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -736,7 +737,7 @@
         startNavigating(true);
     }
 
-    public void setMinTime(long minTime) {
+    public void setMinTime(long minTime, WorkSource ws) {
         if (DEBUG) Log.d(TAG, "setMinTime " + minTime);
         
         if (minTime >= 0) {
@@ -779,7 +780,7 @@
     public void addListener(int uid) {
         synchronized (mWakeLock) {
             mPendingListenerMessages++;
-           mWakeLock.acquire();
+            mWakeLock.acquire();
             Message m = Message.obtain(mHandler, ADD_LISTENER);
             m.arg1 = uid;
             mHandler.sendMessage(m);
diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java
index 084ab81..858a582 100644
--- a/services/java/com/android/server/location/LocationProviderInterface.java
+++ b/services/java/com/android/server/location/LocationProviderInterface.java
@@ -20,6 +20,7 @@
 import android.location.Location;
 import android.net.NetworkInfo;
 import android.os.Bundle;
+import android.os.WorkSource;
 
 /**
  * Location Manager's interface for location providers.
@@ -47,7 +48,7 @@
     /* returns false if single shot is not supported */
     boolean requestSingleShotFix();
     String getInternalState();
-    void setMinTime(long minTime);
+    void setMinTime(long minTime, WorkSource ws);
     void updateNetworkState(int state, NetworkInfo info);
     void updateLocation(Location location);
     boolean sendExtraCommand(String command, Bundle extras);
diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
index 24d7737..7dc9920 100644
--- a/services/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -29,6 +29,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.util.Log;
 
 import com.android.internal.location.DummyLocationProvider;
@@ -52,6 +53,7 @@
     private boolean mLocationTracking = false;
     private boolean mEnabled = false;
     private long mMinTime = -1;
+    private WorkSource mMinTimeSource = new WorkSource();
     private int mNetworkState;
     private NetworkInfo mNetworkInfo;
 
@@ -122,7 +124,7 @@
                     provider.enableLocationTracking(true);
                 }
                 if (mMinTime >= 0) {
-                    provider.setMinTime(mMinTime);
+                    provider.setMinTime(mMinTime, mMinTimeSource);
                 }
                 if (mNetworkInfo != null) {
                     provider.updateNetworkState(mNetworkState, mNetworkInfo);
@@ -318,6 +320,7 @@
         mLocationTracking = enable;
         if (!enable) {
             mMinTime = -1;
+            mMinTimeSource.clear();
         }
         ILocationProvider provider;
         synchronized (mServiceConnection) {
@@ -339,15 +342,16 @@
         return mMinTime;
     }
 
-    public void setMinTime(long minTime) {
-       mMinTime = minTime;
+    public void setMinTime(long minTime, WorkSource ws) {
+        mMinTime = minTime;
+        mMinTimeSource.set(ws);
         ILocationProvider provider;
         synchronized (mServiceConnection) {
             provider = mProvider;
         }
         if (provider != null) {
             try {
-                provider.setMinTime(minTime);
+                provider.setMinTime(minTime, ws);
             } catch (RemoteException e) {
             }
         }
diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java
index 01b34b7..09d799f 100644
--- a/services/java/com/android/server/location/MockProvider.java
+++ b/services/java/com/android/server/location/MockProvider.java
@@ -23,6 +23,7 @@
 import android.net.NetworkInfo;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 
@@ -201,7 +202,7 @@
         return false;
     }
 
-    public void setMinTime(long minTime) {
+    public void setMinTime(long minTime, WorkSource ws) {
     }
 
     public void updateNetworkState(int state, NetworkInfo info) {
diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java
index 7fc93f8..ea0d1b0 100644
--- a/services/java/com/android/server/location/PassiveProvider.java
+++ b/services/java/com/android/server/location/PassiveProvider.java
@@ -24,6 +24,7 @@
 import android.net.NetworkInfo;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.util.Log;
 
 /**
@@ -123,7 +124,7 @@
         return false;
     }
 
-    public void setMinTime(long minTime) {
+    public void setMinTime(long minTime, WorkSource ws) {
     }
 
     public void updateNetworkState(int state, NetworkInfo info) {
diff --git a/services/java/com/android/server/sip/SipHelper.java b/services/java/com/android/server/sip/SipHelper.java
index d9a1bbf..050eddc 100644
--- a/services/java/com/android/server/sip/SipHelper.java
+++ b/services/java/com/android/server/sip/SipHelper.java
@@ -68,6 +68,7 @@
  */
 class SipHelper {
     private static final String TAG = SipHelper.class.getSimpleName();
+    private static final boolean DEBUG = true;
 
     private SipStack mSipStack;
     private SipProvider mSipProvider;
@@ -264,6 +265,7 @@
 
             ClientTransaction clientTransaction =
                     mSipProvider.getNewClientTransaction(request);
+            if (DEBUG) Log.d(TAG, "send INVITE: " + request);
             clientTransaction.sendRequest();
             return clientTransaction;
         } catch (ParseException e) {
@@ -281,6 +283,7 @@
 
             ClientTransaction clientTransaction =
                     mSipProvider.getNewClientTransaction(request);
+            if (DEBUG) Log.d(TAG, "send RE-INVITE: " + request);
             dialog.sendRequest(clientTransaction);
             return clientTransaction;
         } catch (ParseException e) {
@@ -314,6 +317,7 @@
             ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
             toHeader.setTag(tag);
             response.addHeader(toHeader);
+            if (DEBUG) Log.d(TAG, "send RINGING: " + response);
             transaction.sendResponse(response);
             return transaction;
         } catch (ParseException e) {
@@ -340,7 +344,9 @@
             if (inviteTransaction == null) {
                 inviteTransaction = getServerTransaction(event);
             }
+
             if (inviteTransaction.getState() != TransactionState.COMPLETED) {
+                if (DEBUG) Log.d(TAG, "send OK: " + response);
                 inviteTransaction.sendResponse(response);
             }
 
@@ -358,6 +364,7 @@
                     Response.BUSY_HERE, request);
 
             if (inviteTransaction.getState() != TransactionState.COMPLETED) {
+                if (DEBUG) Log.d(TAG, "send BUSY HERE: " + response);
                 inviteTransaction.sendResponse(response);
             }
         } catch (ParseException e) {
@@ -373,27 +380,31 @@
         Response response = event.getResponse();
         long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME))
                 .getSeqNumber();
-        dialog.sendAck(dialog.createAck(cseq));
+        Request ack = dialog.createAck(cseq);
+        if (DEBUG) Log.d(TAG, "send ACK: " + ack);
+        dialog.sendAck(ack);
     }
 
     public void sendBye(Dialog dialog) throws SipException {
         Request byeRequest = dialog.createRequest(Request.BYE);
-        Log.d(TAG, "send BYE: " + byeRequest);
+        if (DEBUG) Log.d(TAG, "send BYE: " + byeRequest);
         dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest));
     }
 
     public void sendCancel(ClientTransaction inviteTransaction)
             throws SipException {
         Request cancelRequest = inviteTransaction.createCancel();
+        if (DEBUG) Log.d(TAG, "send CANCEL: " + cancelRequest);
         mSipProvider.getNewClientTransaction(cancelRequest).sendRequest();
     }
 
     public void sendResponse(RequestEvent event, int responseCode)
             throws SipException {
         try {
-            getServerTransaction(event).sendResponse(
-                    mMessageFactory.createResponse(
-                            responseCode, event.getRequest()));
+            Response response = mMessageFactory.createResponse(
+                    responseCode, event.getRequest());
+            if (DEBUG) Log.d(TAG, "send response: " + response);
+            getServerTransaction(event).sendResponse(response);
         } catch (ParseException e) {
             throw new SipException("sendResponse()", e);
         }
@@ -402,8 +413,10 @@
     public void sendInviteRequestTerminated(Request inviteRequest,
             ServerTransaction inviteTransaction) throws SipException {
         try {
-            inviteTransaction.sendResponse(mMessageFactory.createResponse(
-                    Response.REQUEST_TERMINATED, inviteRequest));
+            Response response = mMessageFactory.createResponse(
+                    Response.REQUEST_TERMINATED, inviteRequest);
+            if (DEBUG) Log.d(TAG, "send response: " + response);
+            inviteTransaction.sendResponse(response);
         } catch (ParseException e) {
             throw new SipException("sendInviteRequestTerminated()", e);
         }
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java
index eee97c3..d7747fb 100644
--- a/services/java/com/android/server/sip/SipService.java
+++ b/services/java/com/android/server/sip/SipService.java
@@ -27,6 +27,7 @@
 import android.net.sip.ISipService;
 import android.net.sip.ISipSession;
 import android.net.sip.ISipSessionListener;
+import android.net.sip.SipErrorCode;
 import android.net.sip.SipManager;
 import android.net.sip.SipProfile;
 import android.net.sip.SipSessionAdapter;
@@ -58,6 +59,8 @@
  */
 public final class SipService extends ISipService.Stub {
     private static final String TAG = "SipService";
+    private static final boolean DEBUG = true;
+    private static final boolean DEBUG_TIMER = DEBUG && false;
     private static final int EXPIRY_TIME = 3600;
     private static final int SHORT_EXPIRY_TIME = 10;
     private static final int MIN_EXPIRY_TIME = 60;
@@ -89,7 +92,7 @@
     }
 
     private SipService(Context context) {
-        Log.v(TAG, " service started!");
+        if (DEBUG) Log.d(TAG, " service started!");
         mContext = context;
         mConnectivityReceiver = new ConnectivityReceiver();
         context.registerReceiver(mConnectivityReceiver,
@@ -136,7 +139,7 @@
             throw new RuntimeException(
                     "empty broadcast action for incoming call");
         }
-        Log.v(TAG, "open3: " + localProfile.getUriString() + ": "
+        if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": "
                 + incomingCallBroadcastAction + ": " + listener);
         try {
             SipSessionGroupExt group = createGroup(localProfile,
@@ -237,14 +240,14 @@
     }
 
     private void notifyProfileAdded(SipProfile localProfile) {
-        Log.d(TAG, "notify: profile added: " + localProfile);
+        if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile);
         Intent intent = new Intent(SipManager.SIP_ADD_PHONE_ACTION);
         intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
         mContext.sendBroadcast(intent);
     }
 
     private void notifyProfileRemoved(SipProfile localProfile) {
-        Log.d(TAG, "notify: profile removed: " + localProfile);
+        if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile);
         Intent intent = new Intent(SipManager.SIP_REMOVE_PHONE_ACTION);
         intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
         mContext.sendBroadcast(intent);
@@ -259,7 +262,7 @@
 
     private void grabWifiLock() {
         if (mWifiLock == null) {
-            Log.v(TAG, "acquire wifi lock");
+            if (DEBUG) Log.d(TAG, "acquire wifi lock");
             mWifiLock = ((WifiManager)
                     mContext.getSystemService(Context.WIFI_SERVICE))
                     .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
@@ -269,7 +272,7 @@
 
     private void releaseWifiLock() {
         if (mWifiLock != null) {
-            Log.v(TAG, "release wifi lock");
+            if (DEBUG) Log.d(TAG, "release wifi lock");
             mWifiLock.release();
             mWifiLock = null;
         }
@@ -282,7 +285,7 @@
 
     private synchronized void onConnectivityChanged(
             String type, boolean connected) {
-        Log.v(TAG, "onConnectivityChanged(): "
+        if (DEBUG) Log.d(TAG, "onConnectivityChanged(): "
                 + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED")
                 + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED"));
 
@@ -397,19 +400,20 @@
                 mSipGroup.openToReceiveCalls(this);
                 mAutoRegistration.start(mSipGroup);
             }
-            Log.v(TAG, "  openToReceiveCalls: " + getUri() + ": "
+            if (DEBUG) Log.d(TAG, "  openToReceiveCalls: " + getUri() + ": "
                     + mIncomingCallBroadcastAction);
         }
 
         public void onConnectivityChanged(boolean connected)
                 throws SipException {
+            mSipGroup.onConnectivityChanged();
             if (connected) {
                 resetGroup(mLocalIp);
                 if (mOpened) openToReceiveCalls();
             } else {
                 // close mSipGroup but remember mOpened
-                Log.v(TAG, "  close auto reg temporarily: " + getUri() + ": "
-                        + mIncomingCallBroadcastAction);
+                if (DEBUG) Log.d(TAG, "  close auto reg temporarily: "
+                        + getUri() + ": " + mIncomingCallBroadcastAction);
                 mSipGroup.close();
                 mAutoRegistration.stop();
             }
@@ -435,7 +439,7 @@
             mOpened = false;
             mSipGroup.closeToNotReceiveCalls();
             mAutoRegistration.stop();
-            Log.v(TAG, "   close: " + getUri() + ": "
+            if (DEBUG) Log.d(TAG, "   close: " + getUri() + ": "
                     + mIncomingCallBroadcastAction);
         }
 
@@ -454,13 +458,13 @@
                     }
 
                     // send out incoming call broadcast
-                    Log.d(TAG, " ringing~~ " + getUri() + ": " + caller.getUri()
-                            + ": " + session.getCallId());
                     addPendingSession(session);
                     Intent intent = SipManager.createIncomingCallBroadcast(
                             mIncomingCallBroadcastAction, session.getCallId(),
                             sessionDescription);
-                    Log.d(TAG, "   send out intent: " + intent);
+                    if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
+                            + caller.getUri() + ": " + session.getCallId()
+                            + " " + mIncomingCallBroadcastAction);
                     mContext.sendBroadcast(intent);
                 } catch (RemoteException e) {
                     // should never happen with a local call
@@ -472,7 +476,8 @@
         @Override
         public void onError(ISipSession session, String errorClass,
                 String message) {
-            Log.v(TAG, "sip session error: " + errorClass + ": " + message);
+            if (DEBUG) Log.d(TAG, "sip session error: " + errorClass + ": "
+                    + message);
         }
 
         public boolean isOpened() {
@@ -504,7 +509,7 @@
         public void run() {
             synchronized (SipService.this) {
                 SipSessionGroup.SipSessionImpl session = mSession.duplicate();
-                Log.d(TAG, "  ~~~ keepalive");
+                if (DEBUG) Log.d(TAG, "~~~ keepalive");
                 mTimer.cancel(this);
                 session.sendKeepAlive();
                 if (session.isReRegisterRequired()) {
@@ -528,6 +533,8 @@
         private int mBackoff = 1;
         private boolean mRegistered;
         private long mExpiryTime;
+        private SipErrorCode mErrorCode;
+        private String mErrorMessage;
 
         private String getAction() {
             return toString();
@@ -545,21 +552,31 @@
                 // TODO: when rfc5626 is deployed, use reg-id and sip.instance
                 // in registration to avoid adding duplicate entries to server
                 mSession.unregister();
-                Log.v(TAG, "start AutoRegistrationProcess for "
+                if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for "
                         + mSession.getLocalProfile().getUriString());
             }
         }
 
         public void stop() {
+            stop(false);
+        }
+
+        private void stopButKeepStates() {
+            stop(true);
+        }
+
+        private void stop(boolean keepStates) {
             if (mSession == null) return;
-            if (mConnected) mSession.unregister();
+            if (mConnected && mRegistered) mSession.unregister();
             mTimer.cancel(this);
             if (mKeepAliveProcess != null) {
                 mKeepAliveProcess.stop();
                 mKeepAliveProcess = null;
             }
-            mSession = null;
-            mRegistered = false;
+            if (!keepStates) {
+                mSession = null;
+                mRegistered = false;
+            }
         }
 
         private boolean isStopped() {
@@ -567,21 +584,33 @@
         }
 
         public void setListener(ISipSessionListener listener) {
-            Log.v(TAG, "setListener(): " + listener);
-            mProxy.setListener(listener);
-            if (mSession == null) return;
+            synchronized (SipService.this) {
+                mProxy.setListener(listener);
+                if (mSession == null) return;
 
-            try {
-                if ((mSession != null) && SipSessionState.REGISTERING.equals(
-                        mSession.getState())) {
-                    mProxy.onRegistering(mSession);
-                } else if (mRegistered) {
-                    int duration = (int)
-                            (mExpiryTime - SystemClock.elapsedRealtime());
-                    mProxy.onRegistrationDone(mSession, duration);
+                try {
+                    SipSessionState state = (mSession == null)
+                            ? SipSessionState.READY_TO_CALL
+                            : Enum.valueOf(
+                                    SipSessionState.class, mSession.getState());
+                    if ((state == SipSessionState.REGISTERING)
+                            || (state == SipSessionState.DEREGISTERING)) {
+                        mProxy.onRegistering(mSession);
+                    } else if (mRegistered) {
+                        int duration = (int)
+                                (mExpiryTime - SystemClock.elapsedRealtime());
+                        mProxy.onRegistrationDone(mSession, duration);
+                    } else if (mErrorCode != null) {
+                        if (mErrorCode == SipErrorCode.TIME_OUT) {
+                            mProxy.onRegistrationTimeout(mSession);
+                        } else {
+                            mProxy.onRegistrationFailed(mSession,
+                                    mErrorCode.toString(), mErrorMessage);
+                        }
+                    }
+                } catch (Throwable t) {
+                    Log.w(TAG, "setListener(): " + t);
                 }
-            } catch (Throwable t) {
-                Log.w(TAG, "setListener(): " + t);
             }
         }
 
@@ -590,7 +619,9 @@
         }
 
         public void run() {
-            Log.v(TAG, "  ~~~ registering");
+            mErrorCode = null;
+            mErrorMessage = null;
+            if (DEBUG) Log.d(TAG, "~~~ registering");
             synchronized (SipService.this) {
                 if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME);
             }
@@ -613,7 +644,7 @@
         }
 
         private void restart(int duration) {
-            Log.v(TAG, "Refresh registration " + duration + "s later.");
+            if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later.");
             mTimer.cancel(this);
             mTimer.set(duration * 1000, this);
         }
@@ -630,28 +661,22 @@
 
         @Override
         public void onRegistering(ISipSession session) {
-            Log.v(TAG, "onRegistering(): " + session + ": " + mSession);
+            if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
             synchronized (SipService.this) {
                 if (!isStopped() && (session != mSession)) return;
                 mRegistered = false;
-                try {
-                    mProxy.onRegistering(session);
-                } catch (Throwable t) {
-                    Log.w(TAG, "onRegistering()", t);
-                }
+                mProxy.onRegistering(session);
             }
         }
 
         @Override
         public void onRegistrationDone(ISipSession session, int duration) {
-            Log.v(TAG, "onRegistrationDone(): " + session + ": " + mSession);
+            if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
             synchronized (SipService.this) {
                 if (!isStopped() && (session != mSession)) return;
-                try {
-                    mProxy.onRegistrationDone(session, duration);
-                } catch (Throwable t) {
-                    Log.w(TAG, "onRegistrationDone()", t);
-                }
+
+                mProxy.onRegistrationDone(session, duration);
+
                 if (isStopped()) return;
 
                 if (duration > 0) {
@@ -680,39 +705,42 @@
                 } else {
                     mRegistered = false;
                     mExpiryTime = -1L;
-                    Log.v(TAG, "Refresh registration immediately");
+                    if (DEBUG) Log.d(TAG, "Refresh registration immediately");
                     run();
                 }
             }
         }
 
         @Override
-        public void onRegistrationFailed(ISipSession session, String className,
-                String message) {
-            Log.v(TAG, "onRegistrationFailed(): " + session + ": " + mSession
-                    + ": " + className + ": " + message);
+        public void onRegistrationFailed(ISipSession session,
+                String errorCodeString, String message) {
+            SipErrorCode errorCode =
+                    Enum.valueOf(SipErrorCode.class, errorCodeString);
+            if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
+                    + errorCode + ": " + message);
             synchronized (SipService.this) {
                 if (!isStopped() && (session != mSession)) return;
-                try {
-                    mProxy.onRegistrationFailed(session, className, message);
-                } catch (Throwable t) {
-                    Log.w(TAG, "onRegistrationFailed(): " + t);
-                }
+                mErrorCode = errorCode;
+                mErrorMessage = message;
+                mProxy.onRegistrationFailed(session, errorCode.toString(),
+                        message);
 
-                if (!isStopped()) onError();
+                if (errorCode == SipErrorCode.INVALID_CREDENTIALS) {
+                    if (DEBUG) Log.d(TAG, "   pause auto-registration");
+                    stopButKeepStates();
+                } else if (!isStopped()) {
+                    onError();
+                }
             }
         }
 
         @Override
         public void onRegistrationTimeout(ISipSession session) {
-            Log.v(TAG, "onRegistrationTimeout(): " + session + ": " + mSession);
+            if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
             synchronized (SipService.this) {
                 if (!isStopped() && (session != mSession)) return;
-                try {
-                    mProxy.onRegistrationTimeout(session);
-                } catch (Throwable t) {
-                    Log.w(TAG, "onRegistrationTimeout(): " + t);
-                }
+                mErrorCode = SipErrorCode.TIME_OUT;
+                mProxy.onRegistrationTimeout(session);
 
                 if (!isStopped()) {
                     mRegistered = false;
@@ -745,20 +773,46 @@
                             b.get(ConnectivityManager.EXTRA_NETWORK_INFO);
                     String type = netInfo.getTypeName();
                     NetworkInfo.State state = netInfo.getState();
+
+                    NetworkInfo activeNetInfo = getActiveNetworkInfo();
+                    if (DEBUG) {
+                        if (activeNetInfo != null) {
+                            Log.d(TAG, "active network: "
+                                    + activeNetInfo.getTypeName()
+                                    + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED)
+                                            ? " CONNECTED" : " DISCONNECTED"));
+                        } else {
+                            Log.d(TAG, "active network: null");
+                        }
+                    }
+                    if ((state == NetworkInfo.State.CONNECTED)
+                            && (activeNetInfo != null)
+                            && (activeNetInfo.getType() != netInfo.getType())) {
+                        if (DEBUG) Log.d(TAG, "ignore connect event: " + type
+                                + ", active: " + activeNetInfo.getTypeName());
+                        return;
+                    }
+
                     if (state == NetworkInfo.State.CONNECTED) {
-                        Log.v(TAG, "Connectivity alert: CONNECTED " + type);
+                        if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type);
                         onChanged(type, true);
                     } else if (state == NetworkInfo.State.DISCONNECTED) {
-                        Log.v(TAG, "Connectivity alert: DISCONNECTED " + type);
+                        if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type);
                         onChanged(type, false);
                     } else {
-                        Log.d(TAG, "Connectivity alert not processed: " + state
-                                + " " + type);
+                        if (DEBUG) Log.d(TAG, "Connectivity alert not processed: "
+                                + state + " " + type);
                     }
                 }
             }
         }
 
+        private NetworkInfo getActiveNetworkInfo() {
+            ConnectivityManager cm = (ConnectivityManager)
+                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+            return cm.getActiveNetworkInfo();
+        }
+
         private void onChanged(String type, boolean connected) {
             synchronized (SipService.this) {
                 // When turning on WIFI, it needs some time for network
@@ -798,7 +852,7 @@
                         return;
                     }
                     mTask = null;
-                    Log.v(TAG, " deliver change for " + mNetworkType
+                    if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType
                             + (mConnected ? " CONNECTED" : "DISCONNECTED"));
                     onConnectivityChanged(mNetworkType, mConnected);
                 }
@@ -880,8 +934,10 @@
             newQueue.addAll((Collection<MyEvent>) mEventQueue);
             mEventQueue.clear();
             mEventQueue = newQueue;
-            Log.v(TAG, "queue re-calculated");
-            printQueue();
+            if (DEBUG_TIMER) {
+                Log.d(TAG, "queue re-calculated");
+                printQueue();
+            }
         }
 
         // Determines the period and the trigger time of the new event and insert it
@@ -935,10 +991,12 @@
             }
 
             long triggerTime = event.mTriggerTime;
-            Log.v(TAG, " add event " + event + " scheduled at "
-                    + showTime(triggerTime) + " at " + showTime(now)
-                    + ", #events=" + mEventQueue.size());
-            printQueue();
+            if (DEBUG_TIMER) {
+                Log.d(TAG, " add event " + event + " scheduled at "
+                        + showTime(triggerTime) + " at " + showTime(now)
+                        + ", #events=" + mEventQueue.size());
+                printQueue();
+            }
         }
 
         /**
@@ -948,7 +1006,7 @@
          */
         public synchronized void cancel(Runnable callback) {
             if (stopped() || mEventQueue.isEmpty()) return;
-            Log.d(TAG, "cancel:" + callback);
+            if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback);
 
             MyEvent firstEvent = mEventQueue.first();
             for (Iterator<MyEvent> iter = mEventQueue.iterator();
@@ -956,7 +1014,7 @@
                 MyEvent event = iter.next();
                 if (event.mCallback == callback) {
                     iter.remove();
-                    Log.d(TAG, "    cancel found:" + event);
+                    if (DEBUG_TIMER) Log.d(TAG, "    cancel found:" + event);
                 }
             }
             if (mEventQueue.isEmpty()) {
@@ -970,8 +1028,10 @@
                 recalculatePeriods();
                 scheduleNext();
             }
-            Log.d(TAG, "after cancel:");
-            printQueue();
+            if (DEBUG_TIMER) {
+                Log.d(TAG, "after cancel:");
+                printQueue();
+            }
         }
 
         private void scheduleNext() {
@@ -1020,13 +1080,13 @@
         }
 
         private void execute(long triggerTime) {
-            Log.d(TAG, "time's up, triggerTime = " + showTime(triggerTime) + ": "
-                    + mEventQueue.size());
+            if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = "
+                    + showTime(triggerTime) + ": " + mEventQueue.size());
             if (stopped() || mEventQueue.isEmpty()) return;
 
             for (MyEvent event : mEventQueue) {
                 if (event.mTriggerTime != triggerTime) break;
-                Log.d(TAG, "execute " + event);
+                if (DEBUG_TIMER) Log.d(TAG, "execute " + event);
 
                 event.mLastTriggerTime = event.mTriggerTime;
                 event.mTriggerTime += event.mPeriod;
@@ -1034,8 +1094,10 @@
                 // run the callback in a new thread to prevent deadlock
                 new Thread(event.mCallback).start();
             }
-            Log.d(TAG, "after timeout execution");
-            printQueue();
+            if (DEBUG_TIMER) {
+                Log.d(TAG, "after timeout execution");
+                printQueue();
+            }
             scheduleNext();
         }
 
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
index 2f7ddc4..baf9a8e 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -19,6 +19,7 @@
 import gov.nist.javax.sip.clientauthutils.AccountManager;
 import gov.nist.javax.sip.clientauthutils.UserCredentials;
 import gov.nist.javax.sip.header.SIPHeaderNames;
+import gov.nist.javax.sip.header.ProxyAuthenticate;
 import gov.nist.javax.sip.header.WWWAuthenticate;
 import gov.nist.javax.sip.message.SIPMessage;
 
@@ -79,9 +80,12 @@
  */
 class SipSessionGroup implements SipListener {
     private static final String TAG = "SipSession";
+    private static final boolean DEBUG = true;
+    private static final boolean DEBUG_PING = DEBUG && false;
     private static final String ANONYMOUS = "anonymous";
     private static final String SERVER_ERROR_PREFIX = "Response: ";
-    private static final int EXPIRY_TIME = 3600;
+    private static final int EXPIRY_TIME = 3600; // in seconds
+    private static final int CANCEL_CALL_TIMER = 5; // in seconds
 
     private static final EventObject DEREGISTER = new EventObject("Deregister");
     private static final EventObject END_CALL = new EventObject("End call");
@@ -153,6 +157,13 @@
         mSessionMap.clear();
     }
 
+    synchronized void onConnectivityChanged() {
+        for (SipSessionImpl s : mSessionMap.values()) {
+            s.onError(SipErrorCode.DATA_CONNECTION_LOST,
+                    "data connection lost");
+        }
+    }
+
     public SipProfile getLocalProfile() {
         return mLocalProfile;
     }
@@ -210,22 +221,26 @@
 
     private synchronized SipSessionImpl getSipSession(EventObject event) {
         String key = SipHelper.getCallId(event);
-        Log.d(TAG, " sesssion key from event: " + key);
-        Log.d(TAG, " active sessions:");
-        for (String k : mSessionMap.keySet()) {
-            Log.d(TAG, "   .....  '" + k + "': " + mSessionMap.get(k));
-        }
         SipSessionImpl session = mSessionMap.get(key);
+        if ((session != null) && isLoggable(session)) {
+            Log.d(TAG, "session key from event: " + key);
+            Log.d(TAG, "active sessions:");
+            for (String k : mSessionMap.keySet()) {
+                Log.d(TAG, " ..." + k + ": " + mSessionMap.get(k));
+            }
+        }
         return ((session != null) ? session : mCallReceiverSession);
     }
 
     private synchronized void addSipSession(SipSessionImpl newSession) {
         removeSipSession(newSession);
         String key = newSession.getCallId();
-        Log.d(TAG, " +++++  add a session with key:  '" + key + "'");
         mSessionMap.put(key, newSession);
-        for (String k : mSessionMap.keySet()) {
-            Log.d(TAG, "   .....  " + k + ": " + mSessionMap.get(k));
+        if (isLoggable(newSession)) {
+            Log.d(TAG, "+++  add a session with key:  '" + key + "'");
+            for (String k : mSessionMap.keySet()) {
+                Log.d(TAG, "  " + k + ": " + mSessionMap.get(k));
+            }
         }
     }
 
@@ -246,10 +261,12 @@
                 }
             }
         }
-        Log.d(TAG, "   remove session " + session + " with key '" + key + "'");
 
-        for (String k : mSessionMap.keySet()) {
-            Log.d(TAG, "   .....  " + k + ": " + mSessionMap.get(k));
+        if ((s != null) && isLoggable(s)) {
+            Log.d(TAG, "remove session " + session + " @key '" + key + "'");
+            for (String k : mSessionMap.keySet()) {
+                Log.d(TAG, "  " + k + ": " + mSessionMap.get(k));
+            }
         }
     }
 
@@ -280,10 +297,10 @@
     private synchronized void process(EventObject event) {
         SipSessionImpl session = getSipSession(event);
         try {
-            if ((session != null) && session.process(event)) {
-                Log.d(TAG, " ~~~~~   new state: " + session.mState);
-            } else {
-                Log.d(TAG, "event not processed: " + event);
+            boolean isLoggable = isLoggable(session, event);
+            boolean processed = (session != null) && session.process(event);
+            if (isLoggable && processed) {
+                Log.d(TAG, "new state after: " + session.mState);
             }
         } catch (Throwable e) {
             Log.w(TAG, "event process error: " + event, e);
@@ -313,8 +330,8 @@
         }
 
         public boolean process(EventObject evt) throws SipException {
-            Log.d(TAG, " ~~~~~   " + this + ": " + mState + ": processing "
-                    + log(evt));
+            if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~   " + this + ": "
+                    + mState + ": processing " + log(evt));
             if (isRequestEvent(Request.INVITE, evt)) {
                 RequestEvent event = (RequestEvent) evt;
                 SipSessionImpl newSession = new SipSessionImpl(mProxy);
@@ -347,6 +364,40 @@
         String mPeerSessionDescription;
         boolean mInCall;
         boolean mReRegisterFlag = false;
+        SessionTimer mTimer;
+
+        // lightweight timer
+        class SessionTimer {
+            private boolean mRunning = true;
+
+            void start(final int timeout) {
+                new Thread(new Runnable() {
+                    public void run() {
+                        sleep(timeout);
+                        if (mRunning) timeout();
+                    }
+                }).start();
+            }
+
+            synchronized void cancel() {
+                mRunning = false;
+                this.notify();
+            }
+
+            private void timeout() {
+                synchronized (SipSessionGroup.this) {
+                    onError(SipErrorCode.TIME_OUT, "Session timed out!");
+                }
+            }
+
+            private synchronized void sleep(int timeout) {
+                try {
+                    this.wait(timeout * 1000);
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "session timer interrupted!");
+                }
+            }
+        }
 
         public SipSessionImpl(ISipSessionListener listener) {
             setListener(listener);
@@ -366,6 +417,8 @@
             mServerTransaction = null;
             mClientTransaction = null;
             mPeerSessionDescription = null;
+
+            cancelSessionTimer();
         }
 
         public boolean isInCall() {
@@ -412,28 +465,22 @@
                             processCommand(command);
                         } catch (SipException e) {
                             Log.w(TAG, "command error: " + command, e);
-                            // TODO: find a better way to do this
-                            if ((command instanceof RegisterCommand)
-                                    || (command == DEREGISTER)) {
-                                onRegistrationFailed(e);
-                            } else {
-                                onError(e);
-                            }
+                            onError(e);
                         }
                     }
             }).start();
         }
 
-        public void makeCall(SipProfile peerProfile,
-                String sessionDescription) {
-            doCommandAsync(
-                    new MakeCallCommand(peerProfile, sessionDescription));
+        public void makeCall(SipProfile peerProfile, String sessionDescription,
+                int timeout) {
+            doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription,
+                    timeout));
         }
 
-        public void answerCall(String sessionDescription) {
+        public void answerCall(String sessionDescription, int timeout) {
             try {
-                processCommand(
-                        new MakeCallCommand(mPeerProfile, sessionDescription));
+                processCommand(new MakeCallCommand(mPeerProfile,
+                        sessionDescription, timeout));
             } catch (SipException e) {
                 onError(e);
             }
@@ -443,9 +490,15 @@
             doCommandAsync(END_CALL);
         }
 
-        public void changeCall(String sessionDescription) {
-            doCommandAsync(
-                    new MakeCallCommand(mPeerProfile, sessionDescription));
+        public void changeCall(String sessionDescription, int timeout) {
+            doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription,
+                    timeout));
+        }
+
+        public void changeCallWithTimeout(
+                String sessionDescription, int timeout) {
+            doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription,
+                    timeout));
         }
 
         public void register(int duration) {
@@ -480,7 +533,9 @@
 
         private void processCommand(EventObject command) throws SipException {
             if (!process(command)) {
-                throw new SipException("wrong state to execute: " + command);
+                onError(SipErrorCode.IN_PROGRESS,
+                        "cannot initiate a new transaction to execute: "
+                        + command);
             }
         }
 
@@ -499,8 +554,8 @@
         }
 
         public boolean process(EventObject evt) throws SipException {
-            Log.d(TAG, " ~~~~~   " + this + ": " + mState + ": processing "
-                    + log(evt));
+            if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~   " + this + ": "
+                    + mState + ": processing " + log(evt));
             synchronized (SipSessionGroup.this) {
                 if (isClosed()) return false;
 
@@ -562,11 +617,8 @@
                 if (evt instanceof TimeoutEvent) {
                     processTimeout((TimeoutEvent) evt);
                 } else {
-                    Log.d(TAG, "Transaction terminated:" + this);
-                    if (!SipSessionState.IN_CALL.equals(mState)) {
-                        removeSipSession(this);
-                    }
-                    return true;
+                    processTransactionTerminated(
+                            (TransactionTerminatedEvent) evt);
                 }
                 return true;
             } else if (evt instanceof DialogTerminatedEvent) {
@@ -585,6 +637,20 @@
             }
         }
 
+        private void processTransactionTerminated(
+                TransactionTerminatedEvent event) {
+            switch (mState) {
+                case IN_CALL:
+                case READY_TO_CALL:
+                    Log.d(TAG, "Transaction terminated; do nothing");
+                    break;
+                default:
+                    Log.d(TAG, "Transaction terminated early: " + this);
+                    onError(SipErrorCode.TRANSACTION_TERMINTED,
+                            "transaction terminated");
+            }
+        }
+
         private void processTimeout(TimeoutEvent event) {
             Log.d(TAG, "processing Timeout..." + event);
             Transaction current = event.isServerTransaction()
@@ -600,25 +666,26 @@
                 return;
             }
             switch (mState) {
-            case REGISTERING:
-            case DEREGISTERING:
-                reset();
-                mProxy.onRegistrationTimeout(this);
-                break;
-            case INCOMING_CALL:
-            case INCOMING_CALL_ANSWERING:
-            case OUTGOING_CALL_CANCELING:
-                endCallOnError(SipErrorCode.TIME_OUT, event.toString());
-                break;
-            case PINGING:
-                reset();
-                mReRegisterFlag = true;
-                mState = SipSessionState.READY_TO_CALL;
-                break;
+                case REGISTERING:
+                case DEREGISTERING:
+                    reset();
+                    mProxy.onRegistrationTimeout(this);
+                    break;
+                case INCOMING_CALL:
+                case INCOMING_CALL_ANSWERING:
+                case OUTGOING_CALL:
+                case OUTGOING_CALL_CANCELING:
+                    onError(SipErrorCode.TIME_OUT, event.toString());
+                    break;
+                case PINGING:
+                    reset();
+                    mReRegisterFlag = true;
+                    mState = SipSessionState.READY_TO_CALL;
+                    break;
 
-            default:
-                // do nothing
-                break;
+                default:
+                    Log.d(TAG, "   do nothing");
+                    break;
             }
         }
 
@@ -656,16 +723,16 @@
                     if (mRPort == 0) mRPort = rPort;
                     if (mRPort != rPort) {
                         mReRegisterFlag = true;
-                        Log.w(TAG, String.format("rport is changed: %d <> %d",
-                                mRPort, rPort));
+                        if (DEBUG) Log.w(TAG, String.format(
+                                "rport is changed: %d <> %d", mRPort, rPort));
                         mRPort = rPort;
                     } else {
-                        Log.w(TAG, "rport is the same: " + rPort);
+                        if (DEBUG_PING) Log.w(TAG, "rport is the same: " + rPort);
                     }
                 } else {
-                    Log.w(TAG, "peer did not respect our rport request");
+                    if (DEBUG) Log.w(TAG, "peer did not respond rport");
                 }
-                mState = SipSessionState.READY_TO_CALL;
+                reset();
                 return true;
             }
             return false;
@@ -687,7 +754,6 @@
                 switch (statusCode) {
                 case Response.OK:
                     SipSessionState state = mState;
-                    reset();
                     onRegistrationDone((state == SipSessionState.REGISTERING)
                             ? getExpiryTime(((ResponseEvent) evt).getResponse())
                             : -1);
@@ -697,16 +763,20 @@
                 case Response.UNAUTHORIZED:
                 case Response.PROXY_AUTHENTICATION_REQUIRED:
                     if (!handleAuthentication(event)) {
-                        Log.v(TAG, "Incorrect username/password");
-                        reset();
-                        onRegistrationFailed(SipErrorCode.INVALID_CREDENTIALS,
-                                "incorrect username or password");
+                        if (mLastNonce == null) {
+                            onRegistrationFailed(SipErrorCode.SERVER_ERROR,
+                                    "server does not provide challenge");
+                        } else {
+                            Log.v(TAG, "Incorrect username/password");
+                            onRegistrationFailed(
+                                    SipErrorCode.INVALID_CREDENTIALS,
+                                    "incorrect username or password");
+                        }
                     }
                     return true;
                 default:
                     if (statusCode >= 500) {
-                        reset();
-                        onRegistrationFailed(createCallbackException(response));
+                        onRegistrationFailed(response);
                         return true;
                     }
                 }
@@ -719,8 +789,8 @@
             Response response = event.getResponse();
             String nonce = getNonceFromResponse(response);
             if (((nonce != null) && nonce.equals(mLastNonce)) ||
-                    (nonce == mLastNonce)) {
-                Log.v(TAG, "Incorrect username/password");
+                    (nonce == null)) {
+                mLastNonce = nonce;
                 return false;
             } else {
                 mClientTransaction = mSipHelper.handleChallenge(
@@ -753,9 +823,12 @@
         }
 
         private String getNonceFromResponse(Response response) {
-            WWWAuthenticate authHeader = (WWWAuthenticate)(response.getHeader(
-                    SIPHeaderNames.WWW_AUTHENTICATE));
-            return (authHeader == null) ? null : authHeader.getNonce();
+            WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader(
+                    SIPHeaderNames.WWW_AUTHENTICATE);
+            if (wwwAuth != null) return wwwAuth.getNonce();
+            ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
+                    SIPHeaderNames.PROXY_AUTHENTICATE);
+            return (proxyAuth == null) ? null : proxyAuth.getNonce();
         }
 
         private boolean readyForCall(EventObject evt) throws SipException {
@@ -770,6 +843,7 @@
                 addSipSession(this);
                 mState = SipSessionState.OUTGOING_CALL;
                 mProxy.onCalling(this);
+                startSessionTimer(cmd.getTimeout());
                 return true;
             } else if (evt instanceof RegisterCommand) {
                 int duration = ((RegisterCommand) evt).getDuration();
@@ -801,6 +875,7 @@
                         ((MakeCallCommand) evt).getSessionDescription(),
                         mServerTransaction);
                 mState = SipSessionState.INCOMING_CALL_ANSWERING;
+                startSessionTimer(((MakeCallCommand) evt).getTimeout());
                 return true;
             } else if (END_CALL == evt) {
                 mSipHelper.sendInviteBusyHere(mInviteReceived,
@@ -843,6 +918,7 @@
                     if (mState == SipSessionState.OUTGOING_CALL) {
                         mState = SipSessionState.OUTGOING_CALL_RING_BACK;
                         mProxy.onRingingBack(this);
+                        cancelSessionTimer();
                     }
                     return true;
                 case Response.OK:
@@ -850,11 +926,15 @@
                     mPeerSessionDescription = extractContent(response);
                     establishCall();
                     return true;
+                case Response.UNAUTHORIZED:
                 case Response.PROXY_AUTHENTICATION_REQUIRED:
                     if (handleAuthentication(event)) {
                         addSipSession(this);
+                    } else if (mLastNonce == null) {
+                        onError(SipErrorCode.SERVER_ERROR,
+                                "server does not provide challenge");
                     } else {
-                        endCallOnError(SipErrorCode.INVALID_CREDENTIALS,
+                        onError(SipErrorCode.INVALID_CREDENTIALS,
                                 "incorrect username or password");
                     }
                     return true;
@@ -880,6 +960,7 @@
                 // response.
                 mSipHelper.sendCancel(mClientTransaction);
                 mState = SipSessionState.OUTGOING_CALL_CANCELING;
+                startSessionTimer(CANCEL_CALL_TIMER);
                 return true;
             }
             return false;
@@ -892,9 +973,13 @@
                 Response response = event.getResponse();
                 int statusCode = response.getStatusCode();
                 if (expectResponse(Request.CANCEL, evt)) {
-                    if (statusCode == Response.OK) {
-                        // do nothing; wait for REQUEST_TERMINATED
-                        return true;
+                    switch (statusCode) {
+                        case Response.OK:
+                            // do nothing; wait for REQUEST_TERMINATED
+                            return true;
+                        case Response.REQUEST_TERMINATED:
+                            endCallNormally();
+                            return true;
                     }
                 } else if (expectResponse(Request.INVITE, evt)) {
                     if (statusCode == Response.OK) {
@@ -906,7 +991,7 @@
                 }
 
                 if (statusCode >= 400) {
-                    onError(createCallbackException(response));
+                    onError(response);
                     return true;
                 }
             } else if (evt instanceof TransactionTerminatedEvent) {
@@ -944,31 +1029,43 @@
                 mClientTransaction = mSipHelper.sendReinvite(mDialog,
                         ((MakeCallCommand) evt).getSessionDescription());
                 mState = SipSessionState.OUTGOING_CALL;
+                startSessionTimer(((MakeCallCommand) evt).getTimeout());
                 return true;
             }
             return false;
         }
 
+        // timeout in seconds
+        private void startSessionTimer(int timeout) {
+            if (timeout > 0) {
+                mTimer = new SessionTimer();
+                mTimer.start(timeout);
+            }
+        }
+
+        private void cancelSessionTimer() {
+            if (mTimer != null) {
+                mTimer.cancel();
+                mTimer = null;
+            }
+        }
+
         private String createErrorMessage(Response response) {
             return String.format(SERVER_ERROR_PREFIX + "%s (%d)",
                     response.getReasonPhrase(), response.getStatusCode());
         }
 
-        private Exception createCallbackException(Response response) {
-            return new SipException(createErrorMessage(response));
-        }
-
         private void establishCall() {
             mState = SipSessionState.IN_CALL;
             mInCall = true;
+            cancelSessionTimer();
             mProxy.onCallEstablished(this, mPeerSessionDescription);
         }
 
-        private void fallbackToPreviousInCall(Throwable exception) {
+        private void fallbackToPreviousInCall(SipErrorCode errorCode,
+                String message) {
             mState = SipSessionState.IN_CALL;
-            exception = getRootCause(exception);
-            mProxy.onCallChangeFailed(this, getErrorCode(exception).toString(),
-                    exception.toString());
+            mProxy.onCallChangeFailed(this, errorCode.toString(), message);
         }
 
         private void endCallNormally() {
@@ -976,11 +1073,6 @@
             mProxy.onCallEnded(this);
         }
 
-        private void endCallOnError(Throwable exception) {
-            exception = getRootCause(exception);
-            endCallOnError(getErrorCode(exception), exception.toString());
-        }
-
         private void endCallOnError(SipErrorCode errorCode, String message) {
             reset();
             mProxy.onError(this, errorCode.toString(), message);
@@ -991,36 +1083,56 @@
             mProxy.onCallBusy(this);
         }
 
-        private void onError(Throwable exception) {
-            if (mInCall) {
-                fallbackToPreviousInCall(exception);
-            } else {
-                endCallOnError(exception);
+        private void onError(SipErrorCode errorCode, String message) {
+            cancelSessionTimer();
+            switch (mState) {
+                case REGISTERING:
+                case DEREGISTERING:
+                    onRegistrationFailed(errorCode, message);
+                    break;
+                default:
+                    if ((errorCode != SipErrorCode.DATA_CONNECTION_LOST)
+                            && mInCall) {
+                        fallbackToPreviousInCall(errorCode, message);
+                    } else {
+                        endCallOnError(errorCode, message);
+                    }
             }
         }
 
+
+        private void onError(Throwable exception) {
+            exception = getRootCause(exception);
+            onError(getErrorCode(exception), exception.toString());
+        }
+
         private void onError(Response response) {
-            if (mInCall) {
-                fallbackToPreviousInCall(createCallbackException(response));
+            int statusCode = response.getStatusCode();
+            if (!mInCall && (statusCode == Response.BUSY_HERE)) {
+                endCallOnBusy();
             } else {
-                int statusCode = response.getStatusCode();
-                if ((statusCode == Response.TEMPORARILY_UNAVAILABLE)
-                        || (statusCode == Response.BUSY_HERE)) {
-                    endCallOnBusy();
-                } else {
-                    endCallOnError(getErrorCode(statusCode),
-                            createErrorMessage(response));
-                }
+                onError(getErrorCode(statusCode), createErrorMessage(response));
             }
         }
 
         private SipErrorCode getErrorCode(int responseStatusCode) {
             switch (responseStatusCode) {
+                case Response.TEMPORARILY_UNAVAILABLE:
+                case Response.FORBIDDEN:
+                case Response.GONE:
                 case Response.NOT_FOUND:
+                case Response.NOT_ACCEPTABLE:
+                case Response.NOT_ACCEPTABLE_HERE:
+                    return SipErrorCode.PEER_NOT_REACHABLE;
+
+                case Response.REQUEST_URI_TOO_LONG:
                 case Response.ADDRESS_INCOMPLETE:
+                case Response.AMBIGUOUS:
                     return SipErrorCode.INVALID_REMOTE_URI;
+
                 case Response.REQUEST_TIMEOUT:
                     return SipErrorCode.TIME_OUT;
+
                 default:
                     if (responseStatusCode < 500) {
                         return SipErrorCode.CLIENT_ERROR;
@@ -1053,19 +1165,29 @@
         }
 
         private void onRegistrationDone(int duration) {
+            reset();
             mProxy.onRegistrationDone(this, duration);
         }
 
         private void onRegistrationFailed(SipErrorCode errorCode,
                 String message) {
+            reset();
             mProxy.onRegistrationFailed(this, errorCode.toString(), message);
         }
 
         private void onRegistrationFailed(Throwable exception) {
+            reset();
             exception = getRootCause(exception);
             onRegistrationFailed(getErrorCode(exception),
                     exception.toString());
         }
+
+        private void onRegistrationFailed(Response response) {
+            reset();
+            int statusCode = response.getStatusCode();
+            onRegistrationFailed(getErrorCode(statusCode),
+                    createErrorMessage(response));
+        }
     }
 
     /**
@@ -1130,13 +1252,41 @@
                     .setPort(uri.getPort())
                     .setDisplayName(address.getDisplayName())
                     .build();
-        } catch (InvalidArgumentException e) {
+        } catch (IllegalArgumentException e) {
             throw new SipException("createPeerProfile()", e);
         } catch (ParseException e) {
             throw new SipException("createPeerProfile()", e);
         }
     }
 
+    private static boolean isLoggable(SipSessionImpl s) {
+        if (s != null) {
+            switch (s.mState) {
+                case PINGING:
+                    return DEBUG_PING;
+            }
+        }
+        return DEBUG;
+    }
+
+    private static boolean isLoggable(SipSessionImpl s, EventObject evt) {
+        if (!isLoggable(s)) return false;
+        if (evt == null) return false;
+
+        if (evt instanceof OptionsCommand) {
+            return DEBUG_PING;
+        } else if (evt instanceof ResponseEvent) {
+            Response response = ((ResponseEvent) evt).getResponse();
+            if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) {
+                return DEBUG_PING;
+            }
+            return DEBUG;
+        } else if (evt instanceof RequestEvent) {
+            return DEBUG;
+        }
+        return false;
+    }
+
     private static String log(EventObject evt) {
         if (evt instanceof RequestEvent) {
             return ((RequestEvent) evt).getRequest().toString();
@@ -1168,11 +1318,18 @@
 
     private class MakeCallCommand extends EventObject {
         private String mSessionDescription;
+        private int mTimeout; // in seconds
 
         public MakeCallCommand(SipProfile peerProfile,
                 String sessionDescription) {
+            this(peerProfile, sessionDescription, -1);
+        }
+
+        public MakeCallCommand(SipProfile peerProfile,
+                String sessionDescription, int timeout) {
             super(peerProfile);
             mSessionDescription = sessionDescription;
+            mTimeout = timeout;
         }
 
         public SipProfile getPeerProfile() {
@@ -1182,6 +1339,9 @@
         public String getSessionDescription() {
             return mSessionDescription;
         }
-    }
 
+        public int getTimeout() {
+            return mTimeout;
+        }
+    }
 }
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index 7af5e95..d27c2c6 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -24,9 +24,6 @@
 // Log debug messages about InputDispatcherPolicy
 #define DEBUG_INPUT_DISPATCHER_POLICY 0
 
-// Log debug messages about input focus tracking
-#define DEBUG_FOCUS 0
-
 #include "JNIHelp.h"
 #include "jni.h"
 #include <limits.h>
@@ -44,81 +41,6 @@
 
 namespace android {
 
-// Window flags from WindowManager.LayoutParams
-enum {
-    FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001,
-    FLAG_DIM_BEHIND        = 0x00000002,
-    FLAG_BLUR_BEHIND        = 0x00000004,
-    FLAG_NOT_FOCUSABLE      = 0x00000008,
-    FLAG_NOT_TOUCHABLE      = 0x00000010,
-    FLAG_NOT_TOUCH_MODAL    = 0x00000020,
-    FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
-    FLAG_KEEP_SCREEN_ON     = 0x00000080,
-    FLAG_LAYOUT_IN_SCREEN   = 0x00000100,
-    FLAG_LAYOUT_NO_LIMITS   = 0x00000200,
-    FLAG_FULLSCREEN      = 0x00000400,
-    FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800,
-    FLAG_DITHER             = 0x00001000,
-    FLAG_SECURE             = 0x00002000,
-    FLAG_SCALED             = 0x00004000,
-    FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000,
-    FLAG_LAYOUT_INSET_DECOR = 0x00010000,
-    FLAG_ALT_FOCUSABLE_IM = 0x00020000,
-    FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
-    FLAG_SHOW_WHEN_LOCKED = 0x00080000,
-    FLAG_SHOW_WALLPAPER = 0x00100000,
-    FLAG_TURN_SCREEN_ON = 0x00200000,
-    FLAG_DISMISS_KEYGUARD = 0x00400000,
-    FLAG_IMMERSIVE = 0x00800000,
-    FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000,
-    FLAG_COMPATIBLE_WINDOW = 0x20000000,
-    FLAG_SYSTEM_ERROR = 0x40000000,
-};
-
-// Window types from WindowManager.LayoutParams
-enum {
-    FIRST_APPLICATION_WINDOW = 1,
-    TYPE_BASE_APPLICATION   = 1,
-    TYPE_APPLICATION        = 2,
-    TYPE_APPLICATION_STARTING = 3,
-    LAST_APPLICATION_WINDOW = 99,
-    FIRST_SUB_WINDOW        = 1000,
-    TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW,
-    TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1,
-    TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2,
-    TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3,
-    TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4,
-    LAST_SUB_WINDOW         = 1999,
-    FIRST_SYSTEM_WINDOW     = 2000,
-    TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW,
-    TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1,
-    TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2,
-    TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3,
-    TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4,
-    TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5,
-    TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6,
-    TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7,
-    TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8,
-    TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9,
-    TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10,
-    TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11,
-    TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12,
-    TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13,
-    TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14,
-    LAST_SYSTEM_WINDOW      = 2999,
-};
-
-// Delay between reporting long touch events to the power manager.
-const nsecs_t EVENT_IGNORE_DURATION = 300 * 1000000LL; // 300 ms
-
-// Default input dispatching timeout if there is no focused application or paused window
-// from which to determine an appropriate dispatching timeout.
-const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
-
-// Minimum amount of time to provide to the input dispatcher for delivery of an event
-// regardless of how long the application window was paused.
-const nsecs_t MIN_INPUT_DISPATCHING_TIMEOUT = 1000 * 1000000LL; // 1 sec
-
 // ----------------------------------------------------------------------------
 
 static struct {
@@ -127,14 +49,11 @@
     jmethodID notifyConfigurationChanged;
     jmethodID notifyLidSwitchChanged;
     jmethodID notifyInputChannelBroken;
-    jmethodID notifyInputChannelANR;
-    jmethodID notifyInputChannelRecoveredFromANR;
     jmethodID notifyANR;
     jmethodID virtualKeyDownFeedback;
     jmethodID interceptKeyBeforeQueueing;
     jmethodID interceptKeyBeforeDispatching;
     jmethodID checkInjectEventsPermission;
-    jmethodID notifyAppSwitchComing;
     jmethodID filterTouchEvents;
     jmethodID filterJumpyTouchEvents;
     jmethodID getVirtualKeyDefinitions;
@@ -164,6 +83,7 @@
     jclass clazz;
 
     jfieldID inputChannel;
+    jfieldID name;
     jfieldID layoutParamsFlags;
     jfieldID layoutParamsType;
     jfieldID dispatchingTimeoutNanos;
@@ -180,9 +100,11 @@
     jfieldID touchableAreaRight;
     jfieldID touchableAreaBottom;
     jfieldID visible;
+    jfieldID canReceiveKeys;
     jfieldID hasFocus;
     jfieldID hasWallpaper;
     jfieldID paused;
+    jfieldID layer;
     jfieldID ownerPid;
     jfieldID ownerUid;
 } gInputWindowClassInfo;
@@ -235,7 +157,7 @@
 
     inline sp<InputManager> getInputManager() const { return mInputManager; }
 
-    String8 dump();
+    void dump(String8& dump);
 
     void setDisplaySize(int32_t displayId, int32_t width, int32_t height);
     void setDisplayOrientation(int32_t displayId, int32_t orientation);
@@ -247,7 +169,6 @@
     void setInputWindows(JNIEnv* env, jobjectArray windowObjArray);
     void setFocusedApplication(JNIEnv* env, jobject applicationObj);
     void setInputDispatchMode(bool enabled, bool frozen);
-    void preemptInputDispatch();
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -270,72 +191,32 @@
     /* --- InputDispatcherPolicyInterface implementation --- */
 
     virtual void notifyConfigurationChanged(nsecs_t when);
+    virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+            const sp<InputChannel>& inputChannel);
     virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel);
-    virtual bool notifyInputChannelANR(const sp<InputChannel>& inputChannel,
-            nsecs_t& outNewTimeout);
-    virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel);
     virtual nsecs_t getKeyRepeatTimeout();
     virtual nsecs_t getKeyRepeatDelay();
-    virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
-            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
-    virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
-            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
     virtual int32_t getMaxEventsPerSecond();
+    virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
+            const KeyEvent* keyEvent, uint32_t policyFlags);
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType);
+    virtual bool checkInjectEventsPermissionNonReentrant(
+            int32_t injectorPid, int32_t injectorUid);
 
 private:
-    struct InputWindow {
-        sp<InputChannel> inputChannel;
-        int32_t layoutParamsFlags;
-        int32_t layoutParamsType;
-        nsecs_t dispatchingTimeout;
-        int32_t frameLeft;
-        int32_t frameTop;
-        int32_t frameRight;
-        int32_t frameBottom;
-        int32_t visibleFrameLeft;
-        int32_t visibleFrameTop;
-        int32_t visibleFrameRight;
-        int32_t visibleFrameBottom;
-        int32_t touchableAreaLeft;
-        int32_t touchableAreaTop;
-        int32_t touchableAreaRight;
-        int32_t touchableAreaBottom;
-        bool visible;
-        bool hasFocus;
-        bool hasWallpaper;
-        bool paused;
-        int32_t ownerPid;
-        int32_t ownerUid;
-
-        bool visibleFrameIntersects(const InputWindow* other) const;
-        bool touchableAreaContainsPoint(int32_t x, int32_t y) const;
-    };
-
-    struct InputApplication {
-        String8 name;
-        nsecs_t dispatchingTimeout;
-        jweak tokenObjWeak;
-    };
-
-    class ANRTimer {
-        enum Budget {
-            SYSTEM = 0,
-            APPLICATION = 1
-        };
-
-        Budget mBudget;
-        nsecs_t mStartTime;
-        bool mFrozen;
-        InputWindow* mPausedWindow;
+    class ApplicationToken : public InputApplicationHandle {
+        jweak mTokenObjWeak;
 
     public:
-        ANRTimer();
+        ApplicationToken(jweak tokenObjWeak) :
+            mTokenObjWeak(tokenObjWeak) { }
 
-        void dispatchFrozenBySystem();
-        void dispatchPausedByApplication(InputWindow* pausedWindow);
-        bool waitForDispatchStateChangeLd(NativeInputManager* inputManager);
+        virtual ~ApplicationToken() {
+            JNIEnv* env = NativeInputManager::jniEnv();
+            env->DeleteWeakGlobalRef(mTokenObjWeak);
+        }
 
-        nsecs_t getTimeSpentWaitingForApplication() const;
+        inline jweak getTokenObj() { return mTokenObjWeak; }
     };
 
     sp<InputManager> mInputManager;
@@ -364,80 +245,14 @@
 
     jobject getInputChannelObjLocal(JNIEnv* env, const sp<InputChannel>& inputChannel);
 
-    // Input target and focus tracking.  (lock mDispatchLock)
-    Mutex mDispatchLock;
-    Condition mDispatchStateChanged;
-
-    bool mDispatchEnabled;
-    bool mDispatchFrozen;
-    bool mWindowsReady;
-    Vector<InputWindow> mWindows;
-    Vector<InputWindow*> mWallpaperWindows;
-    Vector<sp<InputChannel> > mMonitoringChannels;
-
-    // Focus tracking for keys, trackball, etc.
-    InputWindow* mFocusedWindow;
-
-    // Focus tracking for touch.
-    bool mTouchDown;
-    InputWindow* mTouchedWindow;                   // primary target for current down
-    bool mTouchedWindowIsObscured;                 // true if other windows may obscure the target
-    Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets
-    struct OutsideTarget {
-        InputWindow* window;
-        bool obscured;
-    };
-    Vector<OutsideTarget> mTempTouchedOutsideTargets; // temporary outside touch targets
-    Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets
-
-    // Focused application.
-    InputApplication* mFocusedApplication;
-    InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication
-
-    void dumpDeviceInfo(String8& dump);
-    void dumpDispatchStateLd(String8& dump);
-    void logDispatchStateLd();
-
-    bool notifyANR(jobject tokenObj, nsecs_t& outNewTimeout);
-    void releaseFocusedApplicationLd(JNIEnv* env);
-
-    int32_t waitForFocusedWindowLd(uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid,
-            Vector<InputTarget>& outTargets, InputWindow*& outFocusedWindow);
-    int32_t waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags,
-            int32_t injectorPid, int32_t injectorUid,
-            Vector<InputTarget>& outTargets, InputWindow*& outTouchedWindow);
-    bool isWindowObscuredLocked(const InputWindow* window);
-
-    void releaseTouchedWindowLd();
-
-    int32_t waitForNonTouchEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
-            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
-    int32_t waitForTouchEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
-            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
-
-    bool interceptKeyBeforeDispatching(const InputTarget& target,
-            const KeyEvent* keyEvent, uint32_t policyFlags);
-
-    void pokeUserActivityIfNeeded(int32_t windowType, int32_t eventType);
-    void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
-    bool checkInjectionPermission(const InputWindow* window,
-            int32_t injectorPid, int32_t injectorUid);
-
     static bool populateWindow(JNIEnv* env, jobject windowObj, InputWindow& outWindow);
-    static void addTarget(const InputWindow* window, int32_t targetFlags,
-            nsecs_t timeSpentWaitingForApplication, Vector<InputTarget>& outTargets);
 
-    void registerMonitoringChannel(const sp<InputChannel>& inputChannel);
-    void unregisterMonitoringChannel(const sp<InputChannel>& inputChannel);
-    void addMonitoringTargetsLd(Vector<InputTarget>& outTargets);
+    static bool isPolicyKey(int32_t keyCode, bool isScreenOn);
+    static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
 
     static inline JNIEnv* jniEnv() {
         return AndroidRuntime::getJNIEnv();
     }
-
-    static bool isAppSwitchKey(int32_t keyCode);
-    static bool isPolicyKey(int32_t keyCode, bool isScreenOn);
-    static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
 };
 
 // ----------------------------------------------------------------------------
@@ -445,10 +260,7 @@
 NativeInputManager::NativeInputManager(jobject callbacksObj) :
     mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1),
     mMaxEventsPerSecond(-1),
-    mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0),
-    mDispatchEnabled(true), mDispatchFrozen(false), mWindowsReady(true),
-    mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL),
-    mFocusedApplication(NULL) {
+    mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0) {
     JNIEnv* env = jniEnv();
 
     mCallbacksObj = env->NewGlobalRef(callbacksObj);
@@ -461,27 +273,16 @@
     JNIEnv* env = jniEnv();
 
     env->DeleteGlobalRef(mCallbacksObj);
-
-    releaseFocusedApplicationLd(env);
 }
 
-String8 NativeInputManager::dump() {
-    String8 dump;
-    { // acquire lock
-        AutoMutex _l(mDisplayLock);
-        dump.append("Native Input Dispatcher State:\n");
-        dumpDispatchStateLd(dump);
-        dump.append("\n");
-    } // release lock
+void NativeInputManager::dump(String8& dump) {
+    dump.append("Input Reader State:\n");
+    mInputManager->getReader()->dump(dump);
+    dump.append("\n");
 
-    dump.append("Input Devices:\n");
-    dumpDeviceInfo(dump);
-
-    return dump;
-}
-
-bool NativeInputManager::isAppSwitchKey(int32_t keyCode) {
-    return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL;
+    dump.append("Input Dispatcher State:\n");
+    mInputManager->getDispatcher()->dump(dump);
+    dump.append("\n");
 }
 
 bool NativeInputManager::isPolicyKey(int32_t keyCode, bool isScreenOn) {
@@ -503,6 +304,7 @@
     case AKEYCODE_MEDIA_PREVIOUS:
     case AKEYCODE_MEDIA_REWIND:
     case AKEYCODE_MEDIA_FAST_FORWARD:
+        // The policy always cares about these keys.
         return true;
     default:
         // We need to pass all keys to the policy in the following cases:
@@ -565,12 +367,9 @@
         mInputChannelObjWeakTable.add(inputChannel.get(), inputChannelObjWeak);
     }
 
-    status = mInputManager->registerInputChannel(inputChannel);
+    status = mInputManager->getDispatcher()->registerInputChannel(inputChannel, monitor);
     if (! status) {
         // Success.
-        if (monitor) {
-            registerMonitoringChannel(inputChannel);
-        }
         return OK;
     }
 
@@ -604,17 +403,20 @@
 
     env->DeleteWeakGlobalRef(inputChannelObjWeak);
 
-    unregisterMonitoringChannel(inputChannel);
-
-    return mInputManager->unregisterInputChannel(inputChannel);
+    return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel);
 }
 
 jobject NativeInputManager::getInputChannelObjLocal(JNIEnv* env,
         const sp<InputChannel>& inputChannel) {
+    InputChannel* inputChannelPtr = inputChannel.get();
+    if (! inputChannelPtr) {
+        return NULL;
+    }
+
     {
         AutoMutex _l(mInputChannelRegistryLock);
 
-        ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannel.get());
+        ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannelPtr);
         if (index < 0) {
             return NULL;
         }
@@ -710,20 +512,11 @@
     }
 
     if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) {
-        pokeUserActivity(when, POWER_MANAGER_BUTTON_EVENT);
+        android_server_PowerManagerService_userActivity(when, POWER_MANAGER_BUTTON_EVENT);
     }
 
     if (wmActions & WM_ACTION_PASS_TO_USER) {
         actions |= InputReaderPolicyInterface::ACTION_DISPATCH;
-
-        if (down && isAppSwitchKey(keyCode)) {
-            JNIEnv* env = jniEnv();
-
-            env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyAppSwitchComing);
-            checkAndClearExceptionFromCallback(env, "notifyAppSwitchComing");
-
-            actions |= InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING;
-        }
     }
 
     return actions;
@@ -906,13 +699,44 @@
     JNIEnv* env = jniEnv();
 
     InputConfiguration config;
-    mInputManager->getInputConfiguration(& config);
+    mInputManager->getReader()->getInputConfiguration(& config);
 
     env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyConfigurationChanged,
             when, config.touchScreen, config.keyboard, config.navigation);
     checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged");
 }
 
+nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+        const sp<InputChannel>& inputChannel) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+    LOGD("notifyANR");
+#endif
+
+    JNIEnv* env = jniEnv();
+
+    jobject tokenObjLocal;
+    if (inputApplicationHandle.get()) {
+        ApplicationToken* token = static_cast<ApplicationToken*>(inputApplicationHandle.get());
+        jweak tokenObjWeak = token->getTokenObj();
+        tokenObjLocal = env->NewLocalRef(tokenObjWeak);
+    } else {
+        tokenObjLocal = NULL;
+    }
+
+    jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
+    jlong newTimeout = env->CallLongMethod(mCallbacksObj,
+                gCallbacksClassInfo.notifyANR, tokenObjLocal, inputChannelObjLocal);
+    if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
+        newTimeout = 0; // abort dispatch
+    } else {
+        assert(newTimeout >= 0);
+    }
+
+    env->DeleteLocalRef(tokenObjLocal);
+    env->DeleteLocalRef(inputChannelObjLocal);
+    return newTimeout;
+}
+
 void NativeInputManager::notifyInputChannelBroken(const sp<InputChannel>& inputChannel) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     LOGD("notifyInputChannelBroken - inputChannel='%s'", inputChannel->getName().string());
@@ -928,78 +752,6 @@
 
         env->DeleteLocalRef(inputChannelObjLocal);
     }
-
-    unregisterMonitoringChannel(inputChannel);
-}
-
-bool NativeInputManager::notifyInputChannelANR(const sp<InputChannel>& inputChannel,
-        nsecs_t& outNewTimeout) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    LOGD("notifyInputChannelANR - inputChannel='%s'",
-            inputChannel->getName().string());
-#endif
-
-    JNIEnv* env = jniEnv();
-
-    jlong newTimeout;
-    jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
-    if (inputChannelObjLocal) {
-        newTimeout = env->CallLongMethod(mCallbacksObj,
-                gCallbacksClassInfo.notifyInputChannelANR, inputChannelObjLocal);
-        if (checkAndClearExceptionFromCallback(env, "notifyInputChannelANR")) {
-            newTimeout = -2;
-        }
-
-        env->DeleteLocalRef(inputChannelObjLocal);
-    } else {
-        newTimeout = -2;
-    }
-
-    if (newTimeout == -2) {
-        return false; // abort
-    }
-
-    outNewTimeout = newTimeout;
-    return true; // resume
-}
-
-void NativeInputManager::notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    LOGD("notifyInputChannelRecoveredFromANR - inputChannel='%s'",
-            inputChannel->getName().string());
-#endif
-
-    JNIEnv* env = jniEnv();
-
-    jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
-    if (inputChannelObjLocal) {
-        env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyInputChannelRecoveredFromANR,
-                inputChannelObjLocal);
-        checkAndClearExceptionFromCallback(env, "notifyInputChannelRecoveredFromANR");
-
-        env->DeleteLocalRef(inputChannelObjLocal);
-    }
-}
-
-bool NativeInputManager::notifyANR(jobject tokenObj, nsecs_t& outNewTimeout) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    LOGD("notifyANR");
-#endif
-
-    JNIEnv* env = jniEnv();
-
-    jlong newTimeout = env->CallLongMethod(mCallbacksObj,
-            gCallbacksClassInfo.notifyANR, tokenObj);
-    if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
-        newTimeout = -2;
-    }
-
-    if (newTimeout == -2) {
-        return false; // abort
-    }
-
-    outNewTimeout = newTimeout;
-    return true; // resume
 }
 
 nsecs_t NativeInputManager::getKeyRepeatTimeout() {
@@ -1032,89 +784,26 @@
 }
 
 void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArray) {
-#if DEBUG_FOCUS
-    LOGD("setInputWindows");
-#endif
-    { // acquire lock
-        AutoMutex _l(mDispatchLock);
+    Vector<InputWindow> windows;
 
-        sp<InputChannel> touchedWindowChannel;
-        if (mTouchedWindow) {
-            touchedWindowChannel = mTouchedWindow->inputChannel;
-            mTouchedWindow = NULL;
-        }
-        size_t numTouchedWallpapers = mTouchedWallpaperWindows.size();
-        if (numTouchedWallpapers != 0) {
-            for (size_t i = 0; i < numTouchedWallpapers; i++) {
-                mTempTouchedWallpaperChannels.push(mTouchedWallpaperWindows[i]->inputChannel);
-            }
-            mTouchedWallpaperWindows.clear();
+    jsize length = env->GetArrayLength(windowObjArray);
+    for (jsize i = 0; i < length; i++) {
+        jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i);
+        if (! inputTargetObj) {
+            break; // found null element indicating end of used portion of the array
         }
 
-        bool hadFocusedWindow = mFocusedWindow != NULL;
-
-        mWindows.clear();
-        mFocusedWindow = NULL;
-        mWallpaperWindows.clear();
-
-        if (windowObjArray) {
-            mWindowsReady = true;
-
-            jsize length = env->GetArrayLength(windowObjArray);
-            for (jsize i = 0; i < length; i++) {
-                jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i);
-                if (! inputTargetObj) {
-                    break; // found null element indicating end of used portion of the array
-                }
-
-                mWindows.push();
-                InputWindow& window = mWindows.editTop();
-                bool valid = populateWindow(env, inputTargetObj, window);
-                if (! valid) {
-                    mWindows.pop();
-                }
-
-                env->DeleteLocalRef(inputTargetObj);
-            }
-
-            size_t numWindows = mWindows.size();
-            for (size_t i = 0; i < numWindows; i++) {
-                InputWindow* window = & mWindows.editItemAt(i);
-                if (window->hasFocus) {
-                    mFocusedWindow = window;
-                }
-
-                if (window->layoutParamsType == TYPE_WALLPAPER) {
-                    mWallpaperWindows.push(window);
-
-                    for (size_t j = 0; j < numTouchedWallpapers; j++) {
-                        if (window->inputChannel == mTempTouchedWallpaperChannels[i]) {
-                            mTouchedWallpaperWindows.push(window);
-                        }
-                    }
-                }
-
-                if (window->inputChannel == touchedWindowChannel) {
-                    mTouchedWindow = window;
-                }
-            }
-        } else {
-            mWindowsReady = false;
+        windows.push();
+        InputWindow& window = windows.editTop();
+        bool valid = populateWindow(env, inputTargetObj, window);
+        if (! valid) {
+            windows.pop();
         }
 
-        mTempTouchedWallpaperChannels.clear();
+        env->DeleteLocalRef(inputTargetObj);
+    }
 
-        if ((hadFocusedWindow && ! mFocusedWindow)
-                || (mFocusedWindow && ! mFocusedWindow->visible)) {
-            preemptInputDispatch();
-        }
-
-        mDispatchStateChanged.broadcast();
-
-#if DEBUG_FOCUS
-        logDispatchStateLd();
-#endif
-    } // release lock
+    mInputManager->getDispatcher()->setInputWindows(windows);
 }
 
 bool NativeInputManager::populateWindow(JNIEnv* env, jobject windowObj,
@@ -1127,6 +816,8 @@
         sp<InputChannel> inputChannel =
                 android_view_InputChannel_getInputChannel(env, inputChannelObj);
         if (inputChannel != NULL) {
+            jstring name = jstring(env->GetObjectField(windowObj,
+                    gInputWindowClassInfo.name));
             jint layoutParamsFlags = env->GetIntField(windowObj,
                     gInputWindowClassInfo.layoutParamsFlags);
             jint layoutParamsType = env->GetIntField(windowObj,
@@ -1159,18 +850,25 @@
                     gInputWindowClassInfo.touchableAreaBottom);
             jboolean visible = env->GetBooleanField(windowObj,
                     gInputWindowClassInfo.visible);
+            jboolean canReceiveKeys = env->GetBooleanField(windowObj,
+                    gInputWindowClassInfo.canReceiveKeys);
             jboolean hasFocus = env->GetBooleanField(windowObj,
                     gInputWindowClassInfo.hasFocus);
             jboolean hasWallpaper = env->GetBooleanField(windowObj,
                     gInputWindowClassInfo.hasWallpaper);
             jboolean paused = env->GetBooleanField(windowObj,
                     gInputWindowClassInfo.paused);
+            jint layer = env->GetIntField(windowObj,
+                    gInputWindowClassInfo.layer);
             jint ownerPid = env->GetIntField(windowObj,
                     gInputWindowClassInfo.ownerPid);
             jint ownerUid = env->GetIntField(windowObj,
                     gInputWindowClassInfo.ownerUid);
 
+            const char* nameStr = env->GetStringUTFChars(name, NULL);
+
             outWindow.inputChannel = inputChannel;
+            outWindow.name.setTo(nameStr);
             outWindow.layoutParamsFlags = layoutParamsFlags;
             outWindow.layoutParamsType = layoutParamsType;
             outWindow.dispatchingTimeout = dispatchingTimeoutNanos;
@@ -1187,11 +885,15 @@
             outWindow.touchableAreaRight = touchableAreaRight;
             outWindow.touchableAreaBottom = touchableAreaBottom;
             outWindow.visible = visible;
+            outWindow.canReceiveKeys = canReceiveKeys;
             outWindow.hasFocus = hasFocus;
             outWindow.hasWallpaper = hasWallpaper;
             outWindow.paused = paused;
+            outWindow.layer = layer;
             outWindow.ownerPid = ownerPid;
             outWindow.ownerUid = ownerUid;
+
+            env->ReleaseStringUTFChars(name, nameStr);
             valid = true;
         } else {
             LOGW("Dropping input target because its input channel is not initialized.");
@@ -1205,923 +907,84 @@
 }
 
 void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationObj) {
-#if DEBUG_FOCUS
-    LOGD("setFocusedApplication");
-#endif
-    { // acquire lock
-        AutoMutex _l(mDispatchLock);
+    if (applicationObj) {
+        jstring nameObj = jstring(env->GetObjectField(applicationObj,
+                gInputApplicationClassInfo.name));
+        jlong dispatchingTimeoutNanos = env->GetLongField(applicationObj,
+                gInputApplicationClassInfo.dispatchingTimeoutNanos);
+        jobject tokenObj = env->GetObjectField(applicationObj,
+                gInputApplicationClassInfo.token);
+        jweak tokenObjWeak = env->NewWeakGlobalRef(tokenObj);
+        if (! tokenObjWeak) {
+            LOGE("Could not create weak reference for application token.");
+            LOGE_EX(env);
+            env->ExceptionClear();
+        }
+        env->DeleteLocalRef(tokenObj);
 
-        releaseFocusedApplicationLd(env);
-
-        if (applicationObj) {
-            jstring nameObj = jstring(env->GetObjectField(applicationObj,
-                    gInputApplicationClassInfo.name));
-            jlong dispatchingTimeoutNanos = env->GetLongField(applicationObj,
-                    gInputApplicationClassInfo.dispatchingTimeoutNanos);
-            jobject tokenObj = env->GetObjectField(applicationObj,
-                    gInputApplicationClassInfo.token);
-            jweak tokenObjWeak = env->NewWeakGlobalRef(tokenObj);
-            if (! tokenObjWeak) {
-                LOGE("Could not create weak reference for application token.");
-                LOGE_EX(env);
-                env->ExceptionClear();
-            }
-            env->DeleteLocalRef(tokenObj);
-
-            mFocusedApplication = & mFocusedApplicationStorage;
-
-            if (nameObj) {
-                const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
-                mFocusedApplication->name.setTo(nameStr);
-                env->ReleaseStringUTFChars(nameObj, nameStr);
-                env->DeleteLocalRef(nameObj);
-            } else {
-                LOGE("InputApplication.name should not be null.");
-                mFocusedApplication->name.setTo("unknown");
-            }
-
-            mFocusedApplication->dispatchingTimeout = dispatchingTimeoutNanos;
-            mFocusedApplication->tokenObjWeak = tokenObjWeak;
+        String8 name;
+        if (nameObj) {
+            const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
+            name.setTo(nameStr);
+            env->ReleaseStringUTFChars(nameObj, nameStr);
+            env->DeleteLocalRef(nameObj);
+        } else {
+            LOGE("InputApplication.name should not be null.");
+            name.setTo("unknown");
         }
 
-        mDispatchStateChanged.broadcast();
-
-#if DEBUG_FOCUS
-        logDispatchStateLd();
-#endif
-    } // release lock
-}
-
-void NativeInputManager::releaseFocusedApplicationLd(JNIEnv* env) {
-    if (mFocusedApplication) {
-        env->DeleteWeakGlobalRef(mFocusedApplication->tokenObjWeak);
-        mFocusedApplication = NULL;
+        InputApplication application;
+        application.name = name;
+        application.dispatchingTimeout = dispatchingTimeoutNanos;
+        application.handle = new ApplicationToken(tokenObjWeak);
+        mInputManager->getDispatcher()->setFocusedApplication(& application);
+    } else {
+        mInputManager->getDispatcher()->setFocusedApplication(NULL);
     }
 }
 
 void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) {
-#if DEBUG_FOCUS
-    LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
-#endif
-
-    { // acquire lock
-        AutoMutex _l(mDispatchLock);
-
-        if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
-            mDispatchEnabled = enabled;
-            mDispatchFrozen = frozen;
-
-            mDispatchStateChanged.broadcast();
-        }
-
-#if DEBUG_FOCUS
-        logDispatchStateLd();
-#endif
-    } // release lock
+    mInputManager->getDispatcher()->setInputDispatchMode(enabled, frozen);
 }
 
-void NativeInputManager::preemptInputDispatch() {
-#if DEBUG_FOCUS
-    LOGD("preemptInputDispatch");
-#endif
-
-    mInputManager->preemptInputDispatch();
-}
-
-int32_t NativeInputManager::waitForFocusedWindowLd(uint32_t policyFlags,
-        int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets,
-        InputWindow*& outFocusedWindow) {
-
-    int32_t injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
-    bool firstIteration = true;
-    ANRTimer anrTimer;
-    for (;;) {
-        if (firstIteration) {
-            firstIteration = false;
-        } else {
-            if (! anrTimer.waitForDispatchStateChangeLd(this)) {
-                LOGW("Dropping event because the dispatcher timed out waiting to identify "
-                        "the window that should receive it.");
-                injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
-                break;
-            }
-        }
-
-        // If dispatch is not enabled then fail.
-        if (! mDispatchEnabled) {
-            LOGI("Dropping event because input dispatch is disabled.");
-            injectionResult = INPUT_EVENT_INJECTION_FAILED;
-            break;
-        }
-
-        // If dispatch is frozen or we don't have valid window data yet then wait.
-        if (mDispatchFrozen || ! mWindowsReady) {
-#if DEBUG_FOCUS
-            LOGD("Waiting because dispatch is frozen or windows are not ready.");
-#endif
-            anrTimer.dispatchFrozenBySystem();
-            continue;
-        }
-
-        // If there is no currently focused window and no focused application
-        // then drop the event.
-        if (! mFocusedWindow) {
-            if (mFocusedApplication) {
-#if DEBUG_FOCUS
-                LOGD("Waiting because there is no focused window but there is a "
-                        "focused application that may yet introduce a new target: '%s'.",
-                        mFocusedApplication->name.string());
-#endif
-                continue;
-            }
-
-            LOGI("Dropping event because there is no focused window or focused application.");
-            injectionResult = INPUT_EVENT_INJECTION_FAILED;
-            break;
-        }
-
-        // Check permissions.
-        if (! checkInjectionPermission(mFocusedWindow, injectorPid, injectorUid)) {
-            injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
-            break;
-        }
-
-        // If the currently focused window is paused then keep waiting.
-        if (mFocusedWindow->paused) {
-#if DEBUG_FOCUS
-            LOGD("Waiting because focused window is paused.");
-#endif
-            anrTimer.dispatchPausedByApplication(mFocusedWindow);
-            continue;
-        }
-
-        // Success!
-        break; // done waiting, exit loop
-    }
-
-    // Output targets.
-    if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
-        addTarget(mFocusedWindow, InputTarget::FLAG_SYNC,
-                anrTimer.getTimeSpentWaitingForApplication(), outTargets);
-
-        outFocusedWindow = mFocusedWindow;
-    } else {
-        outFocusedWindow = NULL;
-    }
-
-#if DEBUG_FOCUS
-    LOGD("waitForFocusedWindow finished: injectionResult=%d",
-            injectionResult);
-    logDispatchStateLd();
-#endif
-    return injectionResult;
-}
-
-enum InjectionPermission {
-    INJECTION_PERMISSION_UNKNOWN,
-    INJECTION_PERMISSION_GRANTED,
-    INJECTION_PERMISSION_DENIED
-};
-
-int32_t NativeInputManager::waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags,
-        int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets,
-        InputWindow*& outTouchedWindow) {
-    nsecs_t startTime = now();
-
-    // For security reasons, we defer updating the touch state until we are sure that
-    // event injection will be allowed.
-    //
-    // FIXME In the original code, screenWasOff could never be set to true.
-    //       The reason is that the POLICY_FLAG_WOKE_HERE
-    //       and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw
-    //       EV_KEY, EV_REL and EV_ABS events.  As it happens, the touch event was
-    //       actually enqueued using the policyFlags that appeared in the final EV_SYN
-    //       events upon which no preprocessing took place.  So policyFlags was always 0.
-    //       In the new native input dispatcher we're a bit more careful about event
-    //       preprocessing so the touches we receive can actually have non-zero policyFlags.
-    //       Unfortunately we obtain undesirable behavior.
-    //
-    //       Here's what happens:
-    //
-    //       When the device dims in anticipation of going to sleep, touches
-    //       in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause
-    //       the device to brighten and reset the user activity timer.
-    //       Touches on other windows (such as the launcher window)
-    //       are dropped.  Then after a moment, the device goes to sleep.  Oops.
-    //
-    //       Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE
-    //       instead of POLICY_FLAG_WOKE_HERE...
-    //
-    bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE;
-
-    int32_t action = motionEvent->getAction();
-
-    bool firstIteration = true;
-    ANRTimer anrTimer;
-    int32_t injectionResult;
-    InjectionPermission injectionPermission;
-    for (;;) {
-        if (firstIteration) {
-            firstIteration = false;
-        } else {
-            if (! anrTimer.waitForDispatchStateChangeLd(this)) {
-                LOGW("Dropping event because the dispatcher timed out waiting to identify "
-                        "the window that should receive it.");
-                injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
-                injectionPermission = INJECTION_PERMISSION_UNKNOWN;
-                break; // timed out, exit wait loop
-            }
-        }
-
-        // If dispatch is not enabled then fail.
-        if (! mDispatchEnabled) {
-            LOGI("Dropping event because input dispatch is disabled.");
-            injectionResult = INPUT_EVENT_INJECTION_FAILED;
-            injectionPermission = INJECTION_PERMISSION_UNKNOWN;
-            break; // failed, exit wait loop
-        }
-
-        // If dispatch is frozen or we don't have valid window data yet then wait.
-        if (mDispatchFrozen || ! mWindowsReady) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-            LOGD("Waiting because dispatch is frozen or windows are not ready.");
-#endif
-            anrTimer.dispatchFrozenBySystem();
-            continue;
-        }
-
-        // Update the touch state as needed based on the properties of the touch event.
-        if (action == AMOTION_EVENT_ACTION_DOWN) {
-            /* Case 1: ACTION_DOWN */
-
-            InputWindow* newTouchedWindow = NULL;
-            mTempTouchedOutsideTargets.clear();
-
-            int32_t x = int32_t(motionEvent->getX(0));
-            int32_t y = int32_t(motionEvent->getY(0));
-            InputWindow* topErrorWindow = NULL;
-            bool obscured = false;
-
-            // Traverse windows from front to back to find touched window and outside targets.
-            size_t numWindows = mWindows.size();
-            for (size_t i = 0; i < numWindows; i++) {
-                InputWindow* window = & mWindows.editItemAt(i);
-                int32_t flags = window->layoutParamsFlags;
-
-                if (flags & FLAG_SYSTEM_ERROR) {
-                    if (! topErrorWindow) {
-                        topErrorWindow = window;
-                    }
-                }
-
-                if (window->visible) {
-                    if (! (flags & FLAG_NOT_TOUCHABLE)) {
-                        bool isTouchModal = (flags &
-                                (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL)) == 0;
-                        if (isTouchModal || window->touchableAreaContainsPoint(x, y)) {
-                            if (! screenWasOff || flags & FLAG_TOUCHABLE_WHEN_WAKING) {
-                                newTouchedWindow = window;
-                                obscured = isWindowObscuredLocked(window);
-                            }
-                            break; // found touched window, exit window loop
-                        }
-                    }
-
-                    if (flags & FLAG_WATCH_OUTSIDE_TOUCH) {
-                        OutsideTarget outsideTarget;
-                        outsideTarget.window = window;
-                        outsideTarget.obscured = isWindowObscuredLocked(window);
-                        mTempTouchedOutsideTargets.push(outsideTarget);
-                    }
-                }
-            }
-
-            // If there is an error window but it is not taking focus (typically because
-            // it is invisible) then wait for it.  Any other focused window may in
-            // fact be in ANR state.
-            if (topErrorWindow && newTouchedWindow != topErrorWindow) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-                LOGD("Waiting because system error window is pending.");
-#endif
-                anrTimer.dispatchFrozenBySystem();
-                continue; // wait some more
-            }
-
-            // If we did not find a touched window then fail.
-            if (! newTouchedWindow) {
-                if (mFocusedApplication) {
-#if DEBUG_FOCUS
-                    LOGD("Waiting because there is no focused window but there is a "
-                            "focused application that may yet introduce a new target: '%s'.",
-                            mFocusedApplication->name.string());
-#endif
-                    continue;
-                }
-
-                LOGI("Dropping event because there is no touched window or focused application.");
-                injectionResult = INPUT_EVENT_INJECTION_FAILED;
-                injectionPermission = INJECTION_PERMISSION_UNKNOWN;
-                break; // failed, exit wait loop
-            }
-
-            // Check permissions.
-            if (! checkInjectionPermission(newTouchedWindow, injectorPid, injectorUid)) {
-                injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
-                injectionPermission = INJECTION_PERMISSION_DENIED;
-                break; // failed, exit wait loop
-            }
-
-            // If the touched window is paused then keep waiting.
-            if (newTouchedWindow->paused) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-                LOGD("Waiting because touched window is paused.");
-#endif
-                anrTimer.dispatchPausedByApplication(newTouchedWindow);
-                continue; // wait some more
-            }
-
-            // Success!  Update the touch dispatch state for real.
-            releaseTouchedWindowLd();
-
-            mTouchedWindow = newTouchedWindow;
-            mTouchedWindowIsObscured = obscured;
-
-            if (newTouchedWindow->hasWallpaper) {
-                mTouchedWallpaperWindows.appendVector(mWallpaperWindows);
-            }
-
-            injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
-            injectionPermission = INJECTION_PERMISSION_GRANTED;
-            break; // done
-        } else {
-            /* Case 2: Everything but ACTION_DOWN */
-
-            // Check permissions.
-            if (! checkInjectionPermission(mTouchedWindow, injectorPid, injectorUid)) {
-                injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
-                injectionPermission = INJECTION_PERMISSION_DENIED;
-                break; // failed, exit wait loop
-            }
-
-            // If the pointer is not currently down, then ignore the event.
-            if (! mTouchDown) {
-                LOGI("Dropping event because the pointer is not down.");
-                injectionResult = INPUT_EVENT_INJECTION_FAILED;
-                injectionPermission = INJECTION_PERMISSION_GRANTED;
-                break; // failed, exit wait loop
-            }
-
-            // If there is no currently touched window then fail.
-            if (! mTouchedWindow) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-                LOGD("Dropping event because there is no touched window to receive it.");
-#endif
-                injectionResult = INPUT_EVENT_INJECTION_FAILED;
-                injectionPermission = INJECTION_PERMISSION_GRANTED;
-                break; // failed, exit wait loop
-            }
-
-            // If the touched window is paused then keep waiting.
-            if (mTouchedWindow->paused) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-                LOGD("Waiting because touched window is paused.");
-#endif
-                anrTimer.dispatchPausedByApplication(mTouchedWindow);
-                continue; // wait some more
-            }
-
-            // Success!
-            injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
-            injectionPermission = INJECTION_PERMISSION_GRANTED;
-            break; // done
-        }
-    }
-
-    // Output targets.
-    if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
-        size_t numWallpaperWindows = mTouchedWallpaperWindows.size();
-        for (size_t i = 0; i < numWallpaperWindows; i++) {
-            addTarget(mTouchedWallpaperWindows[i],
-                    InputTarget::FLAG_WINDOW_IS_OBSCURED, 0, outTargets);
-        }
-
-        size_t numOutsideTargets = mTempTouchedOutsideTargets.size();
-        for (size_t i = 0; i < numOutsideTargets; i++) {
-            const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i];
-            int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE;
-            if (outsideTarget.obscured) {
-                outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
-            }
-            addTarget(outsideTarget.window, outsideTargetFlags, 0, outTargets);
-        }
-
-        int32_t targetFlags = InputTarget::FLAG_SYNC;
-        if (mTouchedWindowIsObscured) {
-            targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
-        }
-        addTarget(mTouchedWindow, targetFlags,
-                anrTimer.getTimeSpentWaitingForApplication(), outTargets);
-        outTouchedWindow = mTouchedWindow;
-    } else {
-        outTouchedWindow = NULL;
-    }
-    mTempTouchedOutsideTargets.clear();
-
-    // Check injection permission once and for all.
-    if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
-        if (checkInjectionPermission(action == AMOTION_EVENT_ACTION_DOWN ? NULL : mTouchedWindow,
-                injectorPid, injectorUid)) {
-            injectionPermission = INJECTION_PERMISSION_GRANTED;
-        } else {
-            injectionPermission = INJECTION_PERMISSION_DENIED;
-        }
-    }
-
-    // Update final pieces of touch state if the injector had permission.
-    if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
-        if (action == AMOTION_EVENT_ACTION_DOWN) {
-            if (mTouchDown) {
-                // This is weird.  We got a down but we thought it was already down!
-                LOGW("Pointer down received while already down.");
-            } else {
-                mTouchDown = true;
-            }
-
-            if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
-                // Since we failed to identify a target for this touch down, we may still
-                // be holding on to an earlier target from a previous touch down.  Release it.
-                releaseTouchedWindowLd();
-            }
-        } else if (action == AMOTION_EVENT_ACTION_UP) {
-            mTouchDown = false;
-            releaseTouchedWindowLd();
-        }
-    } else {
-        LOGW("Not updating touch focus because injection was denied.");
-    }
-
-#if DEBUG_FOCUS
-    LOGD("waitForTouchedWindow finished: injectionResult=%d",
-            injectionResult);
-    logDispatchStateLd();
-#endif
-    return injectionResult;
-}
-
-void NativeInputManager::releaseTouchedWindowLd() {
-    mTouchedWindow = NULL;
-    mTouchedWindowIsObscured = false;
-    mTouchedWallpaperWindows.clear();
-}
-
-void NativeInputManager::addTarget(const InputWindow* window, int32_t targetFlags,
-        nsecs_t timeSpentWaitingForApplication, Vector<InputTarget>& outTargets) {
-    nsecs_t timeout = window->dispatchingTimeout - timeSpentWaitingForApplication;
-    if (timeout < MIN_INPUT_DISPATCHING_TIMEOUT) {
-        timeout = MIN_INPUT_DISPATCHING_TIMEOUT;
-    }
-
-    outTargets.push();
-
-    InputTarget& target = outTargets.editTop();
-    target.inputChannel = window->inputChannel;
-    target.flags = targetFlags;
-    target.timeout = timeout;
-    target.xOffset = - window->frameLeft;
-    target.yOffset = - window->frameTop;
-}
-
-bool NativeInputManager::checkInjectionPermission(const InputWindow* window,
-        int32_t injectorPid, int32_t injectorUid) {
-    if (injectorUid > 0 && (window == NULL || window->ownerUid != injectorUid)) {
-        JNIEnv* env = jniEnv();
-        jboolean result = env->CallBooleanMethod(mCallbacksObj,
-                gCallbacksClassInfo.checkInjectEventsPermission, injectorPid, injectorUid);
-        checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission");
-
-        if (! result) {
-            if (window) {
-                LOGW("Permission denied: injecting event from pid %d uid %d to window "
-                        "with input channel %s owned by uid %d",
-                        injectorPid, injectorUid, window->inputChannel->getName().string(),
-                        window->ownerUid);
-            } else {
-                LOGW("Permission denied: injecting event from pid %d uid %d",
-                        injectorPid, injectorUid);
-            }
-            return false;
-        }
-    }
-
-    return true;
-}
-
-bool NativeInputManager::isWindowObscuredLocked(const InputWindow* window) {
-    size_t numWindows = mWindows.size();
-    for (size_t i = 0; i < numWindows; i++) {
-        const InputWindow* other = & mWindows.itemAt(i);
-        if (other == window) {
-            break;
-        }
-        if (other->visible && window->visibleFrameIntersects(other)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-int32_t NativeInputManager::waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
-        int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    LOGD("waitForKeyEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
-            policyFlags, injectorPid, injectorUid);
-#endif
-
-    int32_t windowType;
-    { // acquire lock
-        AutoMutex _l(mDispatchLock);
-
-        InputWindow* focusedWindow;
-        int32_t injectionResult = waitForFocusedWindowLd(policyFlags,
-                injectorPid, injectorUid, outTargets, /*out*/ focusedWindow);
-        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
-            return injectionResult;
-        }
-
-        windowType = focusedWindow->layoutParamsType;
-    } // release lock
-
-    if (isPolicyKey(keyEvent->getKeyCode(), isScreenOn())) {
-        const InputTarget& target = outTargets.top();
-        bool consumed = interceptKeyBeforeDispatching(target, keyEvent, policyFlags);
-        if (consumed) {
-            outTargets.clear();
-            return INPUT_EVENT_INJECTION_SUCCEEDED;
-        }
-
-        addMonitoringTargetsLd(outTargets);
-    }
-
-    pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT);
-    return INPUT_EVENT_INJECTION_SUCCEEDED;
-}
-
-int32_t NativeInputManager::waitForMotionEventTargets(MotionEvent* motionEvent,
-        uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid,
-        Vector<InputTarget>& outTargets) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    LOGD("waitForMotionEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
-            policyFlags, injectorPid, injectorUid);
-#endif
-
-    int32_t source = motionEvent->getSource();
-    if (source & AINPUT_SOURCE_CLASS_POINTER) {
-        return waitForTouchEventTargets(motionEvent, policyFlags, injectorPid, injectorUid,
-                outTargets);
-    } else {
-        return waitForNonTouchEventTargets(motionEvent, policyFlags, injectorPid, injectorUid,
-                outTargets);
-    }
-}
-
-int32_t NativeInputManager::waitForNonTouchEventTargets(MotionEvent* motionEvent,
-        uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid,
-        Vector<InputTarget>& outTargets) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    LOGD("waitForNonTouchEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
-            policyFlags, injectorPid, injectorUid);
-#endif
-
-    int32_t windowType;
-    { // acquire lock
-        AutoMutex _l(mDispatchLock);
-
-        InputWindow* focusedWindow;
-        int32_t injectionResult = waitForFocusedWindowLd(policyFlags,
-                injectorPid, injectorUid, outTargets, /*out*/ focusedWindow);
-        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
-            return injectionResult;
-        }
-
-        windowType = focusedWindow->layoutParamsType;
-
-        addMonitoringTargetsLd(outTargets);
-    } // release lock
-
-    pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT);
-    return INPUT_EVENT_INJECTION_SUCCEEDED;
-}
-
-int32_t NativeInputManager::waitForTouchEventTargets(MotionEvent* motionEvent,
-        uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid,
-        Vector<InputTarget>& outTargets) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    LOGD("waitForTouchEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
-            policyFlags, injectorPid, injectorUid);
-#endif
-
-    int32_t windowType;
-    { // acquire lock
-        AutoMutex _l(mDispatchLock);
-
-        InputWindow* touchedWindow;
-        int32_t injectionResult = waitForTouchedWindowLd(motionEvent, policyFlags,
-                injectorPid, injectorUid, outTargets, /*out*/ touchedWindow);
-        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
-            return injectionResult;
-        }
-
-        windowType = touchedWindow->layoutParamsType;
-
-        addMonitoringTargetsLd(outTargets);
-    } // release lock
-
-    int32_t eventType;
-    switch (motionEvent->getAction()) {
-    case AMOTION_EVENT_ACTION_DOWN:
-        eventType = POWER_MANAGER_TOUCH_EVENT;
-        break;
-    case AMOTION_EVENT_ACTION_UP:
-        eventType = POWER_MANAGER_TOUCH_UP_EVENT;
-        break;
-    default:
-        if (motionEvent->getEventTime() - motionEvent->getDownTime()
-                >= EVENT_IGNORE_DURATION) {
-            eventType = POWER_MANAGER_TOUCH_EVENT;
-        } else {
-            eventType = POWER_MANAGER_LONG_TOUCH_EVENT;
-        }
-        break;
-    }
-    pokeUserActivityIfNeeded(windowType, eventType);
-    return INPUT_EVENT_INJECTION_SUCCEEDED;
-}
-
-bool NativeInputManager::interceptKeyBeforeDispatching(const InputTarget& target,
+bool NativeInputManager::interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
         const KeyEvent* keyEvent, uint32_t policyFlags) {
-    JNIEnv* env = jniEnv();
-
-    jobject inputChannelObj = getInputChannelObjLocal(env, target.inputChannel);
-    if (inputChannelObj) {
-        jboolean consumed = env->CallBooleanMethod(mCallbacksObj,
-                gCallbacksClassInfo.interceptKeyBeforeDispatching,
-                inputChannelObj, keyEvent->getAction(), keyEvent->getFlags(),
-                keyEvent->getKeyCode(), keyEvent->getMetaState(),
-                keyEvent->getRepeatCount(), policyFlags);
-        bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
-
-        env->DeleteLocalRef(inputChannelObj);
-
-        return consumed && ! error;
-    } else {
-        LOGW("Could not apply key dispatch policy because input channel '%s' is "
-                "no longer valid.", target.inputChannel->getName().string());
+    bool isScreenOn = this->isScreenOn();
+    if (! isPolicyKey(keyEvent->getKeyCode(), isScreenOn)) {
         return false;
     }
+
+    JNIEnv* env = jniEnv();
+
+    // Note: inputChannel may be null.
+    jobject inputChannelObj = getInputChannelObjLocal(env, inputChannel);
+    jboolean consumed = env->CallBooleanMethod(mCallbacksObj,
+            gCallbacksClassInfo.interceptKeyBeforeDispatching,
+            inputChannelObj, keyEvent->getAction(), keyEvent->getFlags(),
+            keyEvent->getKeyCode(), keyEvent->getMetaState(),
+            keyEvent->getRepeatCount(), policyFlags);
+    bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
+
+    env->DeleteLocalRef(inputChannelObj);
+    return consumed && ! error;
 }
 
-void NativeInputManager::pokeUserActivityIfNeeded(int32_t windowType, int32_t eventType) {
-    if (windowType != TYPE_KEYGUARD) {
-        nsecs_t eventTime = now();
-        pokeUserActivity(eventTime, eventType);
+void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType) {
+    if (windowType != InputWindow::TYPE_KEYGUARD) {
+        android_server_PowerManagerService_userActivity(eventTime, eventType);
     }
 }
 
-void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
-    android_server_PowerManagerService_userActivity(eventTime, eventType);
-}
 
-void NativeInputManager::registerMonitoringChannel(const sp<InputChannel>& inputChannel) {
-    { // acquire lock
-         AutoMutex _l(mDispatchLock);
-         mMonitoringChannels.push(inputChannel);
-    } // release lock
-}
-
-void NativeInputManager::unregisterMonitoringChannel(const sp<InputChannel>& inputChannel) {
-    { // acquire lock
-         AutoMutex _l(mDispatchLock);
-
-         for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
-             if (mMonitoringChannels[i] == inputChannel) {
-                 mMonitoringChannels.removeAt(i);
-                 break;
-             }
-         }
-    } // release lock
-}
-
-void NativeInputManager::addMonitoringTargetsLd(Vector<InputTarget>& outTargets) {
-    for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
-        outTargets.push();
-
-        InputTarget& target = outTargets.editTop();
-        target.inputChannel = mMonitoringChannels[i];
-        target.flags = 0;
-        target.timeout = -1;
-        target.xOffset = 0;
-        target.yOffset = 0;
-    }
-}
-
-static void dumpMotionRange(String8& dump,
-        const char* name, const InputDeviceInfo::MotionRange* range) {
-    if (range) {
-        dump.appendFormat("      %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n",
-                name, range->min, range->max, range->flat, range->fuzz);
-    }
-}
-
-#define DUMP_MOTION_RANGE(range) \
-    dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range));
-
-void NativeInputManager::dumpDeviceInfo(String8& dump) {
-    Vector<int32_t> deviceIds;
-    mInputManager->getInputDeviceIds(deviceIds);
-
-    InputDeviceInfo deviceInfo;
-    for (size_t i = 0; i < deviceIds.size(); i++) {
-        int32_t deviceId = deviceIds[i];
-
-        status_t result = mInputManager->getInputDeviceInfo(deviceId, & deviceInfo);
-        if (result == NAME_NOT_FOUND) {
-            continue;
-        } else if (result != OK) {
-            dump.appendFormat("  ** Unexpected error %d getting information about input devices.\n",
-                    result);
-            continue;
-        }
-
-        dump.appendFormat("  Device %d: '%s'\n",
-                deviceInfo.getId(), deviceInfo.getName().string());
-        dump.appendFormat("    sources = 0x%08x\n",
-                deviceInfo.getSources());
-        dump.appendFormat("    keyboardType = %d\n",
-                deviceInfo.getKeyboardType());
-
-        dump.append("    motion ranges:\n");
-        DUMP_MOTION_RANGE(X);
-        DUMP_MOTION_RANGE(Y);
-        DUMP_MOTION_RANGE(PRESSURE);
-        DUMP_MOTION_RANGE(SIZE);
-        DUMP_MOTION_RANGE(TOUCH_MAJOR);
-        DUMP_MOTION_RANGE(TOUCH_MINOR);
-        DUMP_MOTION_RANGE(TOOL_MAJOR);
-        DUMP_MOTION_RANGE(TOOL_MINOR);
-        DUMP_MOTION_RANGE(ORIENTATION);
-    }
-}
-
-#undef DUMP_MOTION_RANGE
-
-void NativeInputManager::logDispatchStateLd() {
-    String8 dump;
-    dumpDispatchStateLd(dump);
-    LOGD("%s", dump.string());
-}
-
-void NativeInputManager::dumpDispatchStateLd(String8& dump) {
-    dump.appendFormat("  dispatchEnabled: %d\n", mDispatchEnabled);
-    dump.appendFormat("  dispatchFrozen: %d\n", mDispatchFrozen);
-    dump.appendFormat("  windowsReady: %d\n", mWindowsReady);
-
-    if (mFocusedApplication) {
-        dump.appendFormat("  focusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
-                mFocusedApplication->name.string(),
-                mFocusedApplication->dispatchingTimeout / 1000000.0);
-    } else {
-        dump.append("  focusedApplication: <null>\n");
-    }
-    dump.appendFormat("  focusedWindow: '%s'\n",
-            mFocusedWindow != NULL ? mFocusedWindow->inputChannel->getName().string() : "<null>");
-    dump.appendFormat("  touchedWindow: '%s', touchDown=%d\n",
-            mTouchedWindow != NULL ? mTouchedWindow->inputChannel->getName().string() : "<null>",
-            mTouchDown);
-    for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) {
-        dump.appendFormat("  touchedWallpaperWindows[%d]: '%s'\n",
-                i, mTouchedWallpaperWindows[i]->inputChannel->getName().string());
-    }
-    for (size_t i = 0; i < mWindows.size(); i++) {
-        dump.appendFormat("  windows[%d]: '%s', paused=%d, hasFocus=%d, hasWallpaper=%d, "
-                "visible=%d, flags=0x%08x, type=0x%08x, "
-                "frame=[%d,%d][%d,%d], "
-                "visibleFrame=[%d,%d][%d,%d], "
-                "touchableArea=[%d,%d][%d,%d], "
-                "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
-                i, mWindows[i].inputChannel->getName().string(),
-                mWindows[i].paused, mWindows[i].hasFocus, mWindows[i].hasWallpaper,
-                mWindows[i].visible, mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType,
-                mWindows[i].frameLeft, mWindows[i].frameTop,
-                mWindows[i].frameRight, mWindows[i].frameBottom,
-                mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop,
-                mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom,
-                mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop,
-                mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom,
-                mWindows[i].ownerPid, mWindows[i].ownerUid,
-                mWindows[i].dispatchingTimeout / 1000000.0);
-    }
-
-    for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
-        dump.appendFormat("  monitoringChannel[%d]: '%s'\n",
-                i, mMonitoringChannels[i]->getName().string());
-    }
-}
-
-// ----------------------------------------------------------------------------
-
-bool NativeInputManager::InputWindow::visibleFrameIntersects(const InputWindow* other) const {
-    return visibleFrameRight > other->visibleFrameLeft
-        && visibleFrameLeft < other->visibleFrameRight
-        && visibleFrameBottom > other->visibleFrameTop
-        && visibleFrameTop < other->visibleFrameBottom;
-}
-
-bool NativeInputManager::InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const {
-    return x >= touchableAreaLeft && x <= touchableAreaRight
-            && y >= touchableAreaTop && y <= touchableAreaBottom;
-}
-
-// ----------------------------------------------------------------------------
-
-NativeInputManager::ANRTimer::ANRTimer() :
-        mBudget(APPLICATION), mStartTime(now()), mFrozen(false), mPausedWindow(NULL) {
-}
-
-void NativeInputManager::ANRTimer::dispatchFrozenBySystem() {
-    mFrozen = true;
-}
-
-void NativeInputManager::ANRTimer::dispatchPausedByApplication(InputWindow* pausedWindow) {
-    mPausedWindow = pausedWindow;
-}
-
-bool NativeInputManager::ANRTimer::waitForDispatchStateChangeLd(NativeInputManager* inputManager) {
-    nsecs_t currentTime = now();
-
-    Budget newBudget;
-    nsecs_t dispatchingTimeout;
-    sp<InputChannel> pausedChannel = NULL;
-    jobject tokenObj = NULL;
-    if (mFrozen) {
-        newBudget = SYSTEM;
-        dispatchingTimeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
-        mFrozen = false;
-    } else if (mPausedWindow) {
-        newBudget = APPLICATION;
-        dispatchingTimeout = mPausedWindow->dispatchingTimeout;
-        pausedChannel = mPausedWindow->inputChannel;
-        mPausedWindow = NULL;
-    } else if (inputManager->mFocusedApplication) {
-        newBudget = APPLICATION;
-        dispatchingTimeout = inputManager->mFocusedApplication->dispatchingTimeout;
-        tokenObj = jniEnv()->NewLocalRef(inputManager->mFocusedApplication->tokenObjWeak);
-    } else {
-        newBudget = APPLICATION;
-        dispatchingTimeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
-    }
-
-    if (mBudget != newBudget) {
-        mBudget = newBudget;
-        mStartTime = currentTime;
-    }
-
-    bool result = false;
-    nsecs_t timeoutRemaining = mStartTime + dispatchingTimeout - currentTime;
-    if (timeoutRemaining > 0
-            && inputManager->mDispatchStateChanged.waitRelative(inputManager->mDispatchLock,
-                    timeoutRemaining) == OK) {
-        result = true;
-    } else {
-        if (pausedChannel != NULL || tokenObj != NULL) {
-            bool resumed;
-            nsecs_t newTimeout = 0;
-
-            inputManager->mDispatchLock.unlock(); // release lock
-            if (pausedChannel != NULL) {
-                resumed = inputManager->notifyInputChannelANR(pausedChannel, /*out*/ newTimeout);
-            } else {
-                resumed = inputManager->notifyANR(tokenObj, /*out*/ newTimeout);
-            }
-            inputManager->mDispatchLock.lock(); // re-acquire lock
-
-            if (resumed) {
-                mStartTime = now() - dispatchingTimeout + newTimeout;
-                result = true;
-            }
-        }
-    }
-
-    if (tokenObj) {
-        jniEnv()->DeleteLocalRef(tokenObj);
-    }
-
+bool NativeInputManager::checkInjectEventsPermissionNonReentrant(
+        int32_t injectorPid, int32_t injectorUid) {
+    JNIEnv* env = jniEnv();
+    jboolean result = env->CallBooleanMethod(mCallbacksObj,
+            gCallbacksClassInfo.checkInjectEventsPermission, injectorPid, injectorUid);
+    checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission");
     return result;
 }
 
-nsecs_t NativeInputManager::ANRTimer::getTimeSpentWaitingForApplication() const {
-    return mBudget == APPLICATION ? now() - mStartTime : 0;
-}
-
 // ----------------------------------------------------------------------------
 
 static sp<NativeInputManager> gNativeInputManager;
@@ -2184,7 +1047,7 @@
         return AKEY_STATE_UNKNOWN;
     }
 
-    return gNativeInputManager->getInputManager()->getScanCodeState(
+    return gNativeInputManager->getInputManager()->getReader()->getScanCodeState(
             deviceId, uint32_t(sourceMask), scanCode);
 }
 
@@ -2194,7 +1057,7 @@
         return AKEY_STATE_UNKNOWN;
     }
 
-    return gNativeInputManager->getInputManager()->getKeyCodeState(
+    return gNativeInputManager->getInputManager()->getReader()->getKeyCodeState(
             deviceId, uint32_t(sourceMask), keyCode);
 }
 
@@ -2204,7 +1067,7 @@
         return AKEY_STATE_UNKNOWN;
     }
 
-    return gNativeInputManager->getInputManager()->getSwitchState(
+    return gNativeInputManager->getInputManager()->getReader()->getSwitchState(
             deviceId, uint32_t(sourceMask), sw);
 }
 
@@ -2219,7 +1082,7 @@
     jsize numCodes = env->GetArrayLength(keyCodes);
     jboolean result;
     if (numCodes == env->GetArrayLength(keyCodes)) {
-        result = gNativeInputManager->getInputManager()->hasKeys(
+        result = gNativeInputManager->getInputManager()->getReader()->hasKeys(
                 deviceId, uint32_t(sourceMask), numCodes, codes, flags);
     } else {
         result = JNI_FALSE;
@@ -2306,14 +1169,14 @@
         KeyEvent keyEvent;
         android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
 
-        return gNativeInputManager->getInputManager()->injectInputEvent(& keyEvent,
-                injectorPid, injectorUid, syncMode, timeoutMillis);
+        return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent(
+                & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis);
     } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
         MotionEvent motionEvent;
         android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent);
 
-        return gNativeInputManager->getInputManager()->injectInputEvent(& motionEvent,
-                injectorPid, injectorUid, syncMode, timeoutMillis);
+        return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent(
+                & motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis);
     } else {
         jniThrowRuntimeException(env, "Invalid input event type.");
         return INPUT_EVENT_INJECTION_FAILED;
@@ -2347,15 +1210,6 @@
     gNativeInputManager->setInputDispatchMode(enabled, frozen);
 }
 
-static void android_server_InputManager_nativePreemptInputDispatch(JNIEnv* env,
-        jclass clazz) {
-    if (checkInputManagerUnitialized(env)) {
-        return;
-    }
-
-    gNativeInputManager->preemptInputDispatch();
-}
-
 static jobject android_server_InputManager_nativeGetInputDevice(JNIEnv* env,
         jclass clazz, jint deviceId) {
     if (checkInputManagerUnitialized(env)) {
@@ -2363,7 +1217,7 @@
     }
 
     InputDeviceInfo deviceInfo;
-    status_t status = gNativeInputManager->getInputManager()->getInputDeviceInfo(
+    status_t status = gNativeInputManager->getInputManager()->getReader()->getInputDeviceInfo(
             deviceId, & deviceInfo);
     if (status) {
         return NULL;
@@ -2405,7 +1259,7 @@
     }
 
     Vector<int> deviceIds;
-    gNativeInputManager->getInputManager()->getInputDeviceIds(deviceIds);
+    gNativeInputManager->getInputManager()->getReader()->getInputDeviceIds(deviceIds);
 
     jintArray deviceIdsObj = env->NewIntArray(deviceIds.size());
     if (! deviceIdsObj) {
@@ -2421,7 +1275,8 @@
         return NULL;
     }
 
-    String8 dump(gNativeInputManager->dump());
+    String8 dump;
+    gNativeInputManager->dump(dump);
     return env->NewStringUTF(dump.string());
 }
 
@@ -2457,8 +1312,6 @@
             (void*) android_server_InputManager_nativeSetFocusedApplication },
     { "nativeSetInputDispatchMode", "(ZZ)V",
             (void*) android_server_InputManager_nativeSetInputDispatchMode },
-    { "nativePreemptInputDispatch", "()V",
-            (void*) android_server_InputManager_nativePreemptInputDispatch },
     { "nativeGetInputDevice", "(I)Landroid/view/InputDevice;",
             (void*) android_server_InputManager_nativeGetInputDevice },
     { "nativeGetInputDeviceIds", "()[I",
@@ -2498,14 +1351,8 @@
     GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelBroken, gCallbacksClassInfo.clazz,
             "notifyInputChannelBroken", "(Landroid/view/InputChannel;)V");
 
-    GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelANR, gCallbacksClassInfo.clazz,
-            "notifyInputChannelANR", "(Landroid/view/InputChannel;)J");
-
-    GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelRecoveredFromANR, gCallbacksClassInfo.clazz,
-            "notifyInputChannelRecoveredFromANR", "(Landroid/view/InputChannel;)V");
-
     GET_METHOD_ID(gCallbacksClassInfo.notifyANR, gCallbacksClassInfo.clazz,
-            "notifyANR", "(Ljava/lang/Object;)J");
+            "notifyANR", "(Ljava/lang/Object;Landroid/view/InputChannel;)J");
 
     GET_METHOD_ID(gCallbacksClassInfo.virtualKeyDownFeedback, gCallbacksClassInfo.clazz,
             "virtualKeyDownFeedback", "()V");
@@ -2519,9 +1366,6 @@
     GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz,
             "checkInjectEventsPermission", "(II)Z");
 
-    GET_METHOD_ID(gCallbacksClassInfo.notifyAppSwitchComing, gCallbacksClassInfo.clazz,
-            "notifyAppSwitchComing", "()V");
-
     GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, gCallbacksClassInfo.clazz,
             "filterTouchEvents", "()Z");
 
@@ -2580,6 +1424,9 @@
     GET_FIELD_ID(gInputWindowClassInfo.inputChannel, gInputWindowClassInfo.clazz,
             "inputChannel", "Landroid/view/InputChannel;");
 
+    GET_FIELD_ID(gInputWindowClassInfo.name, gInputWindowClassInfo.clazz,
+            "name", "Ljava/lang/String;");
+
     GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, gInputWindowClassInfo.clazz,
             "layoutParamsFlags", "I");
 
@@ -2628,6 +1475,9 @@
     GET_FIELD_ID(gInputWindowClassInfo.visible, gInputWindowClassInfo.clazz,
             "visible", "Z");
 
+    GET_FIELD_ID(gInputWindowClassInfo.canReceiveKeys, gInputWindowClassInfo.clazz,
+            "canReceiveKeys", "Z");
+
     GET_FIELD_ID(gInputWindowClassInfo.hasFocus, gInputWindowClassInfo.clazz,
             "hasFocus", "Z");
 
@@ -2637,6 +1487,9 @@
     GET_FIELD_ID(gInputWindowClassInfo.paused, gInputWindowClassInfo.clazz,
             "paused", "Z");
 
+    GET_FIELD_ID(gInputWindowClassInfo.layer, gInputWindowClassInfo.clazz,
+            "layer", "I");
+
     GET_FIELD_ID(gInputWindowClassInfo.ownerPid, gInputWindowClassInfo.clazz,
             "ownerPid", "I");
 
diff --git a/services/jni/com_android_server_PowerManagerService.h b/services/jni/com_android_server_PowerManagerService.h
index 7c329b2..af10711 100644
--- a/services/jni/com_android_server_PowerManagerService.h
+++ b/services/jni/com_android_server_PowerManagerService.h
@@ -20,20 +20,10 @@
 #include "JNIHelp.h"
 #include "jni.h"
 
+#include <ui/PowerManager.h>
+
 namespace android {
 
-enum {
-    POWER_MANAGER_OTHER_EVENT = 0,
-    POWER_MANAGER_CHEEK_EVENT = 1,
-    POWER_MANAGER_TOUCH_EVENT = 2, // touch events are TOUCH for 300ms, and then either
-                                   // up events or LONG_TOUCH events.
-    POWER_MANAGER_LONG_TOUCH_EVENT = 3,
-    POWER_MANAGER_TOUCH_UP_EVENT = 4,
-    POWER_MANAGER_BUTTON_EVENT = 5, // Button and trackball events.
-
-    POWER_MANAGER_LAST_EVENT = POWER_MANAGER_BUTTON_EVENT, // Last valid event code.
-};
-
 extern bool android_server_PowerManagerService_isScreenOn();
 extern bool android_server_PowerManagerService_isScreenBright();
 extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType);
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 3025f77..e204e04 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -529,7 +529,7 @@
     LOGE_IF(size<0, "dropping %d events on the floor (%s)",
             count, strerror(-size));
 
-    return size < 0 ? size : NO_ERROR;
+    return size < 0 ? status_t(size) : status_t(NO_ERROR);
 }
 
 sp<SensorChannel> SensorService::SensorEventConnection::getSensorChannel() const
diff --git a/services/sensorservice/tests/sensorservicetest.cpp b/services/sensorservice/tests/sensorservicetest.cpp
index e464713..42bf983 100644
--- a/services/sensorservice/tests/sensorservicetest.cpp
+++ b/services/sensorservice/tests/sensorservicetest.cpp
@@ -18,11 +18,11 @@
 #include <gui/Sensor.h>
 #include <gui/SensorManager.h>
 #include <gui/SensorEventQueue.h>
-#include <utils/PollLoop.h>
+#include <utils/Looper.h>
 
 using namespace android;
 
-bool receiver(int fd, int events, void* data)
+int receiver(int fd, int events, void* data)
 {
     sp<SensorEventQueue> q((SensorEventQueue*)data);
     ssize_t n;
@@ -41,7 +41,7 @@
     if (n<0 && n != -EAGAIN) {
         printf("error reading events (%s)\n", strerror(-n));
     }
-    return true;
+    return 1;
 }
 
 
@@ -51,7 +51,7 @@
 
     Sensor const* const* list;
     ssize_t count = mgr.getSensorList(&list);
-    printf("numSensors=%d\n", count);
+    printf("numSensors=%d\n", int(count));
 
     sp<SensorEventQueue> q = mgr.createEventQueue();
     printf("queue=%p\n", q.get());
@@ -63,13 +63,16 @@
 
     q->setEventRate(accelerometer, ms2ns(10));
 
-    sp<PollLoop> loop = new PollLoop(false);
-    loop->setCallback(q->getFd(), POLLIN, receiver, q.get());
+    sp<Looper> loop = new Looper(false);
+    loop->addFd(q->getFd(), 0, ALOOPER_EVENT_INPUT, receiver, q.get());
 
     do {
         //printf("about to poll...\n");
-        int32_t ret = loop->pollOnce(-1, 0, 0);
+        int32_t ret = loop->pollOnce(-1);
         switch (ret) {
+            case ALOOPER_POLL_WAKE:
+                //("ALOOPER_POLL_WAKE\n");
+                break;
             case ALOOPER_POLL_CALLBACK:
                 //("ALOOPER_POLL_CALLBACK\n");
                 break;
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 3e23929..bd348bf 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -310,6 +310,10 @@
     return mNativeWindow->compositionComplete();
 }
 
+int DisplayHardware::getCurrentBufferIndex() const {
+    return mNativeWindow->getCurrentBufferIndex();
+}
+
 void DisplayHardware::flip(const Region& dirty) const
 {
     checkGLErrors();
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
index f2cfd2d..75b55df 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
@@ -89,6 +89,9 @@
         return Rect(mWidth, mHeight);
     }
 
+    // only for debugging
+    int getCurrentBufferIndex() const;
+
 private:
     void init(uint32_t displayIndex) __attribute__((noinline));
     void fini() __attribute__((noinline));
diff --git a/services/surfaceflinger/GLExtensions.cpp b/services/surfaceflinger/GLExtensions.cpp
index 7f4f9fc..850866a 100644
--- a/services/surfaceflinger/GLExtensions.cpp
+++ b/services/surfaceflinger/GLExtensions.cpp
@@ -86,7 +86,7 @@
         mHaveNpot = true;
     }
 
-    if (hasExtension("GL_OES_texture_external")) {
+    if (hasExtension("GL_OES_EGL_image_external")) {
         mHaveTextureExternal = true;
     } else if (strstr(mRenderer.string(), "Adreno")) {
         // hack for Adreno 200
diff --git a/services/surfaceflinger/LayerBlur.cpp b/services/surfaceflinger/LayerBlur.cpp
index 2ee21b9..4cfcfe3 100644
--- a/services/surfaceflinger/LayerBlur.cpp
+++ b/services/surfaceflinger/LayerBlur.cpp
@@ -146,7 +146,7 @@
     Region::const_iterator it = clip.begin();
     Region::const_iterator const end = clip.end();
     if (it != end) {
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
         if (GLExtensions::getInstance().haveTextureExternal()) {
             glDisable(GL_TEXTURE_EXTERNAL_OES);
         }
diff --git a/services/surfaceflinger/LayerDim.cpp b/services/surfaceflinger/LayerDim.cpp
index a1f339e..80cc52c 100644
--- a/services/surfaceflinger/LayerDim.cpp
+++ b/services/surfaceflinger/LayerDim.cpp
@@ -71,7 +71,7 @@
         glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
         glColor4f(0, 0, 0, alpha);
 
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
         if (GLExtensions::getInstance().haveTextureExternal()) {
             glDisable(GL_TEXTURE_EXTERNAL_OES);
         }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d820380..b45f6fe 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -38,6 +38,7 @@
 #include <utils/StopWatch.h>
 
 #include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicLog.h>
 #include <ui/PixelFormat.h>
 
 #include <pixelflinger/pixelflinger.h>
@@ -378,15 +379,24 @@
     const DisplayHardware& hw(graphicPlane(0).displayHardware());
     if (LIKELY(hw.canDraw() && !isFrozen())) {
         // repaint the framebuffer (if needed)
+
+        const int index = hw.getCurrentBufferIndex();
+        GraphicLog& logger(GraphicLog::getInstance());
+
+        logger.log(GraphicLog::SF_REPAINT, index);
         handleRepaint();
 
         // inform the h/w that we're done compositing
+        logger.log(GraphicLog::SF_COMPOSITION_COMPLETE, index);
         hw.compositionComplete();
 
+        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
         unlockClients();
@@ -993,7 +1003,7 @@
         glVertexPointer(2, GL_SHORT, 0, vertices);
         glTexCoordPointer(2, GL_SHORT, 0, tcoords);
         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
         if (GLExtensions::getInstance().haveTextureExternal()) {
             glDisable(GL_TEXTURE_EXTERNAL_OES);
         }
@@ -1560,8 +1570,7 @@
         int n;
         switch (code) {
             case 1000: // SHOW_CPU, NOT SUPPORTED ANYMORE
-                return NO_ERROR;
-            case 1001:  // SHOW_FPS, NOT SUPPORTED ANYMORE
+            case 1001: // SHOW_FPS, NOT SUPPORTED ANYMORE
                 return NO_ERROR;
             case 1002:  // SHOW_UPDATES
                 n = data.readInt32();
@@ -1582,6 +1591,11 @@
                 setTransactionFlags(eTransactionNeeded|eTraversalNeeded);
                 return NO_ERROR;
             }
+            case 1006:{ // enable/disable GraphicLog
+                int enabled = data.readInt32();
+                GraphicLog::getInstance().setEnabled(enabled);
+                return NO_ERROR;
+            }
             case 1007: // set mFreezeCount
                 mFreezeCount = data.readInt32();
                 mFreezeDisplayTime = 0;
diff --git a/services/surfaceflinger/TextureManager.cpp b/services/surfaceflinger/TextureManager.cpp
index 76f6159..c9a15f5 100644
--- a/services/surfaceflinger/TextureManager.cpp
+++ b/services/surfaceflinger/TextureManager.cpp
@@ -43,7 +43,7 @@
 }
 
 GLenum TextureManager::getTextureTarget(const Image* image) {
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
     switch (image->target) {
         case Texture::TEXTURE_EXTERNAL:
             return GL_TEXTURE_EXTERNAL_OES;
@@ -85,7 +85,7 @@
     pImage->height = 0;
 
     GLenum target = GL_TEXTURE_2D;
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
     if (GLExtensions::getInstance().haveTextureExternal()) {
         if (format && isYuvFormat(format)) {
             target = GL_TEXTURE_EXTERNAL_OES;
@@ -306,7 +306,7 @@
     if (target == GL_TEXTURE_2D) {
         glBindTexture(GL_TEXTURE_2D, texture.name);
         glEnable(GL_TEXTURE_2D);
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
         if (GLExtensions::getInstance().haveTextureExternal()) {
             glDisable(GL_TEXTURE_EXTERNAL_OES);
         }
@@ -329,7 +329,7 @@
 void TextureManager::deactivateTextures()
 {
     glDisable(GL_TEXTURE_2D);
-#if defined(GL_OES_texture_external)
+#if defined(GL_OES_EGL_image_external)
     if (GLExtensions::getInstance().haveTextureExternal()) {
         glDisable(GL_TEXTURE_EXTERNAL_OES);
     }
diff --git a/telephony/java/android/telephony/gsm/GsmCellLocation.java b/telephony/java/android/telephony/gsm/GsmCellLocation.java
index c4204fa..313bc82 100644
--- a/telephony/java/android/telephony/gsm/GsmCellLocation.java
+++ b/telephony/java/android/telephony/gsm/GsmCellLocation.java
@@ -60,8 +60,10 @@
     }
 
     /**
+     * On a UMTS network, returns the primary scrambling code of the serving
+     * cell.
+     *
      * @return primary scrambling code for UMTS, -1 if unknown or GSM
-     * @hide
      */
     public int getPsc() {
         return mPsc;
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index 4bf3282..83496dc 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.RegistrantList;
+import android.os.Registrant;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.util.Log;
@@ -75,6 +76,7 @@
     private static final int EVENT_SUBSCRIPTION_INFO_READY = 116;
     private static final int EVENT_SUPP_SERVICE_FAILED = 117;
     private static final int EVENT_SERVICE_STATE_CHANGED = 118;
+    private static final int EVENT_POST_DIAL_CHARACTER = 119;
 
     // Singleton instance
     private static final CallManager INSTANCE = new CallManager();
@@ -158,6 +160,9 @@
     protected final RegistrantList mServiceStateChangedRegistrants
     = new RegistrantList();
 
+    protected final RegistrantList mPostDialCharacterRegistrants
+    = new RegistrantList();
+
     private CallManager() {
         mPhones = new ArrayList<Phone>();
         mRingingCalls = new ArrayList<Call>();
@@ -192,10 +197,12 @@
         Phone.State s = Phone.State.IDLE;
 
         for (Phone phone : mPhones) {
-            if (phone.getState() == Phone.State.RINGING) {
-                return Phone.State.RINGING;
+            if (phone.getState() == Phone.State.ANSWERING) {
+                return Phone.State.ANSWERING;
+            } else if (phone.getState() == Phone.State.RINGING) {
+                s = Phone.State.RINGING;
             } else if (phone.getState() == Phone.State.OFFHOOK) {
-                s = Phone.State.OFFHOOK;
+                if (s == Phone.State.IDLE) s = Phone.State.OFFHOOK;
             }
         }
         return s;
@@ -285,6 +292,18 @@
     }
 
     /**
+     * @return the first answering call
+     */
+    public Call getFirstAnsweringCall() {
+        for (Phone phone : mPhones) {
+            if (phone.getState() == Phone.State.ANSWERING) {
+                return phone.getForegroundCall();
+            }
+        }
+        return null;
+    }
+
+    /**
      * unregister phone from CallManager
      * @param phone
      */
@@ -334,6 +353,7 @@
     }
 
     private void registerForPhoneStates(Phone phone) {
+        // for common events supported by all phones
         phone.registerForPreciseCallStateChanged(mHandler, EVENT_PRECISE_CALL_STATE_CHANGED, null);
         phone.registerForDisconnect(mHandler, EVENT_DISCONNECT, null);
         phone.registerForNewRingingConnection(mHandler, EVENT_NEW_RINGING_CONNECTION, null);
@@ -342,20 +362,31 @@
         phone.registerForRingbackTone(mHandler, EVENT_RINGBACK_TONE, null);
         phone.registerForInCallVoicePrivacyOn(mHandler, EVENT_IN_CALL_VOICE_PRIVACY_ON, null);
         phone.registerForInCallVoicePrivacyOff(mHandler, EVENT_IN_CALL_VOICE_PRIVACY_OFF, null);
-        phone.registerForCallWaiting(mHandler, EVENT_CALL_WAITING, null);
         phone.registerForDisplayInfo(mHandler, EVENT_DISPLAY_INFO, null);
         phone.registerForSignalInfo(mHandler, EVENT_SIGNAL_INFO, null);
-        phone.registerForCdmaOtaStatusChange(mHandler, EVENT_CDMA_OTA_STATUS_CHANGE, null);
         phone.registerForResendIncallMute(mHandler, EVENT_RESEND_INCALL_MUTE, null);
         phone.registerForMmiInitiate(mHandler, EVENT_MMI_INITIATE, null);
         phone.registerForMmiComplete(mHandler, EVENT_MMI_COMPLETE, null);
-        phone.registerForEcmTimerReset(mHandler, EVENT_ECM_TIMER_RESET, null);
-        phone.registerForSubscriptionInfoReady(mHandler, EVENT_SUBSCRIPTION_INFO_READY, null);
         phone.registerForSuppServiceFailed(mHandler, EVENT_SUPP_SERVICE_FAILED, null);
         phone.registerForServiceStateChanged(mHandler, EVENT_SERVICE_STATE_CHANGED, null);
+
+        // for events supported only by GSM and CDMA phone
+        if (phone.getPhoneType() == Phone.PHONE_TYPE_GSM ||
+                phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
+            phone.setOnPostDialCharacter(mHandler, EVENT_POST_DIAL_CHARACTER, null);
+        }
+
+        // for events supported only by CDMA phone
+        if (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA ){
+            phone.registerForCdmaOtaStatusChange(mHandler, EVENT_CDMA_OTA_STATUS_CHANGE, null);
+            phone.registerForSubscriptionInfoReady(mHandler, EVENT_SUBSCRIPTION_INFO_READY, null);
+            phone.registerForCallWaiting(mHandler, EVENT_CALL_WAITING, null);
+            phone.registerForEcmTimerReset(mHandler, EVENT_ECM_TIMER_RESET, null);
+        }
     }
 
     private void unregisterForPhoneStates(Phone phone) {
+        //  for common events supported by all phones
         phone.unregisterForPreciseCallStateChanged(mHandler);
         phone.unregisterForDisconnect(mHandler);
         phone.unregisterForNewRingingConnection(mHandler);
@@ -364,17 +395,27 @@
         phone.unregisterForRingbackTone(mHandler);
         phone.unregisterForInCallVoicePrivacyOn(mHandler);
         phone.unregisterForInCallVoicePrivacyOff(mHandler);
-        phone.unregisterForCallWaiting(mHandler);
         phone.unregisterForDisplayInfo(mHandler);
         phone.unregisterForSignalInfo(mHandler);
-        phone.unregisterForCdmaOtaStatusChange(mHandler);
         phone.unregisterForResendIncallMute(mHandler);
         phone.unregisterForMmiInitiate(mHandler);
         phone.unregisterForMmiComplete(mHandler);
-        phone.unregisterForEcmTimerReset(mHandler);
-        phone.unregisterForSubscriptionInfoReady(mHandler);
         phone.unregisterForSuppServiceFailed(mHandler);
         phone.unregisterForServiceStateChanged(mHandler);
+
+        // for events supported only by GSM and CDMA phone
+        if (phone.getPhoneType() == Phone.PHONE_TYPE_GSM ||
+                phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
+            phone.setOnPostDialCharacter(null, EVENT_POST_DIAL_CHARACTER, null);
+        }
+
+        // for events supported only by CDMA phone
+        if (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA ){
+            phone.unregisterForCdmaOtaStatusChange(mHandler);
+            phone.unregisterForSubscriptionInfoReady(mHandler);
+            phone.unregisterForCallWaiting(mHandler);
+            phone.unregisterForEcmTimerReset(mHandler);
+        }
     }
 
     /**
@@ -1132,6 +1173,46 @@
         mSubscriptionInfoReadyRegistrants.remove(h);
     }
 
+    /**
+     * Sets an event to be fired when the telephony system processes
+     * a post-dial character on an outgoing call.<p>
+     *
+     * Messages of type <code>what</code> will be sent to <code>h</code>.
+     * The <code>obj</code> field of these Message's will be instances of
+     * <code>AsyncResult</code>. <code>Message.obj.result</code> will be
+     * a Connection object.<p>
+     *
+     * Message.arg1 will be the post dial character being processed,
+     * or 0 ('\0') if end of string.<p>
+     *
+     * If Connection.getPostDialState() == WAIT,
+     * the application must call
+     * {@link com.android.internal.telephony.Connection#proceedAfterWaitChar()
+     * Connection.proceedAfterWaitChar()} or
+     * {@link com.android.internal.telephony.Connection#cancelPostDial()
+     * Connection.cancelPostDial()}
+     * for the telephony system to continue playing the post-dial
+     * DTMF sequence.<p>
+     *
+     * If Connection.getPostDialState() == WILD,
+     * the application must call
+     * {@link com.android.internal.telephony.Connection#proceedAfterWildChar
+     * Connection.proceedAfterWildChar()}
+     * or
+     * {@link com.android.internal.telephony.Connection#cancelPostDial()
+     * Connection.cancelPostDial()}
+     * for the telephony system to continue playing the
+     * post-dial DTMF sequence.<p>
+     *
+     */
+    public void registerForPostDialCharacter(Handler h, int what, Object obj){
+        mPostDialCharacterRegistrants.addUnique(h, what, obj);
+    }
+
+    public void unregisterForPostDialCharacter(Handler h){
+        mPostDialCharacterRegistrants.remove(h);
+    }
+
     /* APIs to access foregroudCalls, backgroudCalls, and ringingCalls
      * 1. APIs to access list of calls
      * 2. APIs to check if any active call, which has connection other than
@@ -1144,22 +1225,22 @@
     /**
      * @return list of all ringing calls
      */
-    public ArrayList<Call> getRingingCalls() {
-        return mRingingCalls;
+    public List<Call> getRingingCalls() {
+        return Collections.unmodifiableList(mRingingCalls);
     }
 
     /**
      * @return list of all foreground calls
      */
-    public ArrayList<Call> getForegroundCalls() {
-        return mForegroundCalls;
+    public List<Call> getForegroundCalls() {
+        return Collections.unmodifiableList(mForegroundCalls);
     }
 
     /**
      * @return list of all background calls
      */
-    public ArrayList<Call> getBackgroundCalls() {
-        return mBackgroundCalls;
+    public List<Call> getBackgroundCalls() {
+        return Collections.unmodifiableList(mBackgroundCalls);
     }
 
     /**
@@ -1269,7 +1350,7 @@
 
     /**
      * @return the connections of active foreground call
-     * return null if there is no active foreground call
+     * return empty list if there is no active foreground call
      */
     public List<Connection> getFgCallConnections() {
         Call fgCall = getActiveFgCall();
@@ -1408,6 +1489,18 @@
                     break;
                 case EVENT_SERVICE_STATE_CHANGED:
                     mServiceStateChangedRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+                    break;
+                case EVENT_POST_DIAL_CHARACTER:
+                    // we need send the character that is being processed in msg.arg1
+                    // so can't use notifyRegistrants()
+                    for(int i=0; i < mPostDialCharacterRegistrants.size(); i++) {
+                        Message notifyMsg;
+                        notifyMsg = ((Registrant)mPostDialCharacterRegistrants.get(i)).messageForRegistrant();
+                        notifyMsg.obj = msg.obj;
+                        notifyMsg.arg1 = msg.arg1;
+                        notifyMsg.sendToTarget();
+                    }
+                    break;
             }
         }
     };
diff --git a/telephony/java/com/android/internal/telephony/Connection.java b/telephony/java/com/android/internal/telephony/Connection.java
index e5456e4..72c50fc 100644
--- a/telephony/java/com/android/internal/telephony/Connection.java
+++ b/telephony/java/com/android/internal/telephony/Connection.java
@@ -39,7 +39,9 @@
         CONGESTION,                     /* outgoing call to congested network */
         MMI,                            /* not presently used; dial() returns null */
         INVALID_NUMBER,                 /* invalid dial string */
+        NUMBER_UNREACHABLE,             /* cannot reach the peer */
         INVALID_CREDENTIALS,            /* invalid credentials */
+        TIMED_OUT,                      /* client timed out */
         LOST_SIGNAL,
         LIMIT_EXCEEDED,                 /* eg GSM ACM limit exceeded */
         INCOMING_REJECTED,              /* an incoming call that was rejected */
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 521d90c..3030481 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -21,6 +21,7 @@
 import com.android.internal.util.HierarchicalState;
 import com.android.internal.util.HierarchicalStateMachine;
 
+import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.os.AsyncResult;
 import android.os.Message;
@@ -31,6 +32,7 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
+import java.util.HashMap;
 
 /**
  * {@hide}
@@ -262,6 +264,7 @@
     protected PhoneBase phone;
     protected int cid;
     protected LinkProperties mLinkProperties = new LinkProperties();
+    protected LinkCapabilities mCapabilities = new LinkCapabilities();
     protected long createTime;
     protected long lastFailTime;
     protected FailCause lastFailCause;
@@ -912,13 +915,25 @@
     }
 
     /**
-     * @return the connections LinkProperties
+     * Return the LinkProperties for the connection.
+     *
+     * @return a copy of the LinkProperties, is never null.
      */
     public LinkProperties getLinkProperties() {
         return new LinkProperties(mLinkProperties);
     }
 
     /**
+     * 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(mCapabilities);
+    }
+
+    /**
      * @return the current state as a string.
      */
     public String getStateAsString() {
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 765f64b..d753973 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.app.PendingIntent;
+import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -192,8 +193,11 @@
     /** indication of our availability (preconditions to trysetupData are met) **/
     protected boolean mAvailability = false;
 
-    /** all our link properties (dns, gateway, ip, etc) */
-    protected LinkProperties mLinkProperties;
+    /** The link properties (dns, gateway, ip, etc) */
+    protected LinkProperties mLinkProperties = new LinkProperties();
+
+    /** The link capabilities */
+    protected LinkCapabilities mLinkCapabilities = new LinkCapabilities();
 
     /**
      * Default constructor
@@ -425,10 +429,40 @@
         if (isApnIdEnabled(id)) {
             return new LinkProperties(mLinkProperties);
         } else {
-            return null;
+            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++) {
@@ -672,8 +706,4 @@
             }
         }
     }
-
-    protected LinkProperties getLinkProperties(DataConnection connection) {
-        return connection.getLinkProperties();
-    }
 }
diff --git a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index bf3c4d1..6a163dd 100644
--- a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -109,8 +110,10 @@
         // 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(
@@ -119,6 +122,7 @@
                     sender.getActiveApn(),
                     apnType,
                     linkProperties,
+                    linkCapabilities,
                     ((telephony!=null) ? telephony.getNetworkType() :
                     TelephonyManager.NETWORK_TYPE_UNKNOWN));
         } catch (RemoteException ex) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index eb7e566..6407a4e 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -18,6 +18,7 @@
 
 import android.content.Intent;
 import android.net.LinkProperties;
+import android.net.LinkCapabilities;
 import android.os.Bundle;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -34,7 +35,7 @@
     void notifyDataActivity(int state);
     void notifyDataConnection(int state, boolean isDataConnectivityPossible,
             String reason, String apn, String apnType, in LinkProperties linkProperties,
-            int networkType);
+            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/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index fffe057..e5736ca 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.content.Context;
+import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.os.Handler;
 import android.os.Message;
@@ -53,10 +54,12 @@
      * <li>OFFHOOK = The phone is off hook. At least one call
      * exists that is dialing, active or holding and no calls are
      * ringing or waiting.</li>
+     * <li>ANSWERING = The incoming call is picked up but the
+     *  call is not established yet.</li>
      * </ul>
      */
     enum State {
-        IDLE, RINGING, OFFHOOK;
+        IDLE, RINGING, OFFHOOK, ANSWERING;
     };
 
     /**
@@ -100,6 +103,7 @@
     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 NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
@@ -324,6 +328,11 @@
     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).
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index 78ce473..5412768 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.SharedPreferences;
+import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.net.wifi.WifiManager;
 import android.os.AsyncResult;
@@ -942,6 +943,10 @@
         return mDataConnection.getLinkProperties(apnType);
     }
 
+    public LinkCapabilities getLinkCapabilities(String apnType) {
+        return mDataConnection.getLinkCapabilities(apnType);
+    }
+
     public String getActiveApn() {
         return mDataConnection.getActiveApnString();
     }
diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java
index b6e4cda..9e0c087 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -20,6 +20,7 @@
 import android.app.ActivityManagerNative;
 import android.content.Context;
 import android.content.Intent;
+import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.os.Handler;
 import android.os.Message;
@@ -212,6 +213,10 @@
         return mActivePhone.getLinkProperties(apnType);
     }
 
+    public LinkCapabilities getLinkCapabilities(String apnType) {
+        return mActivePhone.getLinkCapabilities(apnType);
+    }
+
     public String getActiveApn() {
         return mActivePhone.getActiveApn();
     }
diff --git a/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java b/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
index 30d06d8..4c4e718 100644
--- a/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
+++ b/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -111,8 +112,10 @@
         // 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(
@@ -121,6 +124,7 @@
                     sender.getActiveApn(),
                     apnType,
                     linkProperties,
+                    linkCapabilities,
                     ((telephony!=null) ? telephony.getNetworkType() :
                     TelephonyManager.NETWORK_TYPE_UNKNOWN));
         } catch (RemoteException ex) {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index 5918245..e499e95 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -732,7 +732,9 @@
         }
 
         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);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index f6887eb..66da6e8 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -1099,7 +1099,9 @@
         }
 
         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) {
diff --git a/telephony/java/com/android/internal/telephony/sip/SipCallBase.java b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java
index e7eda4f..7e407b2 100644
--- a/telephony/java/com/android/internal/telephony/sip/SipCallBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java
@@ -26,7 +26,6 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
-import javax.sip.SipException;
 
 abstract class SipCallBase extends Call {
     private static final int MAX_CONNECTIONS_PER_CALL = 5;
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index f30e11e..4af35a6 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -24,6 +24,7 @@
 import android.net.rtp.AudioStream;
 import android.net.sip.SipAudioCall;
 import android.net.sip.SipErrorCode;
+import android.net.sip.SipException;
 import android.net.sip.SipManager;
 import android.net.sip.SipProfile;
 import android.net.sip.SipSessionState;
@@ -66,14 +67,13 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import javax.sip.SipException;
-
 /**
  * {@hide}
  */
 public class SipPhone extends SipPhoneBase {
     private static final String LOG_TAG = "SipPhone";
     private static final boolean LOCAL_DEBUG = true;
+    private static final int SESSION_TIMEOUT = 8; // in seconds
 
     // A call that is ringing or (call) waiting
     private SipCall ringingCall = new SipCall();
@@ -166,6 +166,7 @@
             } else {
                 throw new CallStateException("phone not ringing");
             }
+            updatePhoneState();
         }
     }
 
@@ -396,25 +397,45 @@
             }
         }
 
-        private CallerInfo getCallerInfo(String number) {
+        private CallerInfo createCallerInfo(String number, SipProfile callee) {
+            SipProfile p = callee;
+            String name = p.getDisplayName();
+            if (TextUtils.isEmpty(name)) name = p.getUserName();
+            CallerInfo info = new CallerInfo();
+            info.name = name;
+            info.phoneNumber = number;
+            Log.v(LOG_TAG, "create caller info from scratch:");
+            Log.v(LOG_TAG, "     name: " + info.name);
+            Log.v(LOG_TAG, "     numb: " + info.phoneNumber);
+            return info;
+        }
+
+        // from contacts
+        private CallerInfo findCallerInfo(String number) {
             CallerInfo info = CallerInfo.getCallerInfo(mContext, number);
             if ((info == null) || (info.name == null)) return null;
-            Log.v(LOG_TAG, "++******++ got info from contact:");
+            Log.v(LOG_TAG, "got caller info from contact:");
             Log.v(LOG_TAG, "     name: " + info.name);
             Log.v(LOG_TAG, "     numb: " + info.phoneNumber);
             Log.v(LOG_TAG, "     pres: " + info.numberPresentation);
             return info;
         }
 
-        Connection dial(String calleeSipUri) throws SipException {
-            CallerInfo info = getCallerInfo(calleeSipUri);
+        private CallerInfo getCallerInfo(String number, SipProfile callee) {
+            CallerInfo info = findCallerInfo(number);
+            if (info == null) info = createCallerInfo(number, callee);
+            return info;
+        }
+
+        Connection dial(String originalNumber) throws SipException {
+            String calleeSipUri = originalNumber;
             if (!calleeSipUri.contains("@")) {
                 calleeSipUri += "@" + getSipDomain(mProfile);
-                if (info != null) info.phoneNumber = calleeSipUri;
             }
             try {
                 SipProfile callee =
                         new SipProfile.Builder(calleeSipUri).build();
+                CallerInfo info = getCallerInfo(originalNumber, callee);
                 SipConnection c = new SipConnection(this, callee, info);
                 connections.add(c);
                 c.dial();
@@ -446,9 +467,9 @@
 
         void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
             SipProfile callee = sipAudioCall.getPeerProfile();
-            CallerInfo info = getCallerInfo(getUriString(callee));
-            if (info == null) info = getCallerInfo(callee.getUserName());
-            if (info == null) info = getCallerInfo(callee.getDisplayName());
+            CallerInfo info = findCallerInfo(getUriString(callee));
+            if (info == null) info = findCallerInfo(callee.getUserName());
+            if (info == null) info = findCallerInfo(callee.getDisplayName());
             SipConnection c = new SipConnection(this, callee, info);
             connections.add(c);
 
@@ -610,6 +631,7 @@
                 }
                 synchronized (SipPhone.class) {
                     setState(Call.State.DISCONNECTED);
+                    mSipAudioCall.close();
                     mOwner.onConnectionEnded(SipConnection.this);
                     Log.v(LOG_TAG, "-------- connection ended: "
                             + mPeer.getUriString() + ": "
@@ -627,6 +649,7 @@
                     if (newState == Call.State.INCOMING) {
                         setState(mOwner.getState()); // INCOMING or WAITING
                     } else {
+                        if (newState == Call.State.ACTIVE) call.startAudio();
                         setState(newState);
                     }
                     mOwner.onConnectionStateChanged(SipConnection.this);
@@ -639,9 +662,9 @@
             @Override
             protected void onError(DisconnectCause cause) {
                 Log.w(LOG_TAG, "SIP error: " + cause);
-                if (mSipAudioCall.isInCall()) {
-                    // Don't end the call when in call.
-                    // TODO: how to deliver the error to PhoneApp
+                if (mSipAudioCall.isInCall()
+                        && (cause != DisconnectCause.LOST_SIGNAL)) {
+                    // Don't end the call when in a call.
                     return;
                 }
 
@@ -654,20 +677,9 @@
             super(getUriString(callee));
             mOwner = owner;
             mPeer = callee;
-            if (info == null) info = createCallerInfo();
             setUserData(info);
         }
 
-        private CallerInfo createCallerInfo() {
-            SipProfile p = mPeer;
-            String name = p.getDisplayName();
-            if (TextUtils.isEmpty(name)) name = p.getUserName();
-            CallerInfo info = new CallerInfo();
-            info.name = name;
-            info.phoneNumber = getUriString(p);
-            return info;
-        }
-
         void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
             setState(newState);
             mSipAudioCall = sipAudioCall;
@@ -677,7 +689,7 @@
 
         void acceptCall() throws CallStateException {
             try {
-                mSipAudioCall.answerCall();
+                mSipAudioCall.answerCall(SESSION_TIMEOUT);
             } catch (SipException e) {
                 throw new CallStateException("acceptCall(): " + e);
             }
@@ -695,7 +707,7 @@
         void dial() throws SipException {
             setState(Call.State.DIALING);
             mSipAudioCall = mSipManager.makeAudioCall(mContext, mProfile,
-                    mPeer, null);
+                    mPeer, null, SESSION_TIMEOUT);
             mSipAudioCall.setRingbackToneEnabled(false);
             mSipAudioCall.setListener(mAdapter);
         }
@@ -703,7 +715,7 @@
         void hold() throws CallStateException {
             setState(Call.State.HOLDING);
             try {
-                mSipAudioCall.holdCall();
+                mSipAudioCall.holdCall(SESSION_TIMEOUT);
             } catch (SipException e) {
                 throw new CallStateException("hold(): " + e);
             }
@@ -713,7 +725,7 @@
             mSipAudioCall.setAudioGroup(audioGroup);
             setState(Call.State.ACTIVE);
             try {
-                mSipAudioCall.continueCall();
+                mSipAudioCall.continueCall(SESSION_TIMEOUT);
             } catch (SipException e) {
                 throw new CallStateException("unhold(): " + e);
             }
@@ -757,7 +769,7 @@
                 Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": "
                         + ": on phone " + getPhone().getPhoneName());
                 try {
-                    mSipAudioCall.endCall();
+                    if (mSipAudioCall != null) mSipAudioCall.endCall();
                     setState(Call.State.DISCONNECTING);
                     setDisconnectCause(DisconnectCause.LOCAL);
                 } catch (SipException e) {
@@ -819,14 +831,21 @@
         }
 
         @Override
-        public void onError(SipAudioCall call, String errorCode,
+        public void onError(SipAudioCall call, SipErrorCode errorCode,
                 String errorMessage) {
-            switch (Enum.valueOf(SipErrorCode.class, errorCode)) {
+            switch (errorCode) {
+                case PEER_NOT_REACHABLE:
+                    onError(Connection.DisconnectCause.NUMBER_UNREACHABLE);
+                    break;
                 case INVALID_REMOTE_URI:
                     onError(Connection.DisconnectCause.INVALID_NUMBER);
                     break;
                 case TIME_OUT:
-                    onError(Connection.DisconnectCause.CONGESTION);
+                case TRANSACTION_TERMINTED:
+                    onError(Connection.DisconnectCause.TIMED_OUT);
+                    break;
+                case DATA_CONNECTION_LOST:
+                    onError(Connection.DisconnectCause.LOST_SIGNAL);
                     break;
                 case INVALID_CREDENTIALS:
                     onError(Connection.DisconnectCause.INVALID_CREDENTIALS);
@@ -835,6 +854,7 @@
                 case SERVER_ERROR:
                 case CLIENT_ERROR:
                 default:
+                    Log.w(LOG_TAG, "error: " + errorCode + ": " + errorMessage);
                     onError(Connection.DisconnectCause.ERROR_UNSPECIFIED);
             }
         }
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
index 5f26af4..fec83d6 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -549,6 +549,8 @@
 
         if (getRingingCall().isRinging()) {
             state = State.RINGING;
+        } else if (getForegroundCall().isRinging()) {
+            state = State.ANSWERING;
         } else if (getForegroundCall().isIdle()
                 && getBackgroundCall().isIdle()) {
             state = State.IDLE;
diff --git a/tests/CoreTests/android/core/SSLPerformanceTest.java b/tests/CoreTests/android/core/SSLPerformanceTest.java
index fd87e89..5b5be0a 100644
--- a/tests/CoreTests/android/core/SSLPerformanceTest.java
+++ b/tests/CoreTests/android/core/SSLPerformanceTest.java
@@ -211,17 +211,17 @@
         deleteDirectory();
 
         OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
-        sslContext.engineInit(null, null, null,
-                FileClientSessionCache.usingDirectory(getCacheDirectory()),
-                null);
+        sslContext.engineInit(null, null, null);
+        sslContext.engineGetClientSessionContext().setPersistentCache(
+                FileClientSessionCache.usingDirectory(getCacheDirectory()));
 
         // Make sure www.google.com is in the cache.
         getVerisignDotCom(sslContext);
 
         // Re-initialize so we hit the file cache.
-        sslContext.engineInit(null, null, null,
-                FileClientSessionCache.usingDirectory(getCacheDirectory()),
-                null);
+        sslContext.engineInit(null, null, null);
+        sslContext.engineGetClientSessionContext().setPersistentCache(
+                FileClientSessionCache.usingDirectory(getCacheDirectory()));
 
         Stopwatch stopwatch = new Stopwatch();
 
diff --git a/tests/CoreTests/android/core/SSLSocketTest.java b/tests/CoreTests/android/core/SSLSocketTest.java
index 021df80..03905e1 100644
--- a/tests/CoreTests/android/core/SSLSocketTest.java
+++ b/tests/CoreTests/android/core/SSLSocketTest.java
@@ -911,7 +911,8 @@
 
         // Cache size = 2.
         FakeClientSessionCache fakeCache = new FakeClientSessionCache();
-        context.engineInit(null, null, null, fakeCache, null);
+        context.engineInit(null, null, null);
+        context.engineGetClientSessionContext().setPersistentCache(fakeCache);
         SSLSocketFactory socketFactory = context.engineGetSocketFactory();
         context.engineGetClientSessionContext().setSessionCacheSize(2);
         makeRequests(socketFactory);
@@ -933,7 +934,8 @@
 
         // Cache size = 3.
         fakeCache = new FakeClientSessionCache();
-        context.engineInit(null, null, null, fakeCache, null);
+        context.engineInit(null, null, null);
+        context.engineGetClientSessionContext().setPersistentCache(fakeCache);
         socketFactory = context.engineGetSocketFactory();
         context.engineGetClientSessionContext().setSessionCacheSize(3);
         makeRequests(socketFactory);
@@ -952,7 +954,8 @@
 
         // Cache size = 4.
         fakeCache = new FakeClientSessionCache();
-        context.engineInit(null, null, null, fakeCache, null);
+        context.engineInit(null, null, null);
+        context.engineGetClientSessionContext().setPersistentCache(fakeCache);
         socketFactory = context.engineGetSocketFactory();
         context.engineGetClientSessionContext().setSessionCacheSize(4);
         makeRequests(socketFactory);
@@ -1010,7 +1013,8 @@
         try {
             ClientSessionCacheProxy cacheProxy
                     = new ClientSessionCacheProxy(fileCache);
-            context.engineInit(null, null, null, cacheProxy, null);
+            context.engineInit(null, null, null);
+            context.engineGetClientSessionContext().setPersistentCache(cacheProxy);
             SSLSocketFactory socketFactory = context.engineGetSocketFactory();
             context.engineGetClientSessionContext().setSessionCacheSize(1);
             makeRequests(socketFactory);
@@ -1033,7 +1037,8 @@
             // Try again now that file-based cache is populated.
             fileCache = FileClientSessionCache.usingDirectory(cacheDir);
             cacheProxy = new ClientSessionCacheProxy(fileCache);
-            context.engineInit(null, null, null, cacheProxy, null);
+            context.engineInit(null, null, null);
+            context.engineGetClientSessionContext().setPersistentCache(cacheProxy);
             socketFactory = context.engineGetSocketFactory();
             context.engineGetClientSessionContext().setSessionCacheSize(1);
             makeRequests(socketFactory);
diff --git a/tests/DumpRenderTree2/AndroidManifest.xml b/tests/DumpRenderTree2/AndroidManifest.xml
index b4dc190..ea6571e 100644
--- a/tests/DumpRenderTree2/AndroidManifest.xml
+++ b/tests/DumpRenderTree2/AndroidManifest.xml
@@ -23,7 +23,7 @@
                   android:configChanges="orientation">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.TEST" />
             </intent-filter>
         </activity>
 
@@ -40,6 +40,7 @@
         </activity>
 
         <activity android:name=".LayoutTestsExecutor"
+                  android:theme="@style/WhiteBackground"
                   android:label="Layout tests' executor"
                   android:process=":executor">
         </activity>
@@ -56,4 +57,4 @@
     <uses-permission android:name="android.permission.WRITE_SDCARD" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/DumpRenderTree2/assets/run_apache2.py b/tests/DumpRenderTree2/assets/run_apache2.py
index 5d1e66c..c799b5c 100755
--- a/tests/DumpRenderTree2/assets/run_apache2.py
+++ b/tests/DumpRenderTree2/assets/run_apache2.py
@@ -26,12 +26,14 @@
 import os
 import subprocess
 import logging
+import optparse
+import time
 
-def main():
-  if len(sys.argv) < 2:
+def main(options, args):
+  if len(args) < 1:
     run_cmd = ""
   else:
-    run_cmd = sys.argv[1]
+    run_cmd = args[0]
 
   # Setup logging class
   logging.basicConfig(level=logging.INFO, format='%(message)s')
@@ -53,9 +55,13 @@
   android_tree_root = os.path.join(script_location, parent, parent, parent, parent, parent)
   android_tree_root = os.path.normpath(android_tree_root)
 
-  # Paths relative to 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")
-  layout_tests_path = os.path.join(webkit_path, "LayoutTests")
+  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}
@@ -66,9 +72,13 @@
   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_cmd = "apache2 -k " + run_cmd
+  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.
@@ -99,7 +109,20 @@
 
   # Try to execute the commands
   logging.info("Will " + run_cmd + " apache2 server.")
-  cmd = export_envvars_cmd + " && " + apache2_restart_cmd + directives + conf_file_cmd
+  cmd_template = export_envvars_cmd + " && " + apache2_restart_template + directives + conf_file_cmd
+
+  # 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 result in second apache2 instance.
+  if (run_cmd == 'restart'):
+    logging.info("First will stop...")
+    execute_cmd(cmd_template % ('stop'))
+    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(cmd_template % (run_cmd))
+
+def execute_cmd(cmd):
   p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   (out, err) = p.communicate()
 
@@ -121,4 +144,8 @@
     logging.info("OK")
 
 if __name__ == "__main__":
-  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
index b13d8c9..fd76e4a9 100755
--- a/tests/DumpRenderTree2/assets/run_layout_tests.py
+++ b/tests/DumpRenderTree2/assets/run_layout_tests.py
@@ -3,34 +3,46 @@
 """Run layout tests on the device.
 
   It runs the specified tests on the device, downloads the summaries to the temporary directory
-  and opens html details in the default browser.
+  and optionally shows the detailed results the host's default browser.
 
   Usage:
-    run_layout_tests.py PATH
+    run_layout_tests.py --show-results-in-browser test-relative-path
 """
 
-import sys
-import os
-import subprocess
 import logging
-import webbrowser
+import optparse
+import os
+import sys
+import subprocess
 import tempfile
+import webbrowser
 
 #TODO: These should not be hardcoded
-RESULTS_ABSOLUTE_PATH = "/sdcard/android/LayoutTests-results/"
+RESULTS_ABSOLUTE_PATH = "/sdcard/layout-test-results/"
 DETAILS_HTML = "details.html"
 SUMMARY_TXT = "summary.txt"
 
-def main():
-  if len(sys.argv) > 1:
-    path = sys.argv[1]
+def main(options, args):
+  if args:
+    path = " ".join(args);
   else:
-    path = ""
+    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
   cmd = "adb shell am instrument "
   cmd += "-e class com.android.dumprendertree2.scriptsupport.Starter#startLayoutTests "
@@ -44,12 +56,14 @@
 
   # Download the txt summary to tmp folder
   summary_txt_tmp_path = os.path.join(tmpdir, SUMMARY_TXT)
-  cmd = "adb pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path
+  cmd = "rm -f " + summary_txt_tmp_path + ";"
+  cmd += "adb pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path
   subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
 
   # Download the html summary to tmp folder
   details_html_tmp_path = os.path.join(tmpdir, DETAILS_HTML)
-  cmd = "adb pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path
+  cmd = "rm -f " + details_html_tmp_path + ";"
+  cmd += "adb pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path
   subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
 
   # Print summary to console
@@ -59,7 +73,14 @@
   logging.info("")
 
   # Open the browser with summary
-  webbrowser.open(details_html_tmp_path)
+  if options.show_results_in_browser != "false":
+    webbrowser.open(details_html_tmp_path)
 
 if __name__ == "__main__":
-  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")
+  options, args = option_parser.parse_args();
+  main(options, args);
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
index 7cbb397..96c1e5e 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java
@@ -18,8 +18,16 @@
 
 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.
@@ -46,12 +54,14 @@
         public abstract AbstractResult createResult(Bundle bundle);
     }
 
-    public enum ResultCode {
-        PASS("Passed"),
-        FAIL_RESULT_DIFFERS("Result differs"),
-        FAIL_NO_EXPECTED_RESULT("No expected result"),
-        FAIL_TIMED_OUT("Timed out"),
-        FAIL_CRASHED("Crashed");
+    /**
+     * 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;
 
@@ -79,6 +89,58 @@
         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.
@@ -123,14 +185,48 @@
     public abstract String getActualTextResult();
 
     /**
-     * Returns the code of this result.
+     * Returns the status code representing the result of comparing actual and expected results.
      *
      * @return
-     *      the code of this result
+     *      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
@@ -150,4 +246,4 @@
     public abstract String getDiffAsHtml();
 
     public abstract Bundle getBundle();
-}
\ 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
index 31da776..4831168 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/CrashedDummyResult.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/CrashedDummyResult.java
@@ -61,7 +61,23 @@
 
     @Override
     public ResultCode getResultCode() {
-        return ResultCode.FAIL_CRASHED;
+        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
@@ -106,4 +122,4 @@
     public void setExpectedTextResultPath(String relativePath) {
         /** TODO */
     }
-}
\ No newline at end of file
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java
index 4ab76e3..9bbf64a 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java
@@ -56,12 +56,7 @@
     private final Set<String> mFailList = new HashSet<String>();
     private final Set<String> mSlowList = new HashSet<String>();
 
-    private final String mRootDirPath;
-
-    public FileFilter(String rootDirPath) {
-        /** It may or may not contain a trailing slash */
-        this.mRootDirPath = rootDirPath;
-
+    public FileFilter() {
         loadTestExpectations();
     }
 
@@ -287,37 +282,6 @@
     }
 
     /**
-     * Return the path to the file relative to the tests root dir
-     *
-     * @param filePath
-     * @return
-     *      the path relative to the tests root dir
-     */
-    public String getRelativePath(String filePath) {
-        File rootDir = new File(mRootDirPath);
-        return filePath.replaceFirst(rootDir.getPath() + File.separator, "");
-    }
-
-    /**
-     * Return the path to the file relative to the tests root dir
-     *
-     * @param filePath
-     * @return
-     *      the path relative to the tests root dir
-     */
-    public String getRelativePath(File file) {
-        return getRelativePath(file.getAbsolutePath());
-    }
-
-    public File getAbsoluteFile(String relativePath) {
-        return new File(mRootDirPath, relativePath);
-    }
-
-    public String getAboslutePath(String relativePath) {
-        return getAbsoluteFile(relativePath).getAbsolutePath();
-    }
-
-    /**
      * If the path contains extension (e.g .foo at the end of the file) then it changes
      * this (.foo) into newEnding (so it has to contain the dot if we want to preserve it).
      *
@@ -336,4 +300,4 @@
 
         return relativePath.substring(0, dotPos) + newEnding;
     }
-}
\ No newline at end of file
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
index 4202668..4438811 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
@@ -20,19 +20,35 @@
 
 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.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 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.net.URLConnection;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -45,6 +61,29 @@
     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 {
@@ -98,32 +137,34 @@
             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 {
-            InputStream inputStream = null;
-            ByteArrayOutputStream outputStream = null;
-            try {
-                URLConnection urlConnection = url.openConnection();
-                inputStream = urlConnection.getInputStream();
-                outputStream = new ByteArrayOutputStream();
-
-                byte[] buffer = new byte[4096];
-                int length;
-                while ((length = inputStream.read(buffer)) > 0) {
-                    outputStream.write(buffer, 0, length);
+            /**
+             * 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);
                 }
-
-                bytes = outputStream.toByteArray();
-            } finally {
-                if (inputStream != null) {
-                    inputStream.close();
-                }
-                if (outputStream != null) {
-                    outputStream.close();
-                }
-            }
-        } catch (FileNotFoundException e) {
-            Log.w(LOG_TAG, "readDataFromUrl(): File not found: " + e.getMessage());
+            } while (timedOut);
         } catch (IOException e) {
             Log.e(LOG_TAG, "url=" + url, e);
         }
@@ -135,8 +176,6 @@
             boolean mode) {
         String modeString = (mode ? "folders" : "files");
 
-        List<String> results = new LinkedList<String>();
-
         URL url = null;
         try {
             url = new URL(SCRIPT_URL +
@@ -146,34 +185,67 @@
         } catch (MalformedURLException e) {
             Log.e(LOG_TAG, "path=" + dirRelativePath + " recurse=" + recurse + " mode=" +
                     modeString, e);
-            return results;
+            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 {
-            InputStream inputStream = null;
-            BufferedReader bufferedReader = null;
-            try {
-                URLConnection urlConnection = url.openConnection();
-                inputStream = urlConnection.getInputStream();
-                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
-
-                String relativePath;
-                while ((relativePath = bufferedReader.readLine()) != null) {
-                    results.add(relativePath);
-                }
-            } finally {
-                if (inputStream != null) {
-                    inputStream.close();
-                }
-                if (bufferedReader != null) {
-                    bufferedReader.close();
-                }
-            }
+            return getHttpClient().execute(httpRequest, handler);
         } catch (IOException e) {
-            Log.e(LOG_TAG, "path=" + dirRelativePath + " recurse=" + recurse + " mode=" +
-                    modeString, e);
+            Log.e(LOG_TAG, "url=" + url, e);
         }
 
-        return results;
+        return new LinkedList<String>();
+    }
+
+    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);
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java
index 7d57eb7..e608e2d 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java
@@ -35,12 +35,9 @@
         mLayoutTestsExecutor = layoutTestsExecutor;
     }
 
-    public void waitUntilDone() {
-        mLayoutTestsExecutor.waitUntilDone();
-    }
-
-    public void notifyDone() {
-        mLayoutTestsExecutor.notifyDone();
+    public void clearAllDatabases() {
+        Log.i(LOG_TAG, "clearAllDatabases() called");
+        WebStorage.getInstance().deleteAllData();
     }
 
     public void dumpAsText() {
@@ -55,19 +52,27 @@
         mLayoutTestsExecutor.dumpChildFramesAsText();
     }
 
-    public void clearAllDatabases() {
-        Log.i(LOG_TAG, "clearAllDatabases() called");
-        WebStorage.getInstance().deleteAllData();
+    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 dumpDatabaseCallbacks() {
-        mLayoutTestsExecutor.dumpDatabaseCallbacks();
-    }
-
     public void setDatabaseQuota(long quota) {
         /** TODO: Reset this before every test! */
         Log.i(LOG_TAG, "setDatabaseQuota() called with: " + quota);
@@ -79,17 +84,6 @@
         mLayoutTestsExecutor.setGeolocationPermission(allow);
     }
 
-    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 setMockGeolocationError(int code, String message) {
-        Log.i(LOG_TAG, "setMockGeolocationError(): " + "code=" + code + " message=" + message);
-        MockGeolocation.getInstance().setError(code, message);
-    }
-
     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
@@ -100,4 +94,23 @@
         mLayoutTestsExecutor.setMockDeviceOrientation(
                 canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma);
     }
-}
\ No newline at end of file
+
+    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
index 0bd2302..b9fc274 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
@@ -35,6 +35,7 @@
 import android.util.Log;
 import android.view.Window;
 import android.webkit.ConsoleMessage;
+import android.webkit.HttpAuthHandler;
 import android.webkit.JsPromptResult;
 import android.webkit.JsResult;
 import android.webkit.WebChromeClient;
@@ -73,12 +74,6 @@
         }
     }
 
-    /** TODO: make it a setting */
-    static final String TESTS_ROOT_DIR_PATH =
-            Environment.getExternalStorageDirectory() +
-            File.separator + "android" +
-            File.separator + "LayoutTests";
-
     private static final String LOG_TAG = "LayoutTestExecutor";
 
     public static final String EXTRA_TESTS_LIST = "TestsList";
@@ -172,6 +167,19 @@
                 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();
+         }
     };
 
     private WebChromeClient mWebChromeClient = new WebChromeClient() {
@@ -418,6 +426,7 @@
         assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
 
         Log.i(LOG_TAG, "onTestFinished(): " + mCurrentTestRelativePath);
+        mResultHandler.removeMessages(MSG_TEST_TIMED_OUT);
         obtainActualResultsFromWebView();
     }
 
@@ -433,6 +442,9 @@
 
         mCurrentState = CurrentState.OBTAINING_RESULT;
 
+        if (mCurrentTestTimedOut) {
+            mCurrentResult.setDidTimeOut();
+        }
         mCurrentResult.obtainActualResults(mCurrentWebView,
                 mResultHandler.obtainMessage(MSG_ACTUAL_RESULT_OBTAINED));
     }
@@ -444,7 +456,6 @@
         Log.i(LOG_TAG, "onActualResultsObtained(): " + mCurrentTestRelativePath);
         mCurrentState = CurrentState.IDLE;
 
-        mResultHandler.removeMessages(MSG_TEST_TIMED_OUT);
         reportResultToService();
         mCurrentTestIndex++;
         updateProgressBar();
@@ -462,9 +473,6 @@
 
             Bundle bundle = mCurrentResult.getBundle();
             bundle.putInt("testIndex", mCurrentTestIndex);
-            if (mCurrentTestTimedOut) {
-                bundle.putString("resultCode", AbstractResult.ResultCode.FAIL_TIMED_OUT.name());
-            }
             if (!mTestsList.isEmpty()) {
                 bundle.putString("nextTest", mTestsList.get(0));
             }
@@ -513,6 +521,12 @@
     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
@@ -520,24 +534,12 @@
             assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
 
             switch (msg.what) {
-                case MSG_WAIT_UNTIL_DONE:
-                    mCurrentState = CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
-                    break;
-
-                case MSG_NOTIFY_DONE:
-                    if (mCurrentState == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST) {
-                        onTestFinished();
-                    }
-                    break;
-
                 case MSG_DUMP_AS_TEXT:
                     if (mCurrentResult == null) {
                         mCurrentResult = new TextResult(mCurrentTestRelativePath);
                     }
-
                     assert mCurrentResult instanceof TextResult
                             : "mCurrentResult instanceof" + mCurrentResult.getClass().getName();
-
                     break;
 
                 case MSG_DUMP_CHILD_FRAMES_AS_TEXT:
@@ -552,14 +554,36 @@
                     ((TextResult)mCurrentResult).setDumpChildFramesAsText(true);
                     break;
 
-                case MSG_SET_CAN_OPEN_WINDOWS:
-                    mCanOpenWindows = 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;
@@ -576,6 +600,14 @@
                     }
                     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;
@@ -590,16 +622,6 @@
         mPendingGeolocationPermissionCallbacks = null;
     }
 
-    public void waitUntilDone() {
-        Log.i(LOG_TAG, mCurrentTestRelativePath + ": waitUntilDone() called");
-        mLayoutTestControllerHandler.sendEmptyMessage(MSG_WAIT_UNTIL_DONE);
-    }
-
-    public void notifyDone() {
-        Log.i(LOG_TAG, mCurrentTestRelativePath + ": notifyDone() called");
-        mLayoutTestControllerHandler.sendEmptyMessage(MSG_NOTIFY_DONE);
-    }
-
     public void dumpAsText(boolean enablePixelTest) {
         Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpAsText(" + enablePixelTest + ") called");
         /** TODO: Implement */
@@ -614,16 +636,30 @@
         mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_CHILD_FRAMES_AS_TEXT);
     }
 
-    public void setCanOpenWindows() {
-        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setCanOpenWindows() called");
-        mLayoutTestControllerHandler.sendEmptyMessage(MSG_SET_CAN_OPEN_WINDOWS);
-    }
-
     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");
@@ -640,4 +676,16 @@
         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
index d9da672..17e19d0 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
@@ -39,20 +39,13 @@
     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 TESTS_ROOT_DIR_PATH =
-            Environment.getExternalStorageDirectory() +
-            File.separator + "android" +
-            File.separator + "LayoutTests";
-
-    /** TODO: make it a setting */
     static final String RESULTS_ROOT_DIR_PATH =
-            Environment.getExternalStorageDirectory() +
-            File.separator + "android" +
-            File.separator + "LayoutTests-results";
+            Environment.getExternalStorageDirectory() + File.separator + "layout-test-results";
 
     /** TODO: Make it a setting */
     private static final List<String> EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES =
@@ -94,30 +87,42 @@
                     break;
 
                 case MSG_CURRENT_TEST_CRASHED:
-                    mCrashMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
+                    mInternalMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
                     onTestCrashed();
                     break;
 
                 case MSG_ALL_TESTS_FINISHED:
-                    mSummarizer.setTestsRelativePath(mAllTestsRelativePath);
-                    mSummarizer.summarize();
-                    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;
+                    /** 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 mCrashMessagesHandler = new Handler() {
+    private Handler mInternalMessagesHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
-            if (msg.what == MSG_CRASH_TIMEOUT_EXPIRED) {
-                onTestCrashed();
+            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;
             }
         }
     };
@@ -141,8 +146,8 @@
     public void onCreate() {
         super.onCreate();
 
-        mFileFilter = new FileFilter(TESTS_ROOT_DIR_PATH);
-        mSummarizer = new Summarizer(mFileFilter, RESULTS_ROOT_DIR_PATH);
+        mFileFilter = new FileFilter();
+        mSummarizer = new Summarizer(mFileFilter, RESULTS_ROOT_DIR_PATH, getApplicationContext());
     }
 
     @Override
@@ -158,7 +163,7 @@
     }
 
     private void onActualResultsObtained(Bundle bundle) {
-        mCrashMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
+        mInternalMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
         ensureNextTestSetup(bundle.getString("nextTest"), bundle.getInt("testIndex") + 1);
 
         AbstractResult results =
@@ -176,7 +181,7 @@
 
         mCurrentlyRunningTest = nextTest;
         mCurrentlyRunningTestIndex = index;
-        mCrashMessagesHandler.sendEmptyMessageDelayed(MSG_CRASH_TIMEOUT_EXPIRED, CRASH_TIMEOUT_MS);
+        mInternalMessagesHandler.sendEmptyMessageDelayed(MSG_CRASH_TIMEOUT_EXPIRED, CRASH_TIMEOUT_MS);
     }
 
     /**
@@ -283,4 +288,4 @@
 
         return mLastExpectedResultPathFetched;
     }
-}
\ No newline at end of file
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
index 0efb78e..25c5ad5 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
@@ -16,10 +16,13 @@
 
 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;
 
@@ -31,7 +34,6 @@
 import java.net.URL;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.regex.Matcher;
@@ -75,9 +77,12 @@
             "       width: 20px;}" +
             "h3 span.sqr {" +
             "       text-decoration: none;" +
-            "       color: #8ee100;" +
             "       float: left;" +
             "       width: 20px;}" +
+            "h3 span.sqr_pass {" +
+            "       color: #8ee100;}" +
+            "h3 span.sqr_fail {" +
+            "       color: #c30000;}" +
             "span.source {" +
             "       display: block;" +
             "       font-size: 10px;" +
@@ -134,12 +139,6 @@
             "       background-color: #ff8888; }" +
             "span.ins {" +
             "       background-color: #88ff88; }" +
-            "span.fail {" +
-            "       color: red;}" +
-            "span.pass {" +
-            "       color: green;}" +
-            "span.time_out {" +
-            "       color: orange;}" +
             "table.summary {" +
             "       border: 1px solid black;" +
             "       margin-top: 20px;}" +
@@ -151,19 +150,16 @@
             "       text-transform: uppercase;" +
             "       padding: 3px;" +
             "       -webkit-border-radius: 4px;}" +
-            "span." + AbstractResult.ResultCode.PASS.name() + "{" +
-            "       background-color: #8ee100;" +
-            "       color: black;}" +
-            "span." + AbstractResult.ResultCode.FAIL_RESULT_DIFFERS.name() + "{" +
+            "span." + AbstractResult.ResultCode.RESULTS_DIFFER.name() + "{" +
             "       background-color: #ccc;" +
             "       color: black;}" +
-            "span." + AbstractResult.ResultCode.FAIL_NO_EXPECTED_RESULT.name() + "{" +
+            "span." + AbstractResult.ResultCode.NO_EXPECTED_RESULT.name() + "{" +
             "       background-color: #a700e4;" +
             "       color: #fff;}" +
-            "span." + AbstractResult.ResultCode.FAIL_TIMED_OUT.name() + "{" +
+            "span.timed_out {" +
             "       background-color: #f3cb00;" +
             "       color: black;}" +
-            "span." + AbstractResult.ResultCode.FAIL_CRASHED.name() + "{" +
+            "span.crashed {" +
             "       background-color: #c30000;" +
             "       color: #fff;}" +
             "span.noLtc {" +
@@ -193,22 +189,40 @@
     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;
 
-    public Summarizer(FileFilter fileFilter, String resultsRootDirPath) {
+    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() {
@@ -219,11 +233,12 @@
     public void appendTest(AbstractResult result) {
         String relativePath = result.getRelativePath();
 
-        if (result.getResultCode() == AbstractResult.ResultCode.FAIL_CRASHED) {
+        if (result.didCrash()) {
             mCrashedTestsCount++;
         }
 
-        if (result.getResultCode() == AbstractResult.ResultCode.PASS) {
+        if (result.didPass()) {
+            result.clearResults();
             if (mFileFilter.isFail(relativePath)) {
                 mUnexpectedPasses.add(result);
             } else {
@@ -236,50 +251,100 @@
                 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() {
-        createHtmlDetails();
-        createTxtSummary();
+    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;
-        mUnexpectedFailures.clear();
-        mExpectedFailures.clear();
-        mExpectedPasses.clear();
+        clearLists();
+        mDbHelper.reset();
         mDate = new Date();
     }
 
-    private void createTxtSummary() {
+    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(mTestsRelativePath + "\n");
+        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: " + getWebKitRevision() + "\n");
+        txt.append("WebKit revision: " + webKitRevision + "\n");
 
-        txt.append("TOTAL:   " + getTotalTestCount() + "\n");
-        if (mCrashedTestsCount > 0) {
-            txt.append("CRASHED (total among all tests): " + mCrashedTestsCount + "\n");
-            txt.append("-------------");
-        }
-        txt.append("UNEXPECTED FAILURES: " + mUnexpectedFailures.size() + "\n");
-        txt.append("UNEXPECTED PASSES:   " + mUnexpectedPasses.size() + "\n");
-        txt.append("EXPECTED FAILURES:   " + mExpectedFailures.size() + "\n");
-        txt.append("EXPECTED PASSES:     " + mExpectedPasses.size() + "\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() {
+    private void createHtmlDetails(String webKitRevision) {
         StringBuilder html = new StringBuilder();
 
         html.append("<html><head>");
@@ -287,27 +352,23 @@
         html.append(SCRIPT);
         html.append("</head><body>");
 
-        createTopSummaryTable(html);
+        createTopSummaryTable(webKitRevision, html);
+        dumpHtmlToFile(html, false);
 
-        createResultsListWithDiff(html, "Unexpected failures", mUnexpectedFailures);
-
-        createResultsListNoDiff(html, "Unexpected passes", mUnexpectedPasses);
-
-        createResultsListWithDiff(html, "Expected failures", mExpectedFailures);
-
-        createResultsListNoDiff(html, "Expected passes", mExpectedPasses);
+        createResultsList(html, "Unexpected failures", mUnexpectedFailuresCursor);
+        createResultsList(html, "Unexpected passes", mUnexpectedPassesCursor);
+        createResultsList(html, "Expected failures", mExpectedFailuresCursor);
+        createResultsList(html, "Expected passes", mExpectedPassesCursor);
 
         html.append("</body></html>");
-
-        FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_DETAILS_RELATIVE_PATH),
-                html.toString().getBytes(), false);
+        dumpHtmlToFile(html, true);
     }
 
     private int getTotalTestCount() {
-        return mUnexpectedFailures.size() +
-                mUnexpectedPasses.size() +
-                mExpectedPasses.size() +
-                mExpectedFailures.size();
+        return mUnexpectedFailuresCursor.getCount() +
+                mUnexpectedPassesCursor.getCount() +
+                mExpectedPassesCursor.getCount() +
+                mExpectedFailuresCursor.getCount();
     }
 
     private String getWebKitVersionFromUserAgentString() {
@@ -340,26 +401,26 @@
         return "unknown";
     }
 
-    private void createTopSummaryTable(StringBuilder html) {
+    private void createTopSummaryTable(String webKitRevision, StringBuilder html) {
         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
-        html.append("<h1>" + mTestsRelativePath + "</h1>");
+        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>");
 
-        String webkitRevision = getWebKitRevision();
         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("<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", mCrashedTestsCount);
-        createSummaryTableRow(html, "UNEXPECTED FAILURES", mUnexpectedFailures.size());
-        createSummaryTableRow(html, "UNEXPECTED PASSES", mUnexpectedPasses.size());
-        createSummaryTableRow(html, "EXPECTED FAILURES", mExpectedFailures.size());
-        createSummaryTableRow(html, "EXPECTED PASSES", mExpectedPasses.size());
+        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>");
     }
 
@@ -370,18 +431,24 @@
         html.append("</tr>");
     }
 
-    private void createResultsListWithDiff(StringBuilder html, String title,
-            List<AbstractResult> resultsList) {
+    private void createResultsList(
+            StringBuilder html, String title, Cursor cursor) {
         String relativePath;
         String id = "";
         AbstractResult.ResultCode resultCode;
 
-        Collections.sort(resultsList);
-        html.append("<h2>" + title + " [" + resultsList.size() + "]</h2>");
-        for (AbstractResult result : resultsList) {
+        html.append("<h2>" + title + " [" + cursor.getCount() + "]</h2>");
+
+        if (!cursor.moveToFirst()) {
+            return;
+        }
+
+        AbstractResult result;
+        do {
+            result = SummarizerDBHelper.getAbstractResult(cursor);
+
             relativePath = result.getRelativePath();
             resultCode = result.getResultCode();
-            assert resultCode != AbstractResult.ResultCode.PASS : "resultCode=" + resultCode;
 
             html.append("<h3>");
 
@@ -391,61 +458,79 @@
              * to cause any problems in this case
              */
             id = relativePath.replace(File.separator, ":");
-            html.append("<a href=\"#\" onClick=\"toggleDisplay('" + id + "');");
-            html.append("return false;\">");
-            html.append("<span class=\"tri\" id=\"tri." + id + "\">&#x25b6; </span>");
-            html.append("<span class=\"path\">" + relativePath + "</span>");
-            html.append("</a>");
 
-            html.append(" <span class=\"listItem " + resultCode.name() + "\">");
-            html.append(resultCode.toString());
-            html.append("</span>");
+            /** 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>");
+            }
 
-            /** 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>");
-                }
+            if (!result.didPass()) {
+                appendTags(html, result);
             }
 
             html.append("</h3>");
             appendExpectedResultsSources(result, html);
 
-            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>");
+            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 createResultsListNoDiff(StringBuilder html, String title,
-            List<AbstractResult> resultsList) {
-        Collections.sort(resultsList);
-        html.append("<h2>" + title + " [" + resultsList.size() + "]</h2>");
-        for (AbstractResult result : resultsList) {
-            html.append("<h3>");
-            html.append("<a href=\"" + getViewSourceUrl(result.getRelativePath()).toString() +
-                    "\"");
-            html.append(" target=\"_blank\">");
-            html.append("<span class=\"sqr\">&#x25a0; </span>");
-            html.append("<span class=\"path\">" + result.getRelativePath() + "</span>");
-            html.append("</a>");
-            html.append("</h3>");
-            appendExpectedResultsSources(result, html);
-            html.append("<div class=\"space\"></div>");
+    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>");
+            }
         }
     }
 
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/TestsListPreloaderThread.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java
index c714ec4..e0f14500 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java
@@ -29,12 +29,6 @@
 
     private static final String LOG_TAG = "TestsListPreloaderThread";
 
-    /** TODO: make it a setting */
-    private static final String TESTS_ROOT_DIR_PATH =
-            Environment.getExternalStorageDirectory() +
-            File.separator + "android" +
-            File.separator + "LayoutTests";
-
     /** A list containing relative paths of tests to run */
     private ArrayList<String> mTestsList = new ArrayList<String>();
 
@@ -55,7 +49,7 @@
      * @param doneMsg
      */
     public TestsListPreloaderThread(String path, Message doneMsg) {
-        mFileFilter = new FileFilter(TESTS_ROOT_DIR_PATH);
+        mFileFilter = new FileFilter();
         mRelativePath = path;
         mDoneMsg = doneMsg;
     }
@@ -112,4 +106,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java
index 9664efe..f835b6a 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java
@@ -39,12 +39,13 @@
     private String mExpectedResultPath;
     private String mActualResult;
     private String mRelativePath;
+    private boolean mDidTimeOut;
     private ResultCode mResultCode;
-    private Message mResultObtainedMsg;
+    transient private Message mResultObtainedMsg;
 
     private boolean mDumpChildFramesAsText;
 
-    private Handler mHandler = new Handler() {
+    transient private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             if (msg.what == MSG_DOCUMENT_AS_TEXT) {
@@ -74,10 +75,14 @@
         mActualResult = bundle.getString("actualTextualResult");
         setAdditionalTextOutputString(bundle.getString("additionalTextOutputString"));
         mRelativePath = bundle.getString("relativePath");
-        String resultCode = bundle.getString("resultCode");
-        if (resultCode != null) {
-            mResultCode = ResultCode.valueOf(resultCode);
-        }
+        mDidTimeOut = bundle.getBoolean("didTimeOut");
+    }
+
+    @Override
+    public void clearResults() {
+        super.clearResults();
+        mExpectedResult = null;
+        mActualResult = null;
     }
 
     @Override
@@ -87,17 +92,31 @@
         }
 
         if (mExpectedResult == null) {
-            mResultCode = AbstractResult.ResultCode.FAIL_NO_EXPECTED_RESULT;
+            mResultCode = AbstractResult.ResultCode.NO_EXPECTED_RESULT;
         } else if (!mExpectedResult.equals(mActualResult)) {
-            mResultCode = AbstractResult.ResultCode.FAIL_RESULT_DIFFERS;
+            mResultCode = AbstractResult.ResultCode.RESULTS_DIFFER;
         } else {
-            mResultCode = AbstractResult.ResultCode.PASS;
+            mResultCode = AbstractResult.ResultCode.RESULTS_MATCH;
         }
-
         return mResultCode;
     }
 
     @Override
+    public boolean didCrash() {
+        return false;
+    }
+
+    @Override
+    public boolean didTimeOut() {
+        return mDidTimeOut;
+    }
+
+    @Override
+    public void setDidTimeOut() {
+        mDidTimeOut = true;
+    }
+
+    @Override
     public byte[] getActualImageResult() {
         return null;
     }
@@ -239,9 +258,7 @@
         bundle.putString("actualTextualResult", getActualTextResult());
         bundle.putString("additionalTextOutputString", getAdditionalTextOutputString());
         bundle.putString("relativePath", mRelativePath);
-        if (mResultCode != null) {
-            bundle.putString("resultCode", mResultCode.name());
-        }
+        bundle.putBoolean("didTimeOut", mDidTimeOut);
         bundle.putString("type", getType().name());
         return bundle;
     }
@@ -250,4 +267,4 @@
     public String getRelativePath() {
         return mRelativePath;
     }
-}
\ No newline at end of file
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java
index d165a1a..086ff59 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java
@@ -36,52 +36,45 @@
     private static final int ADB_RESPONSE_SIZE = 4;
 
     /**
-     * Send an ADB command using existing socket connection
+     * 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()
      *
-     * The streams provided must be from a socket connected to adb already
+     * @return a socket that can be configured to link to remote machine
+     */
+    public static Socket createSocket() {
+        Socket socket = null;
+        try {
+            socket = new Socket(ADB_HOST, ADB_PORT);
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Creation failed.", e);
+        }
+        return socket;
+    }
+
+    /**
+     * 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 is input stream of the socket connection
-     * @param os output stream of the socket
-     * @param cmd the adb command to send
-     * @return if adb gave a success response
+     * @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
      */
-    private static boolean sendAdbCmd(InputStream is, OutputStream os, String cmd)
-            throws IOException {
-        byte[] buf = new byte[ADB_RESPONSE_SIZE];
-
+    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;
-        os.write(cmd.getBytes());
-        int read = is.read(buf);
+
+        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 faild.");
             return false;
         }
         return true;
     }
-
-    /**
-     * Get a tcp socket connection to specified IP address and port proxied by adb
-     *
-     * The proxying is transparent, e.g. if a socket is returned, then it can be written to and
-     * read from as if it is directly connected to the target
-     *
-     * @param remoteAddress IP address of the host to connect to
-     * @param remotePort port of the host to connect to
-     * @return a valid Socket instance if successful, null otherwise
-     */
-    public static Socket getSocketToRemoteMachine(String remoteAddress, int remotePort) {
-        try {
-            Socket socket = new Socket(ADB_HOST, ADB_PORT);
-            String cmd = "tcp:" + remotePort + ":" + remoteAddress;
-            if (!sendAdbCmd(socket.getInputStream(), socket.getOutputStream(), cmd)) {
-                socket.close();
-                return null;
-            }
-            return socket;
-        } catch (IOException ioe) {
-            Log.w(LOG_TAG, "error creating adb socket", ioe);
-            return null;
-        }
-    }
 }
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java
index 5e9f24e..4f01dae 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java
@@ -18,6 +18,8 @@
 
 import android.util.Log;
 
+import com.android.dumprendertree2.FsUtils;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -38,36 +40,25 @@
 
     private class SocketPipeThread extends Thread {
 
-        private Socket mInSocket, mOutSocket;
+        private InputStream mInputStream;
+        private OutputStream mOutputStream;
 
-        public SocketPipeThread(Socket inSocket, Socket outSocket) {
-            mInSocket = inSocket;
-            mOutSocket = outSocket;
+        public SocketPipeThread(InputStream inputStream, OutputStream outputStream) {
+            mInputStream = inputStream;
+            mOutputStream = outputStream;
+            setName("SocketPipeThread: " + getName());
         }
 
         @Override
         public void run() {
-            InputStream is;
-            OutputStream os;
-            try {
-                synchronized (this) {
-                    is = mInSocket.getInputStream();
-                    os = mOutSocket.getOutputStream();
-                }
-            } catch (IOException e) {
-                Log.w(LOG_TAG, this.toString(), e);
-                finish();
-                return;
-            }
-
             byte[] buffer = new byte[4096];
             int length;
             while (true) {
                 try {
-                    if ((length = is.read(buffer)) <= 0) {
+                    if ((length = mInputStream.read(buffer)) < 0) {
                         break;
                     }
-                    os.write(buffer, 0, length);
+                    mOutputStream.write(buffer, 0, length);
                 } catch (IOException e) {
                     /** This exception means one of the streams is closed */
                     Log.v(LOG_TAG, this.toString(), e);
@@ -75,10 +66,6 @@
                 }
             }
 
-            finish();
-        }
-
-        private void finish() {
             synchronized (mThreadsRunning) {
                 mThreadsRunning--;
                 if (mThreadsRunning == 0) {
@@ -90,7 +77,7 @@
 
         @Override
         public String toString() {
-            return "SocketPipeThread:\n" + mInSocket + "\n=>\n" + mOutSocket;
+            return getName();
         }
     }
 
@@ -98,20 +85,51 @@
 
     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(Socket fromSocket, Socket toSocket) {
+    public ConnectionHandler(String remoteMachineIp, int port, Socket fromSocket, Socket toSocket) {
+        mRemoteMachineIpAddress = remoteMachineIp;
+        mPort = port;
+
         mFromSocket = fromSocket;
         mToSocket = toSocket;
-        mFromToPipe = new SocketPipeThread(mFromSocket, mToSocket);
-        mToFromPipe = new SocketPipeThread(mToSocket, mFromSocket);
+
+        try {
+            mFromSocketInputStream = mFromSocket.getInputStream();
+            mToSocketInputStream = mToSocket.getInputStream();
+            mFromSocketOutputStream = mFromSocket.getOutputStream();
+            mToSocketOutputStream = mToSocket.getOutputStream();
+            if (!AdbUtils.configureConnection(mToSocketInputStream, mToSocketOutputStream,
+                    mRemoteMachineIpAddress, mPort)) {
+                throw new IOException("Configuring socket failed!");
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Unable to start ConnectionHandler", e);
+            closeStreams();
+            return;
+        }
+
+        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;
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java
index 31cd8ea..b361a89 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java
@@ -60,19 +60,18 @@
     @Override
     public void run() {
         while (true) {
-            /** These sockets will be closed when Forwarder.stop() is called */
             Socket localSocket;
             Socket remoteSocket;
             try {
                 localSocket = mServerSocket.accept();
-                remoteSocket = AdbUtils.getSocketToRemoteMachine(mRemoteMachineIpAddress,
-                        mPort);
             } catch (IOException e) {
                 /** This most likely means that mServerSocket is already closed */
                 Log.w(LOG_TAG, "mPort=" + mPort, e);
                 break;
             }
 
+            remoteSocket = AdbUtils.createSocket();
+
             if (remoteSocket == null) {
                 try {
                     localSocket.close();
@@ -86,7 +85,8 @@
             }
 
             final ConnectionHandler connectionHandler =
-                    new ConnectionHandler(localSocket, remoteSocket);
+                    new ConnectionHandler(mRemoteMachineIpAddress, mPort, localSocket,
+                            remoteSocket);
 
             /**
              * We have to close the sockets after the ConnectionHandler finishes, so we
@@ -98,9 +98,7 @@
                 @Override
                 public void onFinished() {
                     synchronized (this) {
-                        if (mConnectionHandlers.remove(connectionHandler)) {
-                            Log.d(LOG_TAG, "removeConnectionHandler(): removed");
-                        } else {
+                        if (!mConnectionHandlers.remove(connectionHandler)) {
                             assert false : "removeConnectionHandler(): not in the collection";
                         }
                     }
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java
index 7317a27..cff436f 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java
@@ -40,11 +40,9 @@
      * 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 = 8080;
+    public static final int HTTP_PORT = 8000;
     public static final int HTTPS_PORT = 8443;
 
-    public static final String HOST = "localhost";
-
     private static ForwarderManager forwarderManager;
 
     private Set<Forwarder> mForwarders;
@@ -75,7 +73,7 @@
 
         URL url = null;
         try {
-            url = new URL(protocol, HOST, port, "/");
+            url = new URL(protocol, HOST_IP, port, "/");
         } catch (MalformedURLException e) {
             assert false : "isHttps=" + isHttps;
         }
@@ -122,4 +120,4 @@
         mIsStarted = false;
         Log.i(LOG_TAG, "ForwarderManager stopped.");
     }
-}
\ 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
index b1862ef..35de88a 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
@@ -56,10 +56,6 @@
 public class DirListActivity extends ListActivity {
 
     private static final String LOG_TAG = "DirListActivity";
-    private static final String ROOT_DIR_PATH =
-            Environment.getExternalStorageDirectory() +
-            File.separator + "android" +
-            File.separator + "LayoutTests";
 
     /** TODO: This is just a guess - think of a better way to achieve it */
     private static final int MEAN_TITLE_CHAR_SIZE = 13;
@@ -82,13 +78,6 @@
     private String mCurrentDirPath;
 
     /**
-     * TODO: This should not be a constant, but rather be configurable from somewhere.
-     */
-    private String mRootDirPath = ROOT_DIR_PATH;
-
-    private FileFilter mFileFilter;
-
-    /**
      * A thread responsible for loading the contents of the directory from sd card
      * and sending them via Message to main thread that then loads them into
      * ListView
@@ -196,7 +185,6 @@
 
         ForwarderManager.getForwarderManager().start();
 
-        mFileFilter = new FileFilter(ROOT_DIR_PATH);
         mListView = getListView();
 
         mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@@ -420,4 +408,4 @@
 
         return subDirs.toArray(new ListItem[subDirs.size()]);
     }
-}
\ No newline at end of file
+}
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index d30a723..8a08c48 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -118,6 +118,16 @@
                 <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"
@@ -204,7 +214,7 @@
         <activity
                 android:name="LinesActivity"
                 android:label="_Lines"
-                android:hardwareAccelerated="false">
+                android:hardwareAccelerated="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
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/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/src/com/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
index 208dd88..4430533 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
@@ -97,6 +97,10 @@
             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.translate(120.0f, 0.0f);
             mAlphaPaint.setShader(mShader);            
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java
index 5b7753e..d9a2893 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java
@@ -39,6 +39,7 @@
             super(c);
 
             mLayerPaint = new Paint();
+            mLayerPaint.setAlpha(127);
             mRectPaint = new Paint();
             mRectPaint.setAntiAlias(true);
             mRectPaint.setTextSize(24.0f);
@@ -47,28 +48,21 @@
         @Override
         protected void onDraw(Canvas canvas) {
             super.onDraw(canvas);
-            
             canvas.drawRGB(128, 255, 128);
+
+            canvas.save();
+
             canvas.translate(140.0f, 100.0f);
-            
-            mLayerPaint.setAlpha(127);
-            int count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint,
-                    Canvas.ALL_SAVE_FLAG);
-
-            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);
+            drawStuff(canvas, Canvas.ALL_SAVE_FLAG);
 
             canvas.translate(0.0f, 200.0f);
+            drawStuff(canvas, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
             
-            mLayerPaint.setAlpha(127);
-            count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint,
-                    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);
@@ -76,7 +70,7 @@
             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/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/voip/java/android/net/sip/ISipSession.aidl b/voip/java/android/net/sip/ISipSession.aidl
index 1a23527..5661b8f 100644
--- a/voip/java/android/net/sip/ISipSession.aidl
+++ b/voip/java/android/net/sip/ISipSession.aidl
@@ -17,12 +17,11 @@
 package android.net.sip;
 
 import android.net.sip.ISipSessionListener;
-import android.net.sip.SessionDescription;
 import android.net.sip.SipProfile;
 
 /**
- * A SIP session that is associated with a SIP dialog or a transaction
- * (e.g., registration) not within a dialog.
+ * A SIP session that is associated with a SIP dialog or a transaction that is
+ * not within a dialog.
  * @hide
  */
 interface ISipSession {
@@ -113,9 +112,11 @@
      *
      * @param callee the SIP profile to make the call to
      * @param sessionDescription the session description of this call
+     * @param timeout the session will be timed out if the call is not
+     *        established within {@code timeout} seconds
      * @see ISipSessionListener
      */
-    void makeCall(in SipProfile callee, String sessionDescription);
+    void makeCall(in SipProfile callee, String sessionDescription, int timeout);
 
     /**
      * Answers an incoming call with the specified session description. The
@@ -123,8 +124,10 @@
      * {@link SipSessionState#INCOMING_CALL}.
      *
      * @param sessionDescription the session description to answer this call
+     * @param timeout the session will be timed out if the call is not
+     *        established within {@code timeout} seconds
      */
-    void answerCall(String sessionDescription);
+    void answerCall(String sessionDescription, int timeout);
 
     /**
      * Ends an established call, terminates an outgoing call or rejects an
@@ -141,6 +144,8 @@
      * to call when the session state is in {@link SipSessionState#IN_CALL}.
      *
      * @param sessionDescription the new session description
+     * @param timeout the session will be timed out if the call is not
+     *        established within {@code timeout} seconds
      */
-    void changeCall(String sessionDescription);
+    void changeCall(String sessionDescription, int timeout);
 }
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index 02f82b3..4abea20 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -20,8 +20,6 @@
 import android.net.rtp.AudioStream;
 import android.os.Message;
 
-import javax.sip.SipException;
-
 /**
  * Interface for making audio calls over SIP.
  * @hide
@@ -90,15 +88,16 @@
          * Called when an error occurs.
          *
          * @param call the call object that carries out the audio call
-         * @param errorCode error code defined in {@link SipErrorCode}
+         * @param errorCode error code of this error
          * @param errorMessage error message
          */
-        void onError(SipAudioCall call, String errorCode, String errorMessage);
+        void onError(SipAudioCall call, SipErrorCode errorCode,
+                String errorMessage);
     }
 
     /**
-     * The adapter class for {@link SipAudioCall#Listener}. The default
-     * implementation of all callback methods is no-op.
+     * The adapter class for {@link Listener}. The default implementation of
+     * all callback methods is no-op.
      */
     public class Adapter implements Listener {
         protected void onChanged(SipAudioCall call) {
@@ -127,7 +126,7 @@
         public void onCallHeld(SipAudioCall call) {
             onChanged(call);
         }
-        public void onError(SipAudioCall call, String errorCode,
+        public void onError(SipAudioCall call, SipErrorCode errorCode,
                 String errorMessage) {
             onChanged(call);
         }
@@ -135,7 +134,7 @@
 
     /**
      * Sets the listener to listen to the audio call events. The method calls
-     * {@link #setListener(Listener, false)}.
+     * {@code setListener(listener, false)}.
      *
      * @param listener to listen to the audio call events of this object
      * @see #setListener(Listener, boolean)
@@ -154,17 +153,29 @@
     void setListener(Listener listener, boolean callbackImmediately);
 
     /**
-     * Closes this object. The object is not usable after being closed.
+     * Closes this object. This object is not usable after being closed.
      */
     void close();
 
     /**
-     * Initiates an audio call to the specified profile.
+     * Initiates an audio call to the specified profile. The attempt will be
+     * timed out if the call is not established within {@code timeout} seconds
+     * and {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * will be called.
      *
      * @param callee the SIP profile to make the call to
      * @param sipManager the {@link SipManager} object to help make call with
+     * @param timeout the timeout value in seconds
+     * @see Listener.onError
      */
-    void makeCall(SipProfile callee, SipManager sipManager) throws SipException;
+    void makeCall(SipProfile callee, SipManager sipManager, int timeout)
+            throws SipException;
+
+    /**
+     * Starts the audio for the established call. This method should be called
+     * after {@link Listener#onCallEstablished} is called.
+     */
+    void startAudio();
 
     /**
      * Attaches an incoming call to this call object.
@@ -179,19 +190,39 @@
     void endCall() throws SipException;
 
     /**
-     * Puts a call on hold.  When succeeds,
-     * {@link #Listener#onCallHeld(SipAudioCall)} is called.
+     * Puts a call on hold.  When succeeds, {@link Listener#onCallHeld} is
+     * called. The attempt will be timed out if the call is not established
+     * within {@code timeout} seconds and
+     * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * will be called.
+     *
+     * @param timeout the timeout value in seconds
+     * @see Listener.onError
      */
-    void holdCall() throws SipException;
+    void holdCall(int timeout) throws SipException;
 
-    /** Answers a call. */
-    void answerCall() throws SipException;
+    /**
+     * Answers a call. The attempt will be timed out if the call is not
+     * established within {@code timeout} seconds and
+     * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * will be called.
+     *
+     * @param timeout the timeout value in seconds
+     * @see Listener.onError
+     */
+    void answerCall(int timeout) throws SipException;
 
     /**
      * Continues a call that's on hold. When succeeds,
-     * {@link #Listener#onCallEstablished(SipAudioCall)} is called.
+     * {@link Listener#onCallEstablished} is called. The attempt will be timed
+     * out if the call is not established within {@code timeout} seconds and
+     * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * will be called.
+     *
+     * @param timeout the timeout value in seconds
+     * @see Listener.onError
      */
-    void continueCall() throws SipException;
+    void continueCall(int timeout) throws SipException;
 
     /** Puts the device to speaker mode. */
     void setSpeakerMode(boolean speakerMode);
diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java
index a4fa053..fd32d4d 100644
--- a/voip/java/android/net/sip/SipAudioCallImpl.java
+++ b/voip/java/android/net/sip/SipAudioCallImpl.java
@@ -28,6 +28,7 @@
 import android.net.rtp.AudioGroup;
 import android.net.rtp.AudioStream;
 import android.net.rtp.RtpStream;
+import android.net.wifi.WifiManager;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.Vibrator;
@@ -36,13 +37,13 @@
 
 import java.io.IOException;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import javax.sdp.SdpException;
-import javax.sip.SipException;
 
 /**
  * Class that handles an audio call over SIP. 
@@ -55,6 +56,7 @@
     private static final boolean DONT_RELEASE_SOCKET = false;
     private static final String AUDIO = "audio";
     private static final int DTMF = 101;
+    private static final int SESSION_TIMEOUT = 5; // in seconds
 
     private Context mContext;
     private SipProfile mLocalProfile;
@@ -77,6 +79,9 @@
 
     private SipProfile mPendingCallRequest;
 
+    private SipErrorCode mErrorCode;
+    private String mErrorMessage;
+
     public SipAudioCallImpl(Context context, SipProfile localProfile) {
         mContext = context;
         mLocalProfile = localProfile;
@@ -89,23 +94,33 @@
     public void setListener(SipAudioCall.Listener listener,
             boolean callbackImmediately) {
         mListener = listener;
-        if ((listener == null) || !callbackImmediately) return;
         try {
-            SipSessionState state = getState();
-            switch (state) {
-            case READY_TO_CALL:
-                listener.onReadyToCall(this);
-                break;
-            case INCOMING_CALL:
-                listener.onRinging(this, getPeerProfile(mSipSession));
-                startRinging();
-                break;
-            case OUTGOING_CALL:
-                listener.onCalling(this);
-                break;
-            default:
-                listener.onError(this, SipErrorCode.CLIENT_ERROR.toString(),
-                        "wrong state to attach call: " + state);
+            if ((listener == null) || !callbackImmediately) {
+                // do nothing
+            } else if (mErrorCode != null) {
+                listener.onError(this, mErrorCode, mErrorMessage);
+            } else if (mInCall) {
+                if (mHold) {
+                    listener.onCallHeld(this);
+                } else {
+                    listener.onCallEstablished(this);
+                }
+            } else {
+                SipSessionState state = getState();
+                switch (state) {
+                    case READY_TO_CALL:
+                        listener.onReadyToCall(this);
+                        break;
+                    case INCOMING_CALL:
+                        listener.onRinging(this, getPeerProfile(mSipSession));
+                        break;
+                    case OUTGOING_CALL:
+                        listener.onCalling(this);
+                        break;
+                    case OUTGOING_CALL_RING_BACK:
+                        listener.onRingingBack(this);
+                        break;
+                }
             }
         } catch (Throwable t) {
             Log.e(TAG, "setListener()", t);
@@ -128,10 +143,21 @@
         if (closeRtp) stopCall(RELEASE_SOCKET);
         stopRingbackTone();
         stopRinging();
-        mSipSession = null;
+
         mInCall = false;
         mHold = false;
         mSessionId = -1L;
+        mErrorCode = null;
+        mErrorMessage = null;
+
+        if (mSipSession != null) {
+            try {
+                mSipSession.setListener(null);
+            } catch (RemoteException e) {
+                // don't care
+            }
+            mSipSession = null;
+        }
     }
 
     public synchronized SipProfile getLocalProfile() {
@@ -166,7 +192,7 @@
         Listener listener = mListener;
         if (listener != null) {
             try {
-                listener.onCalling(SipAudioCallImpl.this);
+                listener.onCalling(this);
             } catch (Throwable t) {
                 Log.e(TAG, "onCalling()", t);
             }
@@ -180,7 +206,7 @@
         Listener listener = mListener;
         if (listener != null) {
             try {
-                listener.onRingingBack(SipAudioCallImpl.this);
+                listener.onRingingBack(this);
             } catch (Throwable t) {
                 Log.e(TAG, "onRingingBack()", t);
             }
@@ -201,7 +227,7 @@
             // session changing request
             try {
                 mPeerSd = new SdpSessionDescription(sessionDescription);
-                answerCall();
+                answerCall(SESSION_TIMEOUT);
             } catch (Throwable e) {
                 Log.e(TAG, "onRinging()", e);
                 session.endCall();
@@ -211,31 +237,25 @@
         }
     }
 
-    private synchronized void establishCall(String sessionDescription) {
-        stopRingbackTone();
-        stopRinging();
-        try {
-            SdpSessionDescription sd =
-                    new SdpSessionDescription(sessionDescription);
-            Log.d(TAG, "sip call established: " + sd);
-            startCall(sd);
-            mInCall = true;
-        } catch (SdpException e) {
-            Log.e(TAG, "createSessionDescription()", e);
-        }
-    }
-
     @Override
     public void onCallEstablished(ISipSession session,
             String sessionDescription) {
-        establishCall(sessionDescription);
+        stopRingbackTone();
+        stopRinging();
+        try {
+            mPeerSd = new SdpSessionDescription(sessionDescription);
+            Log.d(TAG, "sip call established: " + mPeerSd);
+        } catch (SdpException e) {
+            Log.e(TAG, "createSessionDescription()", e);
+        }
+
         Listener listener = mListener;
         if (listener != null) {
             try {
                 if (mHold) {
-                    listener.onCallHeld(SipAudioCallImpl.this);
+                    listener.onCallHeld(this);
                 } else {
-                    listener.onCallEstablished(SipAudioCallImpl.this);
+                    listener.onCallEstablished(this);
                 }
             } catch (Throwable t) {
                 Log.e(TAG, "onCallEstablished()", t);
@@ -250,7 +270,7 @@
         Listener listener = mListener;
         if (listener != null) {
             try {
-                listener.onCallEnded(SipAudioCallImpl.this);
+                listener.onCallEnded(this);
             } catch (Throwable t) {
                 Log.e(TAG, "onCallEnded()", t);
             }
@@ -264,21 +284,27 @@
         Listener listener = mListener;
         if (listener != null) {
             try {
-                listener.onCallBusy(SipAudioCallImpl.this);
+                listener.onCallBusy(this);
             } catch (Throwable t) {
                 Log.e(TAG, "onCallBusy()", t);
             }
         }
     }
 
+    private SipErrorCode getErrorCode(String errorCode) {
+        return Enum.valueOf(SipErrorCode.class, errorCode);
+    }
+
     @Override
     public void onCallChangeFailed(ISipSession session, String errorCode,
             String message) {
         Log.d(TAG, "sip call change failed: " + message);
+        mErrorCode = getErrorCode(errorCode);
+        mErrorMessage = message;
         Listener listener = mListener;
         if (listener != null) {
             try {
-                listener.onError(SipAudioCallImpl.this, errorCode, message);
+                listener.onError(this, mErrorCode, message);
             } catch (Throwable t) {
                 Log.e(TAG, "onCallBusy()", t);
             }
@@ -286,16 +312,21 @@
     }
 
     @Override
-    public void onError(ISipSession session, String errorCode,
+    public void onError(ISipSession session, String errorCodeString,
             String message) {
-        Log.d(TAG, "sip session error: " + errorCode + ": " + message);
+        Log.d(TAG, "sip session error: " + errorCodeString + ": " + message);
+        SipErrorCode errorCode = mErrorCode = getErrorCode(errorCodeString);
+        mErrorMessage = message;
         synchronized (this) {
-            if (!isInCall()) close(true);
+            if ((mErrorCode == SipErrorCode.DATA_CONNECTION_LOST)
+                    || !isInCall()) {
+                close(true);
+            }
         }
         Listener listener = mListener;
         if (listener != null) {
             try {
-                listener.onError(SipAudioCallImpl.this, errorCode, message);
+                listener.onError(this, errorCode, message);
             } catch (Throwable t) {
                 Log.e(TAG, "onError()", t);
             }
@@ -308,6 +339,8 @@
         try {
             mPeerSd = new SdpSessionDescription(sessionDescription);
             session.setListener(this);
+
+            if (getState() == SipSessionState.INCOMING_CALL) startRinging();
         } catch (Throwable e) {
             Log.e(TAG, "attachCall()", e);
             throwSipException(e);
@@ -315,14 +348,15 @@
     }
 
     public synchronized void makeCall(SipProfile peerProfile,
-            SipManager sipManager) throws SipException {
+            SipManager sipManager, int timeout) throws SipException {
         try {
             mSipSession = sipManager.createSipSession(mLocalProfile, this);
             if (mSipSession == null) {
                 throw new SipException(
                         "Failed to create SipSession; network available?");
             }
-            mSipSession.makeCall(peerProfile, createOfferSessionDescription());
+            mSipSession.makeCall(peerProfile, createOfferSessionDescription(),
+                    timeout);
         } catch (Throwable e) {
             if (e instanceof SipException) {
                 throw (SipException) e;
@@ -345,10 +379,10 @@
         }
     }
 
-    public synchronized void holdCall() throws SipException {
+    public synchronized void holdCall(int timeout) throws SipException {
         if (mHold) return;
         try {
-            mSipSession.changeCall(createHoldSessionDescription());
+            mSipSession.changeCall(createHoldSessionDescription(), timeout);
             mHold = true;
         } catch (Throwable e) {
             throwSipException(e);
@@ -358,21 +392,21 @@
         if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
     }
 
-    public synchronized void answerCall() throws SipException {
+    public synchronized void answerCall(int timeout) throws SipException {
         try {
             stopRinging();
-            mSipSession.answerCall(createAnswerSessionDescription());
+            mSipSession.answerCall(createAnswerSessionDescription(), timeout);
         } catch (Throwable e) {
             Log.e(TAG, "answerCall()", e);
             throwSipException(e);
         }
     }
 
-    public synchronized void continueCall() throws SipException {
+    public synchronized void continueCall(int timeout) throws SipException {
         if (!mHold) return;
         try {
             mHold = false;
-            mSipSession.changeCall(createContinueSessionDescription());
+            mSipSession.changeCall(createContinueSessionDescription(), timeout);
         } catch (Throwable e) {
             throwSipException(e);
         }
@@ -545,10 +579,22 @@
         return copies;
     }
 
-    private void startCall(SdpSessionDescription peerSd) {
-        stopCall(DONT_RELEASE_SOCKET);
+    public void startAudio() {
+        try {
+            startAudioInternal();
+        } catch (UnknownHostException e) {
+            onError(mSipSession, SipErrorCode.PEER_NOT_REACHABLE.toString(),
+                    e.getMessage());
+        } catch (Throwable e) {
+            onError(mSipSession, SipErrorCode.CLIENT_ERROR.toString(),
+                    e.getMessage());
+        }
+    }
 
-        mPeerSd = peerSd;
+    private synchronized void startAudioInternal() throws UnknownHostException {
+        stopCall(DONT_RELEASE_SOCKET);
+        mInCall = true;
+        SdpSessionDescription peerSd = mPeerSd;
         String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO);
         // TODO: handle multiple media fields
         int peerMediaPort = peerSd.getPeerMediaPort(AUDIO);
@@ -557,58 +603,55 @@
         int localPort = getLocalMediaPort();
         int sampleRate = 8000;
         int frameSize = sampleRate / 50; // 160
-        try {
-            // TODO: get sample rate from sdp
-            mCodec = getCodec(peerSd);
 
-            AudioStream audioStream = mAudioStream;
-            audioStream.associate(InetAddress.getByName(peerMediaAddress),
-                    peerMediaPort);
-            audioStream.setCodec(convert(mCodec), mCodec.payloadType);
-            audioStream.setDtmfType(DTMF);
-            Log.d(TAG, "start media: localPort=" + localPort + ", peer="
-                    + peerMediaAddress + ":" + peerMediaPort);
+        // TODO: get sample rate from sdp
+        mCodec = getCodec(peerSd);
 
-            audioStream.setMode(RtpStream.MODE_NORMAL);
-            if (!mHold) {
-                // FIXME: won't work if peer is not sending nor receiving
-                if (!peerSd.isSending(AUDIO)) {
-                    Log.d(TAG, "   not receiving");
-                    audioStream.setMode(RtpStream.MODE_SEND_ONLY);
-                }
-                if (!peerSd.isReceiving(AUDIO)) {
-                    Log.d(TAG, "   not sending");
-                    audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY);
-                }
+        AudioStream audioStream = mAudioStream;
+        audioStream.associate(InetAddress.getByName(peerMediaAddress),
+                peerMediaPort);
+        audioStream.setCodec(convert(mCodec), mCodec.payloadType);
+        audioStream.setDtmfType(DTMF);
+        Log.d(TAG, "start media: localPort=" + localPort + ", peer="
+                + peerMediaAddress + ":" + peerMediaPort);
 
-                /* The recorder volume will be very low if the device is in
-                 * IN_CALL mode. Therefore, we have to set the mode to NORMAL
-                 * in order to have the normal microphone level.
-                 */
-                ((AudioManager) mContext.getSystemService
-                        (Context.AUDIO_SERVICE))
-                        .setMode(AudioManager.MODE_NORMAL);
+        audioStream.setMode(RtpStream.MODE_NORMAL);
+        if (!mHold) {
+            // FIXME: won't work if peer is not sending nor receiving
+            if (!peerSd.isSending(AUDIO)) {
+                Log.d(TAG, "   not receiving");
+                audioStream.setMode(RtpStream.MODE_SEND_ONLY);
+            }
+            if (!peerSd.isReceiving(AUDIO)) {
+                Log.d(TAG, "   not sending");
+                audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY);
             }
 
-            // AudioGroup logic:
-            AudioGroup audioGroup = getAudioGroup();
-            if (mHold) {
-                if (audioGroup != null) {
-                    audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
-                }
-                // don't create an AudioGroup here; doing so will fail if
-                // there's another AudioGroup out there that's active
+            /* The recorder volume will be very low if the device is in
+             * IN_CALL mode. Therefore, we have to set the mode to NORMAL
+             * in order to have the normal microphone level.
+             */
+            ((AudioManager) mContext.getSystemService
+                    (Context.AUDIO_SERVICE))
+                    .setMode(AudioManager.MODE_NORMAL);
+        }
+
+        // AudioGroup logic:
+        AudioGroup audioGroup = getAudioGroup();
+        if (mHold) {
+            if (audioGroup != null) {
+                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+            }
+            // don't create an AudioGroup here; doing so will fail if
+            // there's another AudioGroup out there that's active
+        } else {
+            if (audioGroup == null) audioGroup = new AudioGroup();
+            audioStream.join(audioGroup);
+            if (mMuted) {
+                audioGroup.setMode(AudioGroup.MODE_MUTED);
             } else {
-                if (audioGroup == null) audioGroup = new AudioGroup();
-                audioStream.join(audioGroup);
-                if (mMuted) {
-                    audioGroup.setMode(AudioGroup.MODE_MUTED);
-                } else {
-                    audioGroup.setMode(AudioGroup.MODE_NORMAL);
-                }
+                audioGroup.setMode(AudioGroup.MODE_NORMAL);
             }
-        } catch (Exception e) {
-            Log.e(TAG, "call()", e);
         }
     }
 
diff --git a/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java
index 2eb67e8..a27f740 100644
--- a/voip/java/android/net/sip/SipErrorCode.java
+++ b/voip/java/android/net/sip/SipErrorCode.java
@@ -18,10 +18,10 @@
 
 /**
  * Defines error code returned in
- * {@link SipRegistrationListener#onRegistrationFailed(String, String, String)},
- * {@link ISipSessionListener#onError(ISipSession, String, String)},
- * {@link ISipSessionListener#onCallChangeFailed(ISipSession, String, String)} and
- * {@link ISipSessionListener#onRegistrationFailed(ISipSession, String, String)}.
+ * {@link SipRegistrationListener#onRegistrationFailed},
+ * {@link ISipSessionListener#onError},
+ * {@link ISipSessionListener#onCallChangeFailed} and
+ * {@link ISipSessionListener#onRegistrationFailed}.
  * @hide
  */
 public enum SipErrorCode {
@@ -31,6 +31,9 @@
     /** When server responds with an error. */
     SERVER_ERROR,
 
+    /** When transaction is terminated unexpectedly. */
+    TRANSACTION_TERMINTED,
+
     /** When some error occurs on the device, possibly due to a bug. */
     CLIENT_ERROR,
 
@@ -40,6 +43,15 @@
     /** When the remote URI is not valid. */
     INVALID_REMOTE_URI,
 
+    /** When the peer is not reachable. */
+    PEER_NOT_REACHABLE,
+
     /** When invalid credentials are provided. */
-    INVALID_CREDENTIALS;
+    INVALID_CREDENTIALS,
+
+    /** The client is in a transaction and cannot initiate a new one. */
+    IN_PROGRESS,
+
+    /** When data connection is lost. */
+    DATA_CONNECTION_LOST;
 }
diff --git a/voip/java/android/net/sip/SipException.java b/voip/java/android/net/sip/SipException.java
new file mode 100644
index 0000000..f0d846b
--- /dev/null
+++ b/voip/java/android/net/sip/SipException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.sip;
+
+/**
+ * General SIP-related exception class.
+ * @hide
+ */
+public class SipException extends Exception {
+    public SipException() {
+    }
+
+    public SipException(String message) {
+        super(message);
+    }
+
+    public SipException(String message, Throwable cause) {
+        // we want to eliminate the dependency on javax.sip.SipException
+        super(message, ((cause instanceof javax.sip.SipException)
+                && (cause.getCause() != null))
+                ? cause.getCause()
+                : cause);
+    }
+}
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index beec8fe..36895cd 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -25,29 +25,25 @@
 import android.os.ServiceManager;
 
 import java.text.ParseException;
-import javax.sip.SipException;
 
 /**
  * The class provides API for various SIP related tasks. Specifically, the API
- * allows the application to:
+ * allows an application to:
  * <ul>
  * <li>register a {@link SipProfile} to have the background SIP service listen
  *      to incoming calls and broadcast them with registered command string. See
  *      {@link #open(SipProfile, String, SipRegistrationListener)},
- *      {@link #open(SipProfile)}, {@link #close(String)},
- *      {@link #isOpened(String)} and {@link isRegistered(String)}. It also
- *      facilitates handling of the incoming call broadcast intent. See
- *      {@link #isIncomingCallIntent(Intent)}, {@link #getCallId(Intent)},
- *      {@link #getOfferSessionDescription(Intent)} and
- *      {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener)}.</li>
+ *      {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and
+ *      {@link #isRegistered}. It also facilitates handling of the incoming call
+ *      broadcast intent. See
+ *      {@link #isIncomingCallIntent}, {@link #getCallId},
+ *      {@link #getOfferSessionDescription} and {@link #takeAudioCall}.</li>
  * <li>make/take SIP-based audio calls. See
- *      {@link #makeAudioCall(Context, SipProfile, SipProfile, SipAudioCall.Listener)}
- *      and {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener}.</li>
+ *      {@link #makeAudioCall} and {@link #takeAudioCall}.</li>
  * <li>register/unregister with a SIP service provider. See
- *      {@link #register(SipProfile, int, ISipSessionListener)} and
- *      {@link #unregister(SipProfile, ISipSessionListener)}.</li>
+ *      {@link #register} and {@link #unregister}.</li>
  * <li>process SIP events directly with a {@link ISipSession} created by
- *      {@link createSipSession(SipProfile, ISipSessionListener)}.</li>
+ *      {@link #createSipSession}.</li>
  * </ul>
  * @hide
  */
@@ -114,8 +110,7 @@
     /**
      * Opens the profile for making calls and/or receiving calls. Subsequent
      * SIP calls can be made through the default phone UI. The caller may also
-     * make subsequent calls through
-     * {@link #makeAudioCall(Context, String, String, SipAudioCall.Listener)}.
+     * make subsequent calls through {@link #makeAudioCall}.
      * If the receiving-call option is enabled in the profile, the SIP service
      * will register the profile to the corresponding server periodically in
      * order to receive calls from the server.
@@ -135,8 +130,7 @@
     /**
      * Opens the profile for making calls and/or receiving calls. Subsequent
      * SIP calls can be made through the default phone UI. The caller may also
-     * make subsequent calls through
-     * {@link #makeAudioCall(Context, String, String, SipAudioCall.Listener)}.
+     * make subsequent calls through {@link #makeAudioCall}.
      * If the receiving-call option is enabled in the profile, the SIP service
      * will register the profile to the corresponding server periodically in
      * order to receive calls from the server.
@@ -161,9 +155,7 @@
 
     /**
      * Sets the listener to listen to registration events. No effect if the
-     * profile has not been opened to receive calls
-     * (see {@link #open(SipProfile, String, SipRegistrationListener)} and
-     * {@link #open(SipProfile)}).
+     * profile has not been opened to receive calls (see {@link #open}).
      *
      * @param localProfileUri the URI of the profile
      * @param listener to listen to registration events; can be null
@@ -226,44 +218,55 @@
     }
 
     /**
-     * Creates a {@link SipAudioCall} to make a call.
+     * Creates a {@link SipAudioCall} to make a call. The attempt will be timed
+     * out if the call is not established within {@code timeout} seconds and
+     * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * will be called.
      *
      * @param context context to create a {@link SipAudioCall} object
      * @param localProfile the SIP profile to make the call from
      * @param peerProfile the SIP profile to make the call to
      * @param listener to listen to the call events from {@link SipAudioCall};
      *      can be null
+     * @param timeout the timeout value in seconds
      * @return a {@link SipAudioCall} object
      * @throws SipException if calling the SIP service results in an error
+     * @see SipAudioCall.Listener.onError
      */
     public SipAudioCall makeAudioCall(Context context, SipProfile localProfile,
-            SipProfile peerProfile, SipAudioCall.Listener listener)
+            SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
             throws SipException {
         SipAudioCall call = new SipAudioCallImpl(context, localProfile);
         call.setListener(listener);
-        call.makeCall(peerProfile, this);
+        call.makeCall(peerProfile, this, timeout);
         return call;
     }
 
     /**
      * Creates a {@link SipAudioCall} to make a call. To use this method, one
-     * must call {@link #open(SipProfile)} first.
+     * must call {@link #open(SipProfile)} first. The attempt will be timed out
+     * if the call is not established within {@code timeout} seconds and
+     * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * will be called.
      *
      * @param context context to create a {@link SipAudioCall} object
      * @param localProfileUri URI of the SIP profile to make the call from
      * @param peerProfileUri URI of the SIP profile to make the call to
      * @param listener to listen to the call events from {@link SipAudioCall};
      *      can be null
+     * @param timeout the timeout value in seconds
      * @return a {@link SipAudioCall} object
      * @throws SipException if calling the SIP service results in an error
+     * @see SipAudioCall.Listener.onError
      */
     public SipAudioCall makeAudioCall(Context context, String localProfileUri,
-            String peerProfileUri, SipAudioCall.Listener listener)
+            String peerProfileUri, SipAudioCall.Listener listener, int timeout)
             throws SipException {
         try {
             return makeAudioCall(context,
                     new SipProfile.Builder(localProfileUri).build(),
-                    new SipProfile.Builder(peerProfileUri).build(), listener);
+                    new SipProfile.Builder(peerProfileUri).build(), listener,
+                    timeout);
         } catch (ParseException e) {
             throw new SipException("build SipProfile", e);
         }
@@ -284,7 +287,7 @@
     /**
      * Creates a {@link SipAudioCall} to take an incoming call. Before the call
      * is returned, the listener will receive a
-     * {@link SipAudioCall#Listener.onRinging(SipAudioCall, SipProfile)}
+     * {@link SipAudioCall.Listener#onRinging}
      * callback.
      *
      * @param context context to create a {@link SipAudioCall} object
@@ -378,12 +381,11 @@
 
     /**
      * Registers the profile to the corresponding server for receiving calls.
-     * {@link #open(SipProfile, String, SipRegistrationListener)} is still
-     * needed to be called at least once in order for the SIP service to
-     * broadcast an intent when an incoming call is received.
+     * {@link #open} is still needed to be called at least once in order for
+     * the SIP service to broadcast an intent when an incoming call is received.
      *
      * @param localProfile the SIP profile to register with
-     * @param expiryTime registration expiration time (in second)
+     * @param expiryTime registration expiration time (in seconds)
      * @param listener to listen to the registration events
      * @throws SipException if calling the SIP service results in an error
      */
@@ -420,9 +422,9 @@
     /**
      * Gets the {@link ISipSession} that handles the incoming call. For audio
      * calls, consider to use {@link SipAudioCall} to handle the incoming call.
-     * See {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener)}.
-     * Note that the method may be called only once for the same intent. For
-     * subsequent calls on the same intent, the method returns null.
+     * See {@link #takeAudioCall}. Note that the method may be called only once
+     * for the same intent. For subsequent calls on the same intent, the method
+     * returns null.
      *
      * @param incomingCallIntent the incoming call broadcast intent
      * @return the session object that handles the incoming call
@@ -501,15 +503,16 @@
         }
 
         @Override
-        public void onRegistrationFailed(ISipSession session, String className,
+        public void onRegistrationFailed(ISipSession session, String errorCode,
                 String message) {
-            mListener.onRegistrationFailed(getUri(session), className, message);
+            mListener.onRegistrationFailed(getUri(session),
+                    Enum.valueOf(SipErrorCode.class, errorCode), message);
         }
 
         @Override
         public void onRegistrationTimeout(ISipSession session) {
             mListener.onRegistrationFailed(getUri(session),
-                    SipException.class.getName(), "registration timed out");
+                    SipErrorCode.TIME_OUT, "registration timed out");
         }
     }
 }
diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java
index aa2da75..88bfba9 100644
--- a/voip/java/android/net/sip/SipProfile.java
+++ b/voip/java/android/net/sip/SipProfile.java
@@ -61,7 +61,7 @@
             };
 
     /**
-     * Class to help create a {@link SipProfile}.
+     * Class to help create a {@code SipProfile}.
      */
     public static class Builder {
         private AddressFactory mAddressFactory;
@@ -120,8 +120,8 @@
          *
          * @param username username of the SIP account
          * @param serverDomain the SIP server domain; if the network address
-         *      is different from the domain, use
-         *      {@link #setOutboundProxy(String)} to set server address
+         *      is different from the domain, use {@link #setOutboundProxy} to
+         *      set server address
          * @throws ParseException if the parameters are not valid
          */
         public Builder(String username, String serverDomain)
@@ -167,11 +167,15 @@
          *
          * @param port port number of the server
          * @return this builder object
-         * @throws InvalidArgumentException if the port number is out of range
+         * @throws IllegalArgumentException if the port number is out of range
          */
-        public Builder setPort(int port) throws InvalidArgumentException {
-            mUri.setPort(port);
-            return this;
+        public Builder setPort(int port) throws IllegalArgumentException {
+            try {
+                mUri.setPort(port);
+                return this;
+            } catch (InvalidArgumentException e) {
+                throw new IllegalArgumentException(e);
+            }
         }
 
         /**
@@ -180,16 +184,16 @@
          *
          * @param protocol the protocol string
          * @return this builder object
-         * @throws InvalidArgumentException if the protocol is not recognized
+         * @throws IllegalArgumentException if the protocol is not recognized
          */
         public Builder setProtocol(String protocol)
-                throws InvalidArgumentException {
+                throws IllegalArgumentException {
             if (protocol == null) {
                 throw new NullPointerException("protocol cannot be null");
             }
             protocol = protocol.toUpperCase();
             if (!protocol.equals("UDP") && !protocol.equals("TCP")) {
-                throw new InvalidArgumentException(
+                throw new IllegalArgumentException(
                         "unsupported protocol: " + protocol);
             }
             mProfile.mProtocol = protocol;
@@ -305,6 +309,7 @@
      * Gets the SIP URI of this profile.
      *
      * @return the SIP URI of this profile
+     * @hide
      */
     public SipURI getUri() {
         return (SipURI) mAddress.getURI();
@@ -323,6 +328,7 @@
      * Gets the SIP address of this profile.
      *
      * @return the SIP address of this profile
+     * @hide
      */
     public Address getSipAddress() {
         return mAddress;
diff --git a/voip/java/android/net/sip/SipRegistrationListener.java b/voip/java/android/net/sip/SipRegistrationListener.java
index 22488d7..705f271 100644
--- a/voip/java/android/net/sip/SipRegistrationListener.java
+++ b/voip/java/android/net/sip/SipRegistrationListener.java
@@ -29,20 +29,20 @@
     void onRegistering(String localProfileUri);
 
     /**
-     * Called when registration is successfully done.
+     * Called when the registration succeeded.
      *
      * @param localProfileUri the URI string of the SIP profile to register with
-     * @param expiryTime duration in second before the registration expires
+     * @param expiryTime duration in seconds before the registration expires
      */
     void onRegistrationDone(String localProfileUri, long expiryTime);
 
     /**
-     * Called when the registration fails.
+     * Called when the registration failed.
      *
      * @param localProfileUri the URI string of the SIP profile to register with
-     * @param errorCode error code defined in {@link SipErrorCode}
+     * @param errorCode error code of this error
      * @param errorMessage error message
      */
-    void onRegistrationFailed(String localProfileUri, String errorCode,
+    void onRegistrationFailed(String localProfileUri, SipErrorCode errorCode,
             String errorMessage);
 }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1913fa0..e73bca0 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -21,6 +21,8 @@
 import android.net.wifi.ScanResult;
 import android.net.DhcpInfo;
 
+import android.os.WorkSource;
+
 /**
  * Interface that allows controlling and querying Wi-Fi connectivity.
  *
@@ -66,7 +68,9 @@
 
     DhcpInfo getDhcpInfo();
 
-    boolean acquireWifiLock(IBinder lock, int lockType, String tag);
+    boolean acquireWifiLock(IBinder lock, int lockType, String tag, in WorkSource ws);
+
+    void updateWifiLockWorkSource(IBinder lock, in WorkSource ws);
 
     boolean releaseWifiLock(IBinder lock);
 
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 78d5b7e..dfa9f75 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -19,12 +19,22 @@
 import android.app.ActivityManagerNative;
 import android.content.Context;
 import android.content.Intent;
+import android.net.DhcpInfo;
+import android.net.wifi.WifiConfiguration.IpAssignment;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.Status;
+import android.os.Environment;
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.BitSet;
+import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -34,9 +44,23 @@
  *
  * It deals with the following
  * - Add/update/remove a WifiConfiguration
+ *   The configuration contains two types of information.
+ *     = IP configuration that is handled by WifiConfigStore and
+ *       is saved to disk on any change.
+ *     = 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
+ *
  * TODO:
- * - handle static IP per configuration
  * - handle proxy per configuration
  */
 class WifiConfigStore {
@@ -44,10 +68,28 @@
     private static Context sContext;
     private static final String TAG = "WifiConfigStore";
 
-    private static List<WifiConfiguration> sConfiguredNetworks = new ArrayList<WifiConfiguration>();
+    /* 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.
@@ -66,7 +108,7 @@
     static List<WifiConfiguration> getConfiguredNetworks() {
         List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
         synchronized (sConfiguredNetworks) {
-            for (WifiConfiguration config : sConfiguredNetworks) {
+            for(WifiConfiguration config : sConfiguredNetworks.values()) {
                 networks.add(config.clone());
             }
         }
@@ -78,14 +120,20 @@
      * of configured networks indicates all networks as being enabled
      */
     static void enableAllNetworks() {
-        for (WifiConfiguration config : sConfiguredNetworks) {
-            if(config != null && config.status == Status.DISABLED) {
-                WifiNative.enableNetworkCommand(config.networkId, false);
+        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();
-        updateConfigAndSendChangeBroadcast();
+        sendConfigChangeBroadcast();
     }
 
     /**
@@ -102,7 +150,11 @@
     static void selectNetwork(WifiConfiguration config) {
         if (config != null) {
             int netId = addOrUpdateNetworkNative(config);
-            selectNetwork(netId);
+            if (netId != -1) {
+                selectNetwork(netId);
+            } else {
+                Log.e(TAG, "Failed to update network " + config);
+            }
         }
     }
 
@@ -120,10 +172,12 @@
     static void selectNetwork(int netId) {
         // Reset the priority of each network at start or if it goes too high.
         if (sLastPriority == -1 || sLastPriority > 1000000) {
-            for (WifiConfiguration conf : sConfiguredNetworks) {
-                if (conf.networkId != -1) {
-                    conf.priority = 0;
-                    addOrUpdateNetworkNative(conf);
+            synchronized (sConfiguredNetworks) {
+                for(WifiConfiguration config : sConfiguredNetworks.values()) {
+                    if (config.networkId != -1) {
+                        config.priority = 0;
+                        addOrUpdateNetworkNative(config);
+                    }
                 }
             }
             sLastPriority = 0;
@@ -138,13 +192,10 @@
         WifiNative.saveConfigCommand();
 
         /* Enable the given network while disabling all other networks */
-        WifiNative.enableNetworkCommand(netId, true);
+        enableNetworkWithoutBroadcast(netId, true);
 
-        /* update the configured networks list but not send a
-         * broadcast to avoid a fetch from settings
-         * during this temporary disabling of networks
-         */
-        updateConfiguredNetworks();
+       /* Avoid saving the config & sending a broadcast to prevent settings
+        * from displaying a disabled list of networks */
     }
 
     /**
@@ -153,13 +204,17 @@
      * @param config WifiConfiguration to be saved
      */
     static void saveNetwork(WifiConfiguration config) {
+        boolean newNetwork = (config.networkId == -1);
         int netId = addOrUpdateNetworkNative(config);
         /* enable a new network */
-        if (config.networkId < 0) {
+        if (newNetwork && netId >= 0) {
             WifiNative.enableNetworkCommand(netId, false);
+            synchronized (sConfiguredNetworks) {
+                sConfiguredNetworks.get(netId).status = Status.ENABLED;
+            }
         }
         WifiNative.saveConfigCommand();
-        updateConfigAndSendChangeBroadcast();
+        sendConfigChangeBroadcast();
     }
 
     /**
@@ -168,9 +223,15 @@
      * @param netId network to forget
      */
     static void forgetNetwork(int netId) {
-        WifiNative.removeNetworkCommand(netId);
-        WifiNative.saveConfigCommand();
-        updateConfigAndSendChangeBroadcast();
+        if (WifiNative.removeNetworkCommand(netId)) {
+            WifiNative.saveConfigCommand();
+            synchronized (sConfiguredNetworks) {
+                sConfiguredNetworks.remove(netId);
+            }
+            sendConfigChangeBroadcast();
+        } else {
+            Log.e(TAG, "Failed to remove network " + netId);
+        }
     }
 
     /**
@@ -183,7 +244,7 @@
      */
     static int addOrUpdateNetwork(WifiConfiguration config) {
         int ret = addOrUpdateNetworkNative(config);
-        updateConfigAndSendChangeBroadcast();
+        sendConfigChangeBroadcast();
         return ret;
     }
 
@@ -197,7 +258,10 @@
      */
     static boolean removeNetwork(int netId) {
         boolean ret = WifiNative.removeNetworkCommand(netId);
-        updateConfigAndSendChangeBroadcast();
+        synchronized (sConfiguredNetworks) {
+            if (ret) sConfiguredNetworks.remove(netId);
+        }
+        sendConfigChangeBroadcast();
         return ret;
     }
 
@@ -210,8 +274,28 @@
      * @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);
-        updateConfigAndSendChangeBroadcast();
+
+        synchronized (sConfiguredNetworks) {
+            WifiConfiguration config = sConfiguredNetworks.get(netId);
+            if (config != null) config.status = Status.ENABLED;
+        }
+
+        if (disableOthers) {
+            synchronized (sConfiguredNetworks) {
+                for(WifiConfiguration config : sConfiguredNetworks.values()) {
+                    if(config != null && config.networkId != netId) {
+                        config.status = Status.DISABLED;
+                    }
+                }
+            }
+        }
         return ret;
     }
 
@@ -221,7 +305,11 @@
      */
     static boolean disableNetwork(int netId) {
         boolean ret = WifiNative.disableNetworkCommand(netId);
-        updateConfigAndSendChangeBroadcast();
+        synchronized (sConfiguredNetworks) {
+            WifiConfiguration config = sConfiguredNetworks.get(netId);
+            if (config != null) config.status = Status.DISABLED;
+        }
+        sendConfigChangeBroadcast();
         return ret;
     }
 
@@ -232,8 +320,31 @@
         return WifiNative.saveConfigCommand();
     }
 
-    private static void updateConfigAndSendChangeBroadcast() {
-        updateConfiguredNetworks();
+    /**
+     * 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;
+    }
+
+    /**
+     * 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);
@@ -245,6 +356,7 @@
 
         synchronized (sConfiguredNetworks) {
             sConfiguredNetworks.clear();
+            sNetworkIds.clear();
 
             if (listStr == null)
                 return;
@@ -274,9 +386,129 @@
                 if (config.priority > sLastPriority) {
                     sLastPriority = config.priority;
                 }
-                sConfiguredNetworks.add(config);
+                sConfiguredNetworks.put(config.networkId, config);
+                sNetworkIds.put(configKey(config), config.networkId);
             }
         }
+        readIpConfigurations();
+    }
+
+    private static void writeIpConfigurations() {
+        StringBuilder builder = new StringBuilder();
+        BufferedWriter out = null;
+
+        builder.append(IPCONFIG_FILE_VERSION);
+        builder.append("\n");
+
+        synchronized (sConfiguredNetworks) {
+            for(WifiConfiguration config : sConfiguredNetworks.values()) {
+                if (config.ipAssignment == WifiConfiguration.IpAssignment.STATIC) {
+                    builder.append("id=" + configKey(config));
+                    builder.append(":");
+                    builder.append("ip=" + config.ipConfig.ipAddress);
+                    builder.append(":");
+                    builder.append("gateway=" + config.ipConfig.gateway);
+                    builder.append(":");
+                    builder.append("netmask=" + config.ipConfig.netmask);
+                    builder.append(":");
+                    builder.append("dns1=" + config.ipConfig.dns1);
+                    builder.append(":");
+                    builder.append("dns2=" + config.ipConfig.dns2);
+                    builder.append("\n");
+                }
+            }
+        }
+
+        try {
+            out = new BufferedWriter(new FileWriter(ipConfigFile), builder.length());
+            out.write(builder.toString());
+        } catch (IOException e) {
+            Log.e(TAG, "Error writing data file");
+            return;
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (Exception e) {}
+            }
+        }
+    }
+
+    private static void readIpConfigurations() {
+        File f = new File(ipConfigFile);
+        byte[] buffer;
+        FileInputStream s = null;
+        try {
+            buffer = new byte[(int)f.length()];
+            s = new FileInputStream(f);
+            s.read(buffer);
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading data file");
+            return;
+        } finally {
+            if (s != null) {
+                try {
+                    s.close();
+                } catch (Exception e) {}
+            }
+        }
+
+        String data = new String(buffer);
+        if (data == null || data.length() == 0) {
+            Log.d(TAG, "IP configuration file empty");
+            return;
+        }
+
+        String[] parsed = data.split("\n");
+        try {
+            if (Integer.parseInt(parsed[0]) != IPCONFIG_FILE_VERSION) {
+                Log.e(TAG, "Bad version on IP configuration file, ignore read");
+                return;
+            }
+
+            for (String line : parsed) {
+                int hashKey = -1;
+                DhcpInfo ipConfig = new DhcpInfo();
+                String[] keyVals = line.split(":");
+
+                for (String keyVal : keyVals) {
+                    String[] keyValPair = keyVal.split("=");
+                    if (keyValPair[0].equals("id")) {
+                        hashKey = Integer.parseInt(keyValPair[1]);
+                    } else if (keyValPair[0].equals("ip")) {
+                        ipConfig.ipAddress = Integer.parseInt(keyValPair[1]);
+                    } else if (keyValPair[0].equals("gateway")) {
+                        ipConfig.gateway = Integer.parseInt(keyValPair[1]);
+                    } else if (keyValPair[0].equals("netmask")) {
+                        ipConfig.netmask = Integer.parseInt(keyValPair[1]);
+                    } else if (keyValPair[0].equals("dns1")) {
+                        ipConfig.dns1 = Integer.parseInt(keyValPair[1]);
+                    } else if (keyValPair[0].equals("dns2")) {
+                        ipConfig.dns2 = Integer.parseInt(keyValPair[1]);
+                    } else {
+                        Log.w(TAG, "Ignoring " + keyVal);
+                    }
+                }
+
+                if (hashKey != -1) {
+                    synchronized (sConfiguredNetworks) {
+                        WifiConfiguration config = sConfiguredNetworks.get(
+                                sNetworkIds.get(hashKey));
+
+                        if (config == null) {
+                            Log.e(TAG, "IP configuration found for missing network, ignored");
+                        } else {
+                            config.ipAssignment = WifiConfiguration.IpAssignment.STATIC;
+                            config.ipConfig = ipConfig;
+                        }
+                    }
+                } else {
+                    Log.e(TAG,"Missing id while parsing configuration" + line);
+                }
+            }
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "Error parsing configuration");
+        }
     }
 
     private static int addOrUpdateNetworkNative(WifiConfiguration config) {
@@ -286,6 +518,7 @@
          * refer to an existing configuration.
          */
         int netId = config.networkId;
+        boolean updateFailed = true;
         boolean newNetwork = netId == -1;
         // networkId of -1 means we want to create a new network
 
@@ -457,17 +690,53 @@
                     }
                 }
             }
-            return netId;
+            updateFailed = false;
         }
 
-        if (newNetwork) {
-            WifiNative.removeNetworkCommand(netId);
-            Log.d(TAG,
-                    "Failed to set a network variable, removed network: "
-                    + netId);
+        if (updateFailed) {
+            if (newNetwork) {
+                WifiNative.removeNetworkCommand(netId);
+                Log.d(TAG,
+                        "Failed to set a network variable, removed network: "
+                        + netId);
+            }
+            return -1;
         }
 
-        return -1;
+        /* 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);
+
+        if (config.ipAssignment != IpAssignment.UNASSIGNED) {
+            if (newNetwork ||
+                    (sConfig.ipAssignment != config.ipAssignment) ||
+                    (sConfig.ipConfig.ipAddress != config.ipConfig.ipAddress) ||
+                    (sConfig.ipConfig.gateway != config.ipConfig.gateway) ||
+                    (sConfig.ipConfig.netmask != config.ipConfig.netmask) ||
+                    (sConfig.ipConfig.dns1 != config.ipConfig.dns1) ||
+                    (sConfig.ipConfig.dns2 != config.ipConfig.dns2)) {
+                sConfig.ipAssignment = config.ipAssignment;
+                sConfig.ipConfig = config.ipConfig;
+                writeIpConfigurations();
+            }
+        }
+        return netId;
     }
 
     /**
@@ -669,6 +938,24 @@
         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");
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index ca4f29f..8971bdd 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import android.net.DhcpInfo;
 import android.os.Parcelable;
 import android.os.Parcel;
 
@@ -294,6 +295,22 @@
      */
     public BitSet allowedGroupCiphers;
 
+    /**
+     * @hide
+     */
+    public enum IpAssignment {
+        STATIC,
+        DHCP,
+        UNASSIGNED
+    }
+    /**
+     * @hide
+     */
+    public IpAssignment ipAssignment;
+    /**
+     * @hide
+     */
+    public DhcpInfo ipConfig;
 
     public WifiConfiguration() {
         networkId = -1;
@@ -312,6 +329,8 @@
         for (EnterpriseField field : enterpriseFields) {
             field.setValue(null);
         }
+        ipAssignment = IpAssignment.UNASSIGNED;
+        ipConfig = new DhcpInfo();
     }
 
     public String toString() {
@@ -393,6 +412,11 @@
             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');
         return sbuf.toString();
     }
 
@@ -461,6 +485,8 @@
         for (int i = 0; i < enterpriseFields.length; i++) {
             config.enterpriseFields[i].setValue(enterpriseFields[i].value());
         }
+        config.ipAssignment = ipAssignment;
+        config.ipConfig = new DhcpInfo(ipConfig);
         return config;
     }
 
@@ -486,6 +512,14 @@
         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);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -512,6 +546,15 @@
                 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();
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 8c3ec5f..4435110 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.WorkSource;
 
 import java.util.List;
 
@@ -306,6 +307,7 @@
      */
     @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
@@ -315,6 +317,14 @@
     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)
@@ -1050,6 +1060,7 @@
         int mLockType;
         private boolean mRefCounted;
         private boolean mHeld;
+        private WorkSource mWorkSource;
 
         private WifiLock(int lockType, String tag) {
             mTag = tag;
@@ -1075,7 +1086,7 @@
             synchronized (mBinder) {
                 if (mRefCounted ? (++mRefCount > 0) : (!mHeld)) {
                     try {
-                        mService.acquireWifiLock(mBinder, mLockType, mTag);
+                        mService.acquireWifiLock(mBinder, mLockType, mTag, mWorkSource);
                         synchronized (WifiManager.this) {
                             if (mActiveLockCount >= MAX_ACTIVE_LOCKS) {
                                 mService.releaseWifiLock(mBinder);
@@ -1147,6 +1158,32 @@
             }
         }
 
+        public void setWorkSource(WorkSource ws) {
+            synchronized (mBinder) {
+                if (ws != null && ws.size() == 0) {
+                    ws = null;
+                }
+                boolean changed = true;
+                if (ws == null) {
+                    mWorkSource = null;
+                } else if (mWorkSource == null) {
+                    changed = mWorkSource != null;
+                    mWorkSource = new WorkSource(ws);
+                } else {
+                    changed = mWorkSource.diff(ws);
+                    if (changed) {
+                        mWorkSource.set(ws);
+                    }
+                }
+                if (changed && mHeld) {
+                    try {
+                        mService.updateWifiLockWorkSource(mBinder, mWorkSource);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+
         public String toString() {
             String s1, s2, s3;
             synchronized (mBinder) {
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index e1cbf5b..075cca4 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -123,10 +123,8 @@
     private int mLastNetworkId;
     private boolean mEnableRssiPolling = false;
     private boolean mPasswordKeyMayBeIncorrect = false;
-    private boolean mUseStaticIp = false;
     private int mReconnectCount = 0;
     private boolean mIsScanMode = false;
-    private boolean mConfigChanged = false;
 
     /**
      * Instance of the bluetooth headset helper. This needs to be created
@@ -138,10 +136,6 @@
 
     private BluetoothA2dp mBluetoothA2dp;
 
-    /**
-     * Observes the static IP address settings.
-     */
-    private SettingsObserver mSettingsObserver;
     private LinkProperties mLinkProperties;
 
     // Held during driver load and unload
@@ -447,8 +441,6 @@
                 }
         };
 
-        mSettingsObserver = new SettingsObserver(new Handler());
-
         PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
         sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
@@ -596,7 +588,9 @@
     }
 
     public DhcpInfo syncGetDhcpInfo() {
-        return mDhcpInfo;
+        synchronized (mDhcpInfo) {
+            return new DhcpInfo(mDhcpInfo);
+        }
     }
 
     /**
@@ -909,10 +903,8 @@
         sb.append("mEnableAllNetworks ").append(mEnableAllNetworks).append(LS);
         sb.append("mEnableRssiPolling ").append(mEnableRssiPolling).append(LS);
         sb.append("mPasswordKeyMayBeIncorrect ").append(mPasswordKeyMayBeIncorrect).append(LS);
-        sb.append("mUseStaticIp ").append(mUseStaticIp).append(LS);
         sb.append("mReconnectCount ").append(mReconnectCount).append(LS);
         sb.append("mIsScanMode ").append(mIsScanMode).append(LS);
-        sb.append("mConfigChanged ").append(mConfigChanged).append(LS).append(LS);
         sb.append("Supplicant status").append(LS)
                 .append(WifiNative.statusCommand()).append(LS).append(LS);
 
@@ -1196,140 +1188,21 @@
             return;
         }
         // TODO - fix this for v6
-        mLinkProperties.addAddress(NetworkUtils.intToInetAddress(mDhcpInfo.ipAddress));
-        mLinkProperties.setGateway(NetworkUtils.intToInetAddress(mDhcpInfo.gateway));
-        mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns1));
-        mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns2));
+        synchronized (mDhcpInfo) {
+            mLinkProperties.addAddress(NetworkUtils.intToInetAddress(mDhcpInfo.ipAddress));
+            mLinkProperties.setGateway(NetworkUtils.intToInetAddress(mDhcpInfo.gateway));
+            mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns1));
+            mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns2));
+        }
         // TODO - add proxy info
     }
 
-
-    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;
-    }
-
-    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);
-        }
-    }
-
     private int getMaxDhcpRetries() {
         return Settings.Secure.getInt(mContext.getContentResolver(),
                                       Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT,
                                       DEFAULT_MAX_DHCP_RETRIES);
     }
 
-    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);
-        }
-
-        @Override
-        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) {
-                sendMessage(CMD_RECONFIGURE_IP);
-                mConfigChanged = true;
-            }
-        }
-    }
-
     /**
      * Whether to disable coexistence mode while obtaining IP address. This
      * logic will return true only if the current bluetooth
@@ -1382,6 +1255,7 @@
         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);
@@ -1969,7 +1843,6 @@
                     }
                     checkIsBluetoothPlaying();
 
-                    checkUseStaticIp();
                     sendSupplicantConnectionChangedBroadcast(true);
                     transitionTo(mDriverSupReadyState);
                     break;
@@ -2477,8 +2350,9 @@
     }
 
     class ConnectingState extends HierarchicalState {
-        boolean modifiedBluetoothCoexistenceMode;
-        int powerMode;
+        boolean mModifiedBluetoothCoexistenceMode;
+        int mPowerMode;
+        boolean mUseStaticIp;
         Thread mDhcpThread;
 
         @Override
@@ -2486,11 +2360,11 @@
             if (DBG) Log.d(TAG, getName() + "\n");
             EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
 
+            mUseStaticIp = WifiConfigStore.isUsingStaticIp(mLastNetworkId);
             if (!mUseStaticIp) {
-
                 mDhcpThread = null;
-                modifiedBluetoothCoexistenceMode = false;
-                powerMode = DRIVER_POWER_MODE_AUTO;
+                mModifiedBluetoothCoexistenceMode = false;
+                mPowerMode = DRIVER_POWER_MODE_AUTO;
 
                 if (shouldDisableCoexistenceMode()) {
                     /*
@@ -2509,28 +2383,32 @@
                      * are currently connected to a headset, since disabling
                      * coexistence would interrupt that connection.
                      */
-                    modifiedBluetoothCoexistenceMode = true;
+                    mModifiedBluetoothCoexistenceMode = true;
 
                     // Disable the coexistence mode
                     WifiNative.setBluetoothCoexistenceModeCommand(
                             WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
                 }
 
-                powerMode =  WifiNative.getPowerModeCommand();
-                if (powerMode < 0) {
+                mPowerMode =  WifiNative.getPowerModeCommand();
+                if (mPowerMode < 0) {
                   // Handle the case where supplicant driver does not support
                   // getPowerModeCommand.
-                    powerMode = DRIVER_POWER_MODE_AUTO;
+                    mPowerMode = DRIVER_POWER_MODE_AUTO;
                 }
-                if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
+                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() {
-                        if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {
+                        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: " +
@@ -2541,8 +2419,12 @@
                 });
                 mDhcpThread.start();
             } else {
-                if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) {
+                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");
@@ -2558,18 +2440,15 @@
               case CMD_IP_CONFIG_SUCCESS:
                   mReconnectCount = 0;
                   mLastSignalLevel = -1; // force update of signal strength
-                  mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);
-                  Log.d(TAG, "IP configuration: " + mDhcpInfo);
+                  synchronized (mDhcpInfo) {
+                      mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);
+                      Log.d(TAG, "IP configuration: " + mDhcpInfo);
+                  }
                   configureLinkProperties();
                   setDetailedState(DetailedState.CONNECTED);
                   sendNetworkStateChangeBroadcast(mLastBssid);
-                  //TODO: we could also detect an IP config change
-                  // from a DHCP renewal and send out a config change
-                  // broadcast
-                  if (mConfigChanged) {
-                      sendConfigChangeBroadcast();
-                      mConfigChanged = false;
-                  }
+                  //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:
@@ -2631,11 +2510,11 @@
       public void exit() {
           /* reset power state & bluetooth coexistence if on DHCP */
           if (!mUseStaticIp) {
-              if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
-                  WifiNative.setPowerModeCommand(powerMode);
+              if (mPowerMode != DRIVER_POWER_MODE_ACTIVE) {
+                  WifiNative.setPowerModeCommand(mPowerMode);
               }
 
-              if (modifiedBluetoothCoexistenceMode) {
+              if (mModifiedBluetoothCoexistenceMode) {
                   // Set the coexistence mode back to its default value
                   WifiNative.setBluetoothCoexistenceModeCommand(
                           WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 9cc44b5..f8c9bd6 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -23,6 +23,7 @@
 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;
@@ -48,6 +49,7 @@
     private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
 
     private LinkProperties mLinkProperties;
+    private LinkCapabilities mLinkCapabilities;
     private NetworkInfo mNetworkInfo;
 
     /* For sending events to connectivity service handler */
@@ -59,9 +61,9 @@
     public WifiStateTracker() {
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
         mLinkProperties = new LinkProperties();
+        mLinkCapabilities = new LinkCapabilities();
 
         mNetworkInfo.setIsAvailable(false);
-        mLinkProperties.clear();
         setTeardownRequested(false);
     }
 
@@ -196,6 +198,16 @@
     }
 
     /**
+     * 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);
+    }
+
+    /**
      * Fetch default gateway address for the network
      */
     public int getDefaultGatewayAddr() {
@@ -230,8 +242,16 @@
            if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                 mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
                         WifiManager.EXTRA_NETWORK_INFO);
-                mLinkProperties = (LinkProperties) intent.getParcelableExtra(
+                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)) {