Merge "Handle RS reflection name changes."
diff --git a/Android.mk b/Android.mk
index 901f212..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:=8301417
-framework_docs_ADT_CHECKSUM:=27e0de800512f13feae46fb554e6ee2f
-
 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/current.xml b/api/current.xml
index 857fc55..3049bcb 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2792,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"
@@ -17384,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"
@@ -29343,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"
@@ -29350,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"
@@ -29376,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"
@@ -29460,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"
@@ -29472,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"
@@ -29619,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"
@@ -33240,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"
@@ -33339,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"
@@ -39375,7 +39699,7 @@
  value="2743"
  static="true"
  final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </field>
@@ -64214,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"
@@ -64236,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"
@@ -85535,7 +85892,7 @@
  visibility="public"
 >
 </field>
-<field name="mFacing"
+<field name="facing"
  type="int"
  transient="false"
  volatile="false"
@@ -85545,7 +85902,7 @@
  visibility="public"
 >
 </field>
-<field name="mOrientation"
+<field name="orientation"
  type="int"
  transient="false"
  volatile="false"
@@ -142347,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"
@@ -142362,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"
@@ -142405,6 +142792,21 @@
 <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"
@@ -142517,6 +142919,26 @@
  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"
@@ -147962,7 +148384,7 @@
  visibility="public"
 >
 </field>
-<field name="TYPE_MAINDEN_NAME"
+<field name="TYPE_MAIDEN_NAME"
  type="int"
  transient="false"
  volatile="false"
@@ -147973,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"
@@ -148728,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"
@@ -148752,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"
@@ -190008,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"
@@ -190074,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"
@@ -233104,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"
@@ -233119,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"
@@ -234270,6 +234754,17 @@
  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"
@@ -234361,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"
@@ -234401,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/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/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 296b495..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;
@@ -206,7 +251,7 @@
         }
         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 "
@@ -215,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 "
@@ -224,7 +269,7 @@
             }
             fragment.mContainerId = fragment.mFragmentId = containerViewId;
         }
-        
+
         Op op = new Op();
         op.cmd = opcmd;
         op.fragment = fragment;
@@ -234,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;
     }
 
@@ -262,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;
@@ -341,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()");
@@ -405,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) {
@@ -465,10 +534,10 @@
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
                 }
             }
-            
+
             op = op.prev;
         }
-        
+
         if (doStateMove) {
             mManager.moveToState(mManager.mCurState,
                     FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
@@ -479,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/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 a704ec8..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,12 +246,14 @@
     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;
     
@@ -205,7 +271,7 @@
         }
     };
     public FragmentTransaction openTransaction() {
-        return new BackStackEntry(this);
+        return new BackStackRecord(this);
     }
 
     public boolean popBackStack() {
@@ -223,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
@@ -696,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);
@@ -716,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) {
@@ -785,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) {
@@ -801,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 {
@@ -815,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;
                     }
@@ -831,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--;
@@ -844,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));
             }
@@ -856,6 +953,7 @@
                         if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
                         states.get(i).popFromBackStack(i == LAST);
                     }
+                    reportBackStackChanged();
                 }
             });
         }
@@ -1073,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);
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 1f115e8..18602df 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -1083,7 +1083,7 @@
     protected void launchQuerySearch(int actionKey, String actionMsg)  {
         String query = mSearchAutoComplete.getText().toString();
         String action = Intent.ACTION_SEARCH;
-        Intent intent = createIntent(action, null, null, query, null,
+        Intent intent = createIntent(action, null, null, query,
                 actionKey, actionMsg);
         launchIntent(intent);
     }
@@ -1185,11 +1185,6 @@
             // use specific action if supplied, or default action if supplied, or fixed default
             String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
 
-            // some items are display only, or have effect via the cursor respond click reporting.
-            if (SearchManager.INTENT_ACTION_NONE.equals(action)) {
-                return null;
-            }
-
             if (action == null) {
                 action = mSearchable.getSuggestIntentAction();
             }
@@ -1211,14 +1206,10 @@
             }
             Uri dataUri = (data == null) ? null : Uri.parse(data);
 
-            String componentName = getColumnString(
-                    c, SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME);
-
             String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
             String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
 
-            return createIntent(action, dataUri, extraData, query, componentName, actionKey,
-                    actionMsg);
+            return createIntent(action, dataUri, extraData, query, actionKey, actionMsg);
         } catch (RuntimeException e ) {
             int rowNum;
             try {                       // be really paranoid now
@@ -1239,7 +1230,6 @@
      * @param data Intent data, or <code>null</code>.
      * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
      * @param query Intent query, or <code>null</code>.
-     * @param componentName Data for {@link SearchManager#COMPONENT_NAME_KEY} or <code>null</code>.
      * @param actionKey The key code of the action key that was pressed,
      *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
      * @param actionMsg The message for the action key that was pressed,
@@ -1249,7 +1239,7 @@
      * @return The intent.
      */
     private Intent createIntent(String action, Uri data, String extraData, String query,
-            String componentName, int actionKey, String actionMsg) {
+            int actionKey, String actionMsg) {
         // Now build the Intent
         Intent intent = new Intent(action);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 2e9cd96..6715012 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -16,16 +16,12 @@
 
 package android.app;
 
-import android.Manifest;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -40,7 +36,7 @@
 
 /**
  * This class provides access to the system search services.
- * 
+ *
  * <p>In practice, you won't interact with this class directly, as search
  * services are provided through methods in {@link android.app.Activity Activity}
  * and the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
@@ -56,7 +52,7 @@
  * href="{@docRoot}guide/topics/search/index.html">Search</a></strong>.</p>
  * </div>
  */
-public class SearchManager 
+public class SearchManager
         implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
 {
 
@@ -65,20 +61,20 @@
 
     /**
      * This is a shortcut definition for the default menu key to use for invoking search.
-     * 
+     *
      * See Menu.Item.setAlphabeticShortcut() for more information.
      */
     public final static char MENU_KEY = 's';
 
     /**
      * This is a shortcut definition for the default menu key to use for invoking search.
-     * 
+     *
      * See Menu.Item.setAlphabeticShortcut() for more information.
      */
     public final static int MENU_KEYCODE = KeyEvent.KEYCODE_S;
 
     /**
-     * Intent extra data key: Use this key with 
+     * Intent extra data key: Use this key with
      * {@link android.content.Intent#getStringExtra
      *  content.Intent.getStringExtra()}
      * to obtain the query string from Intent.ACTION_SEARCH.
@@ -103,7 +99,7 @@
      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
      * {@link android.content.Intent#getBundleExtra
      *  content.Intent.getBundleExtra()}
-     * to obtain any additional app-specific data that was inserted by the 
+     * to obtain any additional app-specific data that was inserted by the
      * activity that launched the search.
      */
     public final static String APP_DATA = "app_data";
@@ -127,7 +123,7 @@
      * file.
      */
     public final static String ACTION_KEY = "action_key";
-    
+
     /**
      * Intent extra data key: This key will be used for the extra populated by the
      * {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column.
@@ -153,11 +149,19 @@
      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
      * {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
      * to obtain the action message that was defined for a particular search action key and/or
-     * suggestion.  It will be null if the search was launched by typing "enter", touched the the 
-     * "GO" button, or other means not involving any action key. 
+     * suggestion.  It will be null if the search was launched by typing "enter", touched the the
+     * "GO" button, or other means not involving any action key.
      */
     public final static String ACTION_MSG = "action_msg";
-    
+
+    /**
+     * Flag to specify that the entry can be used for query refinement, i.e., the query text
+     * in the search field can be replaced with the text in this entry, when a query refinement
+     * icon is clicked. The suggestion list should show such a clickable icon beside the entry.
+     * <p>Use this flag as a bit-field for {@link #SUGGEST_COLUMN_FLAGS}.
+     */
+    public final static int FLAG_QUERY_REFINEMENT = 1 << 0;
+
     /**
      * Uri path for queried suggestions data.  This is the path that the search manager
      * will use when querying your content provider for suggestions data based on user input
@@ -182,12 +186,12 @@
      * @see #SUGGEST_COLUMN_SHORTCUT_ID
      */
     public final static String SUGGEST_URI_PATH_SHORTCUT = "search_suggest_shortcut";
-    
+
     /**
      * MIME type for shortcut validation.  You'll use this in your suggestions content provider
      * in the getType() function.
      */
-    public final static String SHORTCUT_MIME_TYPE = 
+    public final static String SHORTCUT_MIME_TYPE =
             "vnd.android.cursor.item/vnd.android.search.suggest";
 
     /**
@@ -195,7 +199,7 @@
      */
     public final static String SUGGEST_COLUMN_FORMAT = "suggest_format";
     /**
-     * Column name for suggestions cursor.  <i>Required.</i>  This is the primary line of text that 
+     * Column name for suggestions cursor.  <i>Required.</i>  This is the primary line of text that
      * will be presented to the user as the suggestion.
      */
     public final static String SUGGEST_COLUMN_TEXT_1 = "suggest_text_1";
@@ -227,8 +231,8 @@
      * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
      * </ul>
      *
-     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 
-     * for more information on these schemes. 
+     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
+     * for more information on these schemes.
      */
     public final static String SUGGEST_COLUMN_ICON_1 = "suggest_icon_1";
     /**
@@ -243,8 +247,8 @@
      * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
      * </ul>
      *
-     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)} 
-     * for more information on these schemes. 
+     * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
+     * for more information on these schemes.
      */
     public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
     /**
@@ -276,12 +280,6 @@
      */
     public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
     /**
-     * TODO: Remove
-     *
-     * @hide
-     */
-    public final static String SUGGEST_COLUMN_INTENT_COMPONENT_NAME = "suggest_intent_component";
-    /**
      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
      * this element exists at the given row, then "/" and this value will be appended to the data
      * field in the Intent.  This should only be used if the data field has already been set to an
@@ -289,8 +287,8 @@
      */
     public final static String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id";
     /**
-     * Column name for suggestions cursor.  <i>Required if action is 
-     * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i>  If this 
+     * Column name for suggestions cursor.  <i>Required if action is
+     * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i>  If this
      * column exists <i>and</i> this element exists at the given row, this is the data that will be
      * used when forming the suggestion's query.
      */
@@ -307,15 +305,6 @@
     public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id";
 
     /**
-     * Column name for suggestions cursor. <i>Optional.</i>  This column is used to specify the
-     * cursor item's background color if it needs a non-default background color. A non-zero value
-     * indicates a valid background color to override the default.
-     *
-     * @hide For internal use, not part of the public API.
-     */
-    public final static String SUGGEST_COLUMN_BACKGROUND_COLOR = "suggest_background_color";
-    
-    /**
      * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify
      * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion
      * is being refreshed.
@@ -324,6 +313,15 @@
             "suggest_spinner_while_refreshing";
 
     /**
+     * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify
+     * additional flags per item. Multiple flags can be specified.
+     * <p>
+     * Must be one of {@link #FLAG_QUERY_REFINEMENT} or 0 to indicate no flags.
+     * </p>
+     */
+    public final static String SUGGEST_COLUMN_FLAGS = "suggest_flags";
+
+    /**
      * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion
      * should not be stored as a shortcut in global search.
      */
@@ -343,16 +341,16 @@
      * {@link #EXTRA_SELECT_QUERY},
      * {@link #APP_DATA}.
      */
-    public final static String INTENT_ACTION_GLOBAL_SEARCH 
+    public final static String INTENT_ACTION_GLOBAL_SEARCH
             = "android.search.action.GLOBAL_SEARCH";
-    
+
     /**
      * Intent action for starting the global search settings activity.
      * The global search provider should handle this intent.
      */
-    public final static String INTENT_ACTION_SEARCH_SETTINGS 
+    public final static String INTENT_ACTION_SEARCH_SETTINGS
             = "android.search.action.SEARCH_SETTINGS";
-    
+
     /**
      * Intent action for starting a web search provider's settings activity.
      * Web search providers should handle this intent if they have provider-specific
@@ -368,7 +366,7 @@
      */
     public final static String INTENT_ACTION_SEARCHABLES_CHANGED
             = "android.search.action.SEARCHABLES_CHANGED";
-    
+
     /**
      * Intent action broadcasted to inform that the search settings have changed in some way.
      * Either searchables have been enabled or disabled, or a different web search provider
@@ -378,14 +376,6 @@
             = "android.search.action.SETTINGS_CHANGED";
 
     /**
-     * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
-     * the search dialog will take no action.
-     *
-     * @hide
-     */
-    public final static String INTENT_ACTION_NONE = "android.search.action.ZILCH";
-
-    /**
      * This means that context is voice, and therefore the SearchDialog should
      * continue showing the microphone until the user indicates that he/she does
      * not want to re-speak (e.g. by typing).
@@ -413,7 +403,7 @@
      * The package associated with this seach manager.
      */
     private String mAssociatedPackage;
-    
+
     // package private since they are used by the inner class SearchManagerCallback
     /* package */ final Handler mHandler;
     /* package */ OnDismissListener mDismissListener = null;
@@ -427,15 +417,15 @@
         mService = ISearchManager.Stub.asInterface(
                 ServiceManager.getService(Context.SEARCH_SERVICE));
     }
-    
+
     /**
      * Launch search UI.
      *
      * <p>The search manager will open a search widget in an overlapping
-     * window, and the underlying activity may be obscured.  The search 
+     * window, and the underlying activity may be obscured.  The search
      * entry state will remain in effect until one of the following events:
      * <ul>
-     * <li>The user completes the search.  In most cases this will launch 
+     * <li>The user completes the search.  In most cases this will launch
      * a search intent.</li>
      * <li>The user uses the back, home, or other keys to exit the search.</li>
      * <li>The application calls the {@link #stopSearch}
@@ -443,8 +433,8 @@
      * activity from which it was launched.</li>
      *
      * <p>Most applications will <i>not</i> use this interface to invoke search.
-     * The primary method for invoking search is to call 
-     * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or 
+     * The primary method for invoking search is to call
+     * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or
      * {@link android.app.Activity#startSearch Activity.startSearch()}.
      *
      * @param initialQuery A search string can be pre-entered here, but this
@@ -456,19 +446,19 @@
      * and the user would expect to be able to keep typing.  <i>This parameter is only meaningful
      * if initialQuery is a non-empty string.</i>
      * @param launchActivity The ComponentName of the activity that has launched this search.
-     * @param appSearchData An application can insert application-specific 
-     * context here, in order to improve quality or specificity of its own 
+     * @param appSearchData An application can insert application-specific
+     * context here, in order to improve quality or specificity of its own
      * searches.  This data will be returned with SEARCH intent(s).  Null if
      * no extra data is required.
      * @param globalSearch If false, this will only launch the search that has been specifically
-     * defined by the application (which is usually defined as a local search).  If no default 
+     * defined by the application (which is usually defined as a local search).  If no default
      * search is defined in the current application or activity, global search will be launched.
      * If true, this will always launch a platform-global (e.g. web-based) search instead.
-     * 
+     *
      * @see android.app.Activity#onSearchRequested
      * @see #stopSearch
      */
-    public void startSearch(String initialQuery, 
+    public void startSearch(String initialQuery,
                             boolean selectInitialQuery,
                             ComponentName launchActivity,
                             Bundle appSearchData,
@@ -595,7 +585,7 @@
      * <p>Typically the user will terminate the search UI by launching a
      * search or by canceling.  This function allows the underlying application
      * or activity to cancel the search prematurely (for any reason).
-     * 
+     *
      * <p>This function can be safely called at any time (even if no search is active.)
      *
      * @see #startSearch
@@ -607,12 +597,12 @@
     }
 
     /**
-     * Determine if the Search UI is currently displayed.  
-     * 
+     * Determine if the Search UI is currently displayed.
+     *
      * This is provided primarily for application test purposes.
      *
      * @return Returns true if the search UI is currently displayed.
-     * 
+     *
      * @hide
      */
     public boolean isVisible() {
@@ -631,7 +621,7 @@
          */
         public void onDismiss();
     }
-    
+
     /**
      * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor
      * search UI state.
@@ -647,7 +637,7 @@
 
     /**
      * Set or clear the callback that will be invoked whenever the search UI is dismissed.
-     * 
+     *
      * @param listener The {@link OnDismissListener} to use, or null.
      */
     public void setOnDismissListener(final OnDismissListener listener) {
@@ -656,7 +646,7 @@
 
     /**
      * Set or clear the callback that will be invoked whenever the search UI is canceled.
-     * 
+     *
      * @param listener The {@link OnCancelListener} to use, or null.
      */
     public void setOnCancelListener(OnCancelListener listener) {
@@ -767,10 +757,10 @@
         // finally, make the query
         return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
     }
-     
+
     /**
      * Returns a list of the searchable activities that can be included in global search.
-     * 
+     *
      * @return a list containing searchable information for all searchable activities
      *         that have the <code>android:includeInGlobalSearch</code> attribute set
      *         in their searchable meta-data.
diff --git a/core/java/android/app/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 16a8c57..33fd395 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -465,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}
@@ -488,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:
@@ -611,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
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/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/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 658fc58..bd05e24 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -71,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.
@@ -82,7 +82,15 @@
         synchronized(this) {
             try {
                 long timeStart = acquireAndLock(WRITE);
-                int numChanges = native_execute();
+                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 {
@@ -199,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;
@@ -217,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();
@@ -257,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();
@@ -275,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/DownloadManager.java b/core/java/android/net/DownloadManager.java
index bcee995..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;
@@ -536,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;
+        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) {
@@ -597,6 +598,7 @@
 
     private ContentResolver mResolver;
     private String mPackageName;
+    private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
 
     /**
      * @hide
@@ -607,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.
      *
@@ -642,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);
     }
 
     /**
@@ -690,9 +705,8 @@
     /**
      * 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);
     }
 
     /**
@@ -702,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
@@ -799,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/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 4247ae5..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;
@@ -117,7 +118,6 @@
     // 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 SINGLE_PANE_TAG = ":android:single_pane";
     private static final String PREFERENCES_TAG = ":android:preferences";
 
     /**
@@ -169,6 +169,8 @@
 
     private View mPrefsContainer;
 
+    private FragmentBreadCrumbs mFragmentBreadCrumbs;
+
     private boolean mSinglePane;
 
     private Header mCurHeader;
@@ -300,6 +302,18 @@
         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
          */
@@ -341,6 +355,8 @@
             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);
@@ -357,6 +373,8 @@
             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();
@@ -405,7 +423,6 @@
                     setSelectedHeader(mHeaders.get(curHeader));
                 }
             }
-            mSinglePane = savedInstanceState.getBoolean(SINGLE_PANE_TAG);
 
         } else {
             if (initialFragment != null && mSinglePane) {
@@ -537,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;
         }
@@ -649,6 +670,10 @@
                             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(
@@ -741,7 +766,6 @@
                 }
             }
         }
-        outState.putBoolean(SINGLE_PANE_TAG, mSinglePane);
 
         if (mPreferenceManager != null) {
             final PreferenceScreen preferenceScreen = getPreferenceScreen();
@@ -837,6 +861,20 @@
         startActivity(intent);
     }
 
+    /**
+     * Change the base title of the bread crumbs for the current preferences.
+     * This will normally be called for you.  See
+     * {@link android.app.FragmentBreadCrumbs} for more information.
+     */
+    public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
+        if (mFragmentBreadCrumbs == null) {
+            mFragmentBreadCrumbs = new FragmentBreadCrumbs(this);
+            mFragmentBreadCrumbs.setActivity(this);
+            getActionBar().setCustomNavigationMode(mFragmentBreadCrumbs);
+        }
+        mFragmentBreadCrumbs.setTitle(title, shortTitle);
+    }
+
     void setSelectedHeader(Header header) {
         mCurHeader = header;
         int index = mHeaders.indexOf(header);
@@ -845,6 +883,21 @@
         } 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();
     }
 
     /**
@@ -856,12 +909,7 @@
      */
     public void switchToHeader(String fragmentName, Bundle args) {
         setSelectedHeader(null);
-
-        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();
+        switchToHeaderInner(fragmentName, args);
     }
 
     /**
@@ -871,8 +919,7 @@
      * @param header The new header to display.
      */
     public void switchToHeader(Header header) {
-        switchToHeader(header.fragment, header.fragmentArguments);
-        mCurHeader = header;
+        switchToHeaderInner(header.fragment, header.fragmentArguments);
         setSelectedHeader(header);
     }
 
@@ -930,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/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/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 603e598..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
diff --git a/core/java/android/util/CalendarUtils.java b/core/java/android/util/CalendarUtils.java
index 9a4a67d..3d340d9 100644
--- a/core/java/android/util/CalendarUtils.java
+++ b/core/java/android/util/CalendarUtils.java
@@ -110,6 +110,7 @@
                             }
                         }
                     }
+                    cursor.close();
                     if (writePrefs) {
                         SharedPreferences prefs = getSharedPreferences((Context)cookie, mPrefsName);
                         // Write the prefs
@@ -205,11 +206,11 @@
 
                 // Update the db
                 ContentValues values = new ContentValues();
-                if (mHandler == null) {
-                    mHandler = new AsyncTZHandler(context.getContentResolver());
+                if (mHandler != null) {
+                    mHandler.cancelOperation(mToken);
                 }
 
-                mHandler.cancelOperation(mToken);
+                mHandler = new AsyncTZHandler(context.getContentResolver());
 
                 // skip 0 so query can use it
                 if (++mToken == 0) {
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/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/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 d04007c..ac65e09 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5286,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.
@@ -5318,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.
@@ -5427,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() {
@@ -8743,7 +8745,7 @@
             mPrivateFlags = (mPrivateFlags & ~ACTIVATED) | (activated ? ACTIVATED : 0);
             invalidate();
             refreshDrawableState();
-            dispatchSetSelected(activated);
+            dispatchSetActivated(activated);
         }
     }
 
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 5d4ae6d..c62894c 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -667,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
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 56d6296..acea163 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1705,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;
             }
@@ -1780,6 +1785,7 @@
         int mScrollX;
         int mScrollY;
         boolean mMobileSite;
+        int mViewportWidth;
     }
 
     static class DrawData {
@@ -1821,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");
@@ -2154,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 f3dfb77..bb13f1d 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -4365,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/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 57dc725..09217af 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -66,6 +66,7 @@
     private AutoCompleteTextView mQueryTextView;
     private boolean mSubmitButtonEnabled;
     private CharSequence mQueryHint;
+    private boolean mQueryRefinement;
 
     private SearchableInfo mSearchable;
 
@@ -282,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;
     }
 
     /**
@@ -333,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) {
@@ -403,6 +439,9 @@
             mSuggestionsAdapter = new SuggestionsAdapter(getContext(),
                     this, mSearchable, mOutsideDrawablesCache);
             mQueryTextView.setAdapter(mSuggestionsAdapter);
+            ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
+                    mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
+                    : SuggestionsAdapter.REFINE_BY_ENTRY);
         }
     }
 
@@ -555,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);
     }
 
@@ -571,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,
@@ -581,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);
@@ -624,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();
             }
@@ -650,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 2fb2d6d..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;
         }
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/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_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_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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4d02b60..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" />
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/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/preference_header_item.xml b/core/res/res/layout/preference_header_item.xml
index 80a3ac2..aba7b2ad 100644
--- a/core/res/res/layout/preference_header_item.xml
+++ b/core/res/res/layout/preference_header_item.xml
@@ -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/attrs.xml b/core/res/res/values/attrs.xml
index b3d39f3..2dec5c5 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3561,6 +3561,10 @@
         <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/public.xml b/core/res/res/values/public.xml
index 3b69acb9..fbee438 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1352,6 +1352,8 @@
   <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" />
@@ -1390,6 +1392,7 @@
   <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" />
 
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 9ba0131..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>
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/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 3d4b6a12..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
 
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/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/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/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/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/GraphicLog.h b/include/ui/GraphicLog.h
index f929e6a..ee1b09a 100644
--- a/include/ui/GraphicLog.h
+++ b/include/ui/GraphicLog.h
@@ -30,24 +30,24 @@
 
 public:
     enum {
-        SF_APP_DEQUEUE_BEFORE   = 60000,
-        SF_APP_DEQUEUE_AFTER    = 60001,
-        SF_APP_LOCK_BEFORE      = 60002,
-        SF_APP_LOCK_AFTER       = 60003,
-        SF_APP_QUEUE            = 60004,
+        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              = 60005,
-        SF_COMPOSITION_COMPLETE = 60006,
-        SF_UNLOCK_CLIENTS       = 60007,
-        SF_SWAP_BUFFERS         = 60008,
-        SF_REPAINT_DONE         = 60009,
+        SF_REPAINT              = 60105,
+        SF_COMPOSITION_COMPLETE = 60106,
+        SF_UNLOCK_CLIENTS       = 60107,
+        SF_SWAP_BUFFERS         = 60108,
+        SF_REPAINT_DONE         = 60109,
 
-        SF_FB_POST_BEFORE       = 60010,
-        SF_FB_POST_AFTER        = 60011,
-        SF_FB_DEQUEUE_BEFORE    = 60012,
-        SF_FB_DEQUEUE_AFTER     = 60013,
-        SF_FB_LOCK_BEFORE       = 60014,
-        SF_FB_LOCK_AFTER        = 60015,
+        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) {
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index d7e6254..96b4faed 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -81,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
@@ -109,12 +108,6 @@
     // Flags for the input target.
     int32_t flags;
 
-    // The timeout for event delivery to this target in nanoseconds, or -1 to wait indefinitely.
-    nsecs_t timeout;
-
-    // The time already spent waiting for this target in nanoseconds, or 0 if none.
-    nsecs_t timeSpentWaitingForApplication;
-
     // The x and y offset to add to a MotionEvent as it is delivered.
     // (ignored for KeyEvents)
     float xOffset, yOffset;
@@ -190,6 +183,7 @@
     };
 
     sp<InputChannel> inputChannel;
+    String8 name;
     int32_t layoutParamsFlags;
     int32_t layoutParamsType;
     nsecs_t dispatchingTimeout;
@@ -206,9 +200,11 @@
     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;
 
@@ -257,18 +253,12 @@
 
     /* 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) = 0;
+    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 a new timeout to continue waiting, or 0 to abort dispatch. */
-    virtual nsecs_t notifyInputChannelANR(const sp<InputChannel>& inputChannel) = 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;
 
@@ -361,16 +351,6 @@
      */
     virtual void setInputDispatchMode(bool enabled, bool frozen) = 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.
-     *
-     * This method may be called on any thread (usually by the input manager).
-     */
-    virtual void preemptInputDispatch() = 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.
      *
@@ -424,7 +404,6 @@
     virtual void setInputWindows(const Vector<InputWindow>& inputWindows);
     virtual void setFocusedApplication(const InputApplication* inputApplication);
     virtual void setInputDispatchMode(bool enabled, bool frozen);
-    virtual void preemptInputDispatch();
 
     virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor);
     virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
@@ -454,7 +433,7 @@
         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; }
 
@@ -522,7 +501,6 @@
         int32_t targetFlags;
         float xOffset;
         float yOffset;
-        nsecs_t timeout;
 
         // True if dispatch has started.
         bool inProgress;
@@ -540,12 +518,8 @@
         //   will be set to NULL.
         MotionSample* tailMotionSample;
 
-        inline bool isSyncTarget() const {
-            return targetFlags & InputTarget::FLAG_SYNC;
-        }
-
-        inline void preemptSyncTarget() {
-            targetFlags &= ~ InputTarget::FLAG_SYNC;
+        inline bool hasForegroundTarget() const {
+            return targetFlags & InputTarget::FLAG_FOREGROUND;
         }
     };
 
@@ -628,6 +602,8 @@
             dequeue(first);
             return first;
         }
+
+        uint32_t count() const;
     };
 
     /* Allocates queue entries and performs reference counting as needed. */
@@ -647,7 +623,7 @@
                 nsecs_t downTime, uint32_t pointerCount,
                 const int32_t* pointerIds, const PointerCoords* pointerCoords);
         DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry,
-                int32_t targetFlags, float xOffset, float yOffset, nsecs_t timeout);
+                int32_t targetFlags, float xOffset, float yOffset);
         CommandEntry* obtainCommandEntry(Command command);
 
         void releaseEventEntry(EventEntry* entry);
@@ -761,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
         };
@@ -772,11 +746,9 @@
         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);
 
@@ -788,18 +760,6 @@
         // 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() const {
-            return ! outboundQueue.isEmpty() && outboundQueue.tailSentinel.prev->isSyncTarget();
-        }
-
-        // Assuming there is a pending sync target, make it async.
-        inline void preemptSyncTarget() {
-            outboundQueue.tailSentinel.prev->preemptSyncTarget();
-        }
-
         // Gets the time since the current event was originally obtained from the input driver.
         inline double getEventLatencyMillis(nsecs_t currentTime) const {
             return (currentTime - lastEventTime) / 1000000.0;
@@ -810,15 +770,7 @@
             return (currentTime - lastDispatchTime) / 1000000.0;
         }
 
-        // Gets the time since the current event ANR was declared, if applicable.
-        inline double getANRLatencyMillis(nsecs_t currentTime) const {
-            return (currentTime - lastANRTime) / 1000000.0;
-        }
-
         status_t initialize();
-
-        void setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout);
-        void resetTimeout(nsecs_t currentTime);
     };
 
     sp<InputDispatcherPolicyInterface> mPolicy;
@@ -851,7 +803,7 @@
     // 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
@@ -859,12 +811,6 @@
     // 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;
 
@@ -877,7 +823,7 @@
     void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
 
     Condition mInjectionSyncFinishedCondition;
-    void decrementPendingSyncDispatchesLocked(EventEntry* entry);
+    void decrementPendingForegroundDispatchesLocked(EventEntry* entry);
 
     // Throttling state.
     struct ThrottleState {
@@ -905,8 +851,8 @@
 
     // Inbound event processing.
     void drainInboundQueueLocked();
-    void releasePendingEventLocked(bool wasDropped);
-    void releaseInboundEventLocked(EventEntry* entry, bool wasDropped);
+    void releasePendingEventLocked();
+    void releaseInboundEventLocked(EventEntry* entry);
     bool isEventFromReliableSourceLocked(EventEntry* entry);
 
     // Dispatch state.
@@ -940,10 +886,10 @@
             nsecs_t currentTime, ConfigurationChangedEntry* entry);
     bool dispatchKeyLocked(
             nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
-            nsecs_t* nextWakeupTime);
+            bool dropEvent, nsecs_t* nextWakeupTime);
     bool dispatchMotionLocked(
             nsecs_t currentTime, MotionEntry* entry,
-            nsecs_t* nextWakeupTime);
+            bool dropEvent, nsecs_t* nextWakeupTime);
     void dispatchEventToCurrentInputTargetsLocked(
             nsecs_t currentTime, EventEntry* entry, bool resumeWithAppendedMotionSample);
 
@@ -951,8 +897,6 @@
     void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry);
 
     // 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.
     bool mCurrentInputTargetsValid; // false while targets are being recomputed
     Vector<InputTarget> mCurrentInputTargets;
     int32_t mCurrentInputWindowType;
@@ -970,13 +914,14 @@
     bool mInputTargetWaitTimeoutExpired;
 
     // Finding targets for input events.
-    void startFindingTargetsLocked();
-    void finishFindingTargetsLocked(const InputWindow* window);
+    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);
-    nsecs_t getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(nsecs_t currentTime);
+    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,
@@ -984,14 +929,16 @@
     int32_t findTouchedWindowLocked(nsecs_t currentTime, const MotionEntry* entry,
             nsecs_t* nextWakeupTime, InputWindow** outWindow);
 
-    void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
-            nsecs_t timeSpentWaitingForApplication);
+    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
@@ -1000,21 +947,14 @@
     void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
             EventEntry* eventEntry, const InputTarget* inputTarget,
             bool resumeWithAppendedMotionSample);
-    void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
-            nsecs_t timeSpentWaitingForApplication);
+    void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
     void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
     void startNextDispatchCycleLocked(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 abortDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
             bool broken);
-    void drainOutboundQueueLocked(Connection* connection, DispatchEntry* firstDispatchEntryToDrain);
+    void drainOutboundQueueLocked(Connection* connection);
     static int handleReceiveCallback(int receiveFd, int events, void* data);
 
-    // Preempting input dispatch.
-    bool preemptInputDispatchInnerLocked();
-
     // Dump state.
     void dumpDispatchStateLocked(String8& dump);
     void logDispatchStateLocked();
@@ -1027,20 +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);
-    void doTargetsNotReadyTimeoutLockedInterruptible(CommandEntry* commandEntry);
+
+    // Statistics gathering.
+    void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+            int32_t injectionResult, nsecs_t timeSpentWaitingForApplication);
 };
 
 /* Enqueues and dispatches input events, endlessly. */
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 c3a9f22..f935524 100644
--- a/libs/gui/SensorEventQueue.cpp
+++ b/libs/gui/SensorEventQueue.cpp
@@ -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
@@ -95,8 +99,18 @@
 {
     const int fd = getFd();
     sp<Looper> looper(getLooper());
-    int32_t result = looper->pollOnce(-1);
-    return (result == fd) ? status_t(NO_ERROR) : status_t(-1);
+
+    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
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 b1f5f6b..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
 ///////////////////////////////////////////////////////////////////////////////
@@ -124,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() {
@@ -161,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()) {
@@ -342,7 +356,12 @@
 
     // Layers only make sense if they are in the framebuffer's bounds
     bounds.intersect(*mSnapshot->clipRect);
-    if (bounds.isEmpty()) return false;
+    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);
@@ -399,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);
 
@@ -572,15 +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);
-    mesh->dump();
 
     // 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) {
@@ -778,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;
@@ -812,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) {
@@ -956,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();
 
@@ -1010,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 3126754..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);
@@ -229,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);
 
     /**
@@ -289,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
@@ -395,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/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 5327aac..3dbdbfb 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -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;
 }
 
@@ -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/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 3174e82..9817fca 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -103,13 +103,8 @@
     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;
@@ -131,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");
         }
 
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 28084d7..6446b55 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -68,11 +68,6 @@
     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);
 }
diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h
index d6b3f5a..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);
 
 
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index 47b8a61..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,
@@ -77,13 +77,17 @@
         GL_CLAMP_TO_EDGE, //RS_SAMPLER_CLAMP
     };
 
-    if (!rsc->ext_OES_texture_npot() && npot) {
+    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]);
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/ui/GraphicLog.cpp b/libs/ui/GraphicLog.cpp
index b55ce23..7ba2779 100644
--- a/libs/ui/GraphicLog.cpp
+++ b/libs/ui/GraphicLog.cpp
@@ -30,7 +30,11 @@
 
 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);
@@ -38,7 +42,11 @@
 
 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);
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index 48dea57..1cf7592 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -122,7 +122,7 @@
         AutoMutex _l(mLock);
 
         resetKeyRepeatLocked();
-        releasePendingEventLocked(true);
+        releasePendingEventLocked();
         drainInboundQueueLocked();
     }
 
@@ -174,7 +174,7 @@
     if (! mDispatchEnabled) {
         if (mPendingEvent || ! mInboundQueue.isEmpty()) {
             LOGI("Dropping pending events because input dispatch is disabled.");
-            releasePendingEventLocked(true);
+            releasePendingEventLocked();
             drainInboundQueueLocked();
         }
         return;
@@ -196,42 +196,6 @@
         *nextWakeupTime = mAppSwitchDueTime;
     }
 
-    // Detect and process timeouts for all connections and determine if there are any
-    // synchronous event dispatches pending.  This step is entirely non-interruptible.
-    bool havePendingSyncTarget = false;
-    size_t activeConnectionCount = mActiveConnections.size();
-    for (size_t i = 0; i < activeConnectionCount; i++) {
-        Connection* connection = mActiveConnections.itemAt(i);
-
-        if (connection->hasPendingSyncTarget()) {
-            if (isAppSwitchDue) {
-                connection->preemptSyncTarget();
-            } else {
-                havePendingSyncTarget = 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);
-        *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
-    }
-    mTimedOutConnections.clear();
-
-    // If we have a pending synchronous target, skip dispatch.
-    if (havePendingSyncTarget) {
-        return;
-    }
-
     // Ready to start a new event.
     // If we don't already have a pending event, go grab one.
     if (! mPendingEvent) {
@@ -317,51 +281,50 @@
 
     // Now we have an event to dispatch.
     assert(mPendingEvent != NULL);
-    bool wasDispatched = false;
-    bool wasDropped = false;
+    bool done = false;
     switch (mPendingEvent->type) {
     case EventEntry::TYPE_CONFIGURATION_CHANGED: {
         ConfigurationChangedEntry* typedEntry =
                 static_cast<ConfigurationChangedEntry*>(mPendingEvent);
-        wasDispatched = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+        done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
         break;
     }
 
     case EventEntry::TYPE_KEY: {
         KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
-        if (isAppSwitchPendingLocked()) {
-            if (isAppSwitchKey(typedEntry->keyCode)) {
+        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);
-            } else if (isAppSwitchDue) {
-                LOGI("Dropping key because of pending overdue app switch.");
-                wasDropped = true;
-                break;
             }
         }
-        wasDispatched = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout,
-                nextWakeupTime);
         break;
     }
 
     case EventEntry::TYPE_MOTION: {
         MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
-        if (isAppSwitchDue) {
-            LOGI("Dropping motion because of pending overdue app switch.");
-            wasDropped = true;
-            break;
+        bool dropEvent = isAppSwitchDue;
+        done = dispatchMotionLocked(currentTime, typedEntry, dropEvent, nextWakeupTime);
+        if (done) {
+            if (dropEvent) {
+                LOGI("Dropped motion because of pending overdue app switch.");
+            }
         }
-        wasDispatched = dispatchMotionLocked(currentTime, typedEntry, nextWakeupTime);
         break;
     }
 
     default:
         assert(false);
-        wasDropped = true;
         break;
     }
 
-    if (wasDispatched || wasDropped) {
-        releasePendingEventLocked(wasDropped);
+    if (done) {
+        releasePendingEventLocked();
         *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
     }
 }
@@ -439,21 +402,21 @@
 void InputDispatcher::drainInboundQueueLocked() {
     while (! mInboundQueue.isEmpty()) {
         EventEntry* entry = mInboundQueue.dequeueAtHead();
-        releaseInboundEventLocked(entry, true /*wasDropped*/);
+        releaseInboundEventLocked(entry);
     }
 }
 
-void InputDispatcher::releasePendingEventLocked(bool wasDropped) {
+void InputDispatcher::releasePendingEventLocked() {
     if (mPendingEvent) {
-        releaseInboundEventLocked(mPendingEvent, wasDropped);
+        releaseInboundEventLocked(mPendingEvent);
         mPendingEvent = NULL;
     }
 }
 
-void InputDispatcher::releaseInboundEventLocked(EventEntry* entry, bool wasDropped) {
-    if (wasDropped) {
+void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) {
+    if (entry->injectionResult == INPUT_EVENT_INJECTION_PENDING) {
 #if DEBUG_DISPATCH_CYCLE
-        LOGD("Pending event was dropped.");
+        LOGD("Inbound event was dropped.  Setting injection result to failed.");
 #endif
         setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
     }
@@ -528,7 +491,41 @@
 
 bool InputDispatcher::dispatchKeyLocked(
         nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
-        nsecs_t* nextWakeupTime) {
+        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);
@@ -557,7 +554,7 @@
         }
 
         entry->dispatchInProgress = true;
-        startFindingTargetsLocked();
+        resetTargetsLocked();
     }
 
     // Identify targets.
@@ -575,20 +572,7 @@
         }
 
         addMonitoringTargetsLocked();
-        finishFindingTargetsLocked(window);
-    }
-
-    // Give the policy a chance to intercept the key.
-    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
-        CommandEntry* commandEntry = postCommandLocked(
-                & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
-        commandEntry->inputChannel = mCurrentInputChannel;
-        commandEntry->keyEntry = entry;
-        entry->refCount += 1;
-        return false; // wait for the command to run
-    }
-    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
-        return true;
+        commitTargetsLocked(window);
     }
 
     // Dispatch the key.
@@ -612,13 +596,20 @@
 }
 
 bool InputDispatcher::dispatchMotionLocked(
-        nsecs_t currentTime, MotionEntry* entry, nsecs_t* nextWakeupTime) {
+        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;
-        startFindingTargetsLocked();
+        resetTargetsLocked();
     }
 
     bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
@@ -646,7 +637,7 @@
         }
 
         addMonitoringTargetsLocked();
-        finishFindingTargetsLocked(window);
+        commitTargetsLocked(window);
     }
 
     // Dispatch the motion.
@@ -728,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,
@@ -741,14 +732,14 @@
     }
 }
 
-void InputDispatcher::startFindingTargetsLocked() {
+void InputDispatcher::resetTargetsLocked() {
     mCurrentInputTargetsValid = false;
     mCurrentInputTargets.clear();
     mCurrentInputChannel.clear();
     mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
 }
 
-void InputDispatcher::finishFindingTargetsLocked(const InputWindow* window) {
+void InputDispatcher::commitTargetsLocked(const InputWindow* window) {
     mCurrentInputWindowType = window->layoutParamsType;
     mCurrentInputChannel = window->inputChannel;
     mCurrentInputTargetsValid = true;
@@ -770,9 +761,8 @@
     } else {
         if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
 #if DEBUG_FOCUS
-            LOGD("Waiting for application to become ready for input: name=%s, window=%s",
-                    application ? application->name.string() : "<unknown>",
-                    window ? window->inputChannel->getName().string() : "<unknown>");
+            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;
@@ -789,21 +779,7 @@
     }
 
     if (currentTime >= mInputTargetWaitTimeoutTime) {
-        LOGI("Application is not ready for input: name=%s, window=%s,"
-                "%01.1fms since event, %01.1fms since wait started",
-                application ? application->name.string() : "<unknown>",
-                window ? window->inputChannel->getName().string() : "<unknown>",
-                (currentTime - entry->eventTime) / 1000000.0,
-                (currentTime - mInputTargetWaitStartTime) / 1000000.0);
-
-        CommandEntry* commandEntry = postCommandLocked(
-                & InputDispatcher::doTargetsNotReadyTimeoutLockedInterruptible);
-        if (application) {
-            commandEntry->inputApplicationHandle = application->handle;
-        }
-        if (window) {
-            commandEntry->inputChannel = window->inputChannel;
-        }
+        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.
@@ -818,17 +794,30 @@
     }
 }
 
-void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout) {
+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::getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(
+nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationLocked(
         nsecs_t currentTime) {
     if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
         return currentTime - mInputTargetWaitStartTime;
@@ -841,13 +830,6 @@
         LOGD("Resetting ANR timeouts.");
 #endif
 
-    // Reset timeouts for all active connections.
-    nsecs_t currentTime = now();
-    for (size_t i = 0; i < mActiveConnections.size(); i++) {
-        Connection* connection = mActiveConnections[i];
-        connection->resetTimeout(currentTime);
-    }
-
     // Reset input target wait timeout.
     mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
 }
@@ -865,8 +847,8 @@
         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'.",
-                    mFocusedApplication->name.string());
+                    "focused application that may eventually add a window: %s.",
+                    getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
 #endif
             injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                     mFocusedApplication, NULL, nextWakeupTime);
@@ -894,19 +876,31 @@
         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_SYNC,
-            getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(currentTime));
+    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",
-            injectionResult);
-    logDispatchStateLocked();
+    LOGD("findFocusedWindow finished: injectionResult=%d, "
+            "timeSpendWaitingForApplication=%0.1fms",
+            injectionResult, timeSpentWaitingForApplication / 1000000.0);
 #endif
     return injectionResult;
 }
@@ -1018,8 +1012,8 @@
             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'.",
-                        mFocusedApplication->name.string());
+                        "focused application that may eventually add a new window: %s.",
+                        getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
 #endif
                 injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                         mFocusedApplication, NULL, nextWakeupTime);
@@ -1051,6 +1045,17 @@
             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();
 
@@ -1098,6 +1103,17 @@
             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.
@@ -1108,7 +1124,7 @@
         size_t numWallpaperWindows = mTouchedWallpaperWindows.size();
         for (size_t i = 0; i < numWallpaperWindows; i++) {
             addWindowTargetLocked(mTouchedWallpaperWindows[i],
-                    InputTarget::FLAG_WINDOW_IS_OBSCURED, 0);
+                    InputTarget::FLAG_WINDOW_IS_OBSCURED);
         }
 
         size_t numOutsideTargets = mTempTouchedOutsideTargets.size();
@@ -1118,16 +1134,15 @@
             if (outsideTarget.obscured) {
                 outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
             }
-            addWindowTargetLocked(outsideTarget.window, outsideTargetFlags, 0);
+            addWindowTargetLocked(outsideTarget.window, outsideTargetFlags);
         }
         mTempTouchedOutsideTargets.clear();
 
-        int32_t targetFlags = InputTarget::FLAG_SYNC;
+        int32_t targetFlags = InputTarget::FLAG_FOREGROUND;
         if (mTouchedWindowIsObscured) {
             targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
         }
-        addWindowTargetLocked(mTouchedWindow, targetFlags,
-                getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(currentTime));
+        addWindowTargetLocked(mTouchedWindow, targetFlags);
         *outWindow = mTouchedWindow;
     }
 
@@ -1166,10 +1181,13 @@
     }
 
 Unresponsive:
+    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+    updateDispatchStatisticsLocked(currentTime, entry,
+            injectionResult, timeSpentWaitingForApplication);
 #if DEBUG_FOCUS
-    LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d",
-            injectionResult, injectionPermission);
-    logDispatchStateLocked();
+    LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d,"
+            "timeSpendWaitingForApplication=%0.1fms",
+            injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0);
 #endif
     return injectionResult;
 }
@@ -1180,15 +1198,12 @@
     mTouchedWallpaperWindows.clear();
 }
 
-void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
-        nsecs_t timeSpentWaitingForApplication) {
+void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags) {
     mCurrentInputTargets.push();
 
     InputTarget& target = mCurrentInputTargets.editTop();
     target.inputChannel = window->inputChannel;
     target.flags = targetFlags;
-    target.timeout = window->dispatchingTimeout;
-    target.timeSpentWaitingForApplication = timeSpentWaitingForApplication;
     target.xOffset = - window->frameLeft;
     target.yOffset = - window->frameTop;
 }
@@ -1200,8 +1215,6 @@
         InputTarget& target = mCurrentInputTargets.editTop();
         target.inputChannel = mMonitoringChannels[i];
         target.flags = 0;
-        target.timeout = -1;
-        target.timeSpentWaitingForApplication = 0;
         target.xOffset = 0;
         target.yOffset = 0;
     }
@@ -1241,6 +1254,34 @@
     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(
@@ -1254,25 +1295,18 @@
         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,
             toString(resumeWithAppendedMotionSample));
 #endif
 
     // Skip this event if the connection status is not normal.
-    // We don't want to enqueue additional outbound events 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) {
         LOGW("channel '%s' ~ Dropping event because the channel status is %s",
                 connection->getInputChannelName(), connection->getStatusLabel());
-
-        // If the connection is not responding but the user is poking the application anyways,
-        // retrigger the original timeout.
-        if (connection->status == Connection::STATUS_NOT_RESPONDING) {
-            timeoutDispatchCycleLocked(currentTime, connection);
-        }
         return;
     }
 
@@ -1379,7 +1413,7 @@
 
                 DispatchEntry* cancelationDispatchEntry =
                         mAllocator.obtainDispatchEntry(cancelationEventEntry,
-                        0, inputTarget->xOffset, inputTarget->yOffset, inputTarget->timeout);
+                        0, inputTarget->xOffset, inputTarget->yOffset); // increments ref
                 connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
 
                 mAllocator.releaseEventEntry(cancelationEventEntry);
@@ -1390,10 +1424,9 @@
     // This is a new event.
     // Enqueue a new dispatch entry onto the outbound queue for this connection.
     DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref
-            inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset,
-            inputTarget->timeout);
-    if (dispatchEntry->isSyncTarget()) {
-        eventEntry->pendingSyncDispatches += 1;
+            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
@@ -1416,13 +1449,12 @@
     // If the outbound queue was previously empty, start the dispatch cycle going.
     if (wasEmpty) {
         activateConnectionLocked(connection.get());
-        startDispatchCycleLocked(currentTime, connection,
-                inputTarget->timeSpentWaitingForApplication);
+        startDispatchCycleLocked(currentTime, connection);
     }
 }
 
 void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
-        const sp<Connection>& connection, nsecs_t timeSpentWaitingForApplication) {
+        const sp<Connection>& connection) {
 #if DEBUG_DISPATCH_CYCLE
     LOGD("channel '%s' ~ startDispatchCycle",
             connection->getInputChannelName());
@@ -1588,9 +1620,6 @@
     connection->lastEventTime = dispatchEntry->eventEntry->eventTime;
     connection->lastDispatchTime = currentTime;
 
-    nsecs_t timeout = dispatchEntry->timeout - timeSpentWaitingForApplication;
-    connection->setNextTimeoutTime(currentTime, timeout);
-
     // Notify other system components.
     onDispatchCycleStartedLocked(currentTime, connection);
 }
@@ -1610,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
@@ -1653,20 +1669,20 @@
                 dispatchEntry->inProgress = false;
                 dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample;
                 dispatchEntry->tailMotionSample = NULL;
-                startDispatchCycleLocked(currentTime, connection, 0);
+                startDispatchCycleLocked(currentTime, connection);
                 return;
             }
             // 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, 0);
+            startDispatchCycleLocked(currentTime, connection);
             return;
         }
     }
@@ -1675,66 +1691,6 @@
     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) {
-        // Enter the not responding state.
-        connection->status = Connection::STATUS_NOT_RESPONDING;
-        connection->lastANRTime = currentTime;
-    } else if (connection->status != Connection::STATUS_NOT_RESPONDING) {
-        // Connection is broken or dead.
-        return;
-    }
-
-    // Notify other system components.
-    // This enqueues a command which will eventually call resumeAfterTimeoutDispatchCycleLocked.
-    onDispatchCycleANRLocked(currentTime, connection);
-}
-
-void InputDispatcher::resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
-        const sp<Connection>& connection, nsecs_t newTimeout) {
-#if DEBUG_DISPATCH_CYCLE
-    LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked - newTimeout=%lld",
-            connection->getInputChannelName(), newTimeout);
-#endif
-
-    if (connection->status != Connection::STATUS_NOT_RESPONDING) {
-        return;
-    }
-
-    if (newTimeout > 0) {
-        // The system has decided to give the application some more time.
-        // Keep waiting synchronously and resume normal dispatch.
-        connection->status = Connection::STATUS_NORMAL;
-        connection->setNextTimeoutTime(currentTime, newTimeout);
-    } else {
-        // The system is about to throw up an ANR dialog and has requested that we abort dispatch.
-        // Reset the timeout.
-        connection->nextTimeoutTime = LONG_LONG_MAX;
-
-        // Input state will no longer be realistic.
-        connection->inputState.setOutOfSync();
-
-        if (! connection->outboundQueue.isEmpty()) {
-            // Make the current pending dispatch asynchronous (if it isn't already) so that
-            // subsequent events can be delivered to the ANR dialog or to another application.
-            DispatchEntry* currentDispatchEntry = connection->outboundQueue.headSentinel.next;
-            currentDispatchEntry->preemptSyncTarget();
-
-            // Drain all but the first entry in the outbound queue.  We keep the first entry
-            // since that is the one that dispatch is stuck on.  We throw away the others
-            // so that we don't spam the application with stale messages if it eventually
-            // wakes up and recovers from the ANR.
-            drainOutboundQueueLocked(connection.get(), currentDispatchEntry->next);
-        }
-    }
-}
-
 void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime,
         const sp<Connection>& connection, bool broken) {
 #if DEBUG_DISPATCH_CYCLE
@@ -1742,20 +1698,16 @@
             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.
-    drainOutboundQueueLocked(connection.get(), connection->outboundQueue.headSentinel.next);
+    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.
@@ -1764,24 +1716,16 @@
     }
 }
 
-void InputDispatcher::drainOutboundQueueLocked(Connection* connection,
-        DispatchEntry* firstDispatchEntryToDrain) {
-    for (DispatchEntry* dispatchEntry = firstDispatchEntryToDrain;
-            dispatchEntry != & connection->outboundQueue.tailSentinel;) {
-        DispatchEntry* next = dispatchEntry->next;
-        connection->outboundQueue.dequeue(dispatchEntry);
-
-        if (dispatchEntry->isSyncTarget()) {
-            decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
+void InputDispatcher::drainOutboundQueueLocked(Connection* connection) {
+    while (! connection->outboundQueue.isEmpty()) {
+        DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
+        if (dispatchEntry->hasForegroundTarget()) {
+            decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
         }
         mAllocator.releaseDispatchEntry(dispatchEntry);
-
-        dispatchEntry = next;
     }
 
-    if (connection->outboundQueue.isEmpty()) {
-        deactivateConnectionLocked(connection);
-    }
+    deactivateConnectionLocked(connection);
 }
 
 int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
@@ -1941,56 +1885,63 @@
             // 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.tailSentinel.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!
                 }
             }
 
@@ -2074,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;
@@ -2137,10 +2088,10 @@
     }
 }
 
-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();
     }
 }
@@ -2238,6 +2189,9 @@
     { // acquire lock
         AutoMutex _l(mLock);
 
+        // Clear old window pointers but remember their associated channels.
+        mFocusedWindow = NULL;
+
         sp<InputChannel> touchedWindowChannel;
         if (mTouchedWindow) {
             touchedWindowChannel = mTouchedWindow->inputChannel;
@@ -2250,13 +2204,11 @@
             }
             mTouchedWallpaperWindows.clear();
         }
-
-        bool hadFocusedWindow = mFocusedWindow != NULL;
-
-        mFocusedWindow = NULL;
         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();
@@ -2280,14 +2232,8 @@
                 mTouchedWindow = window;
             }
         }
-
         mTempTouchedWallpaperChannels.clear();
 
-        if ((hadFocusedWindow && ! mFocusedWindow)
-                || (mFocusedWindow && ! mFocusedWindow->visible)) {
-            preemptInputDispatchInnerLocked();
-        }
-
 #if DEBUG_FOCUS
         logDispatchStateLocked();
 #endif
@@ -2359,43 +2305,20 @@
     }
 }
 
-void InputDispatcher::preemptInputDispatch() {
-#if DEBUG_FOCUS
-    LOGD("preemptInputDispatch");
-#endif
-
-    bool preemptedOne;
-    { // acquire lock
-        AutoMutex _l(mLock);
-        preemptedOne = preemptInputDispatchInnerLocked();
-    } // release lock
-
-    if (preemptedOne) {
-        // Wake up the poll loop so it can get a head start dispatching the next event.
-        mLooper->wake();
-    }
-}
-
-bool InputDispatcher::preemptInputDispatchInnerLocked() {
-    bool preemptedOne = false;
-    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->preemptSyncTarget();
-            preemptedOne = true;
-        }
-    }
-    return preemptedOne;
-}
-
 void InputDispatcher::logDispatchStateLocked() {
     String8 dump;
     dumpDispatchStateLocked(dump);
-    LOGD("%s", dump.string());
+
+    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) {
@@ -2409,28 +2332,30 @@
     } 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>",
+    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]: '%s'\n",
-                i, mTouchedWallpaperWindows[i]->inputChannel->getName().string());
+        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]: '%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
-                "visible=%s, flags=0x%08x, type=0x%08x, "
+        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].inputChannel->getName().string(),
+                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,
@@ -2447,12 +2372,14 @@
                 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, hasPendingSyncTarget=%s, "
+        dump.appendFormat("  activeConnection[%d]: '%s', status=%s, outboundQueueLength=%u"
                 "inputState.isNeutral=%s, inputState.isOutOfSync=%s\n",
                 i, connection->getInputChannelName(), connection->getStatusLabel(),
-                toString(connection->hasPendingSyncTarget()),
+                connection->outboundQueue.count(),
                 toString(connection->inputState.isNeutral()),
                 toString(connection->inputState.isOutOfSync()));
     }
@@ -2474,7 +2401,7 @@
     { // 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;
@@ -2510,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());
@@ -2543,7 +2470,7 @@
     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);
@@ -2578,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(
@@ -2615,6 +2518,25 @@
     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();
@@ -2637,33 +2559,16 @@
     }
 }
 
-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 = mPolicy->notifyInputChannelANR(connection->inputChannel);
+    mLock.lock();
 
-        mLock.lock();
-
-        nsecs_t currentTime = now();
-        resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout);
-    }
-}
-
-void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible(
-        CommandEntry* commandEntry) {
-    sp<Connection> connection = commandEntry->connection;
-
-    if (connection->status != Connection::STATUS_ZOMBIE) {
-        mLock.unlock();
-
-        mPolicy->notifyInputChannelRecoveredFromANR(connection->inputChannel);
-
-        mLock.lock();
-    }
+    resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, commandEntry->inputChannel);
 }
 
 void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
@@ -2695,22 +2600,9 @@
     mLock.lock();
 }
 
-void InputDispatcher::doTargetsNotReadyTimeoutLockedInterruptible(
-        CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    nsecs_t newTimeout;
-    if (commandEntry->inputChannel.get()) {
-        newTimeout = mPolicy->notifyInputChannelANR(commandEntry->inputChannel);
-    } else if (commandEntry->inputApplicationHandle.get()) {
-        newTimeout = mPolicy->notifyANR(commandEntry->inputApplicationHandle);
-    } else {
-        newTimeout = 0;
-    }
-
-    mLock.lock();
-
-    resumeAfterTargetsNotReadyTimeoutLocked(newTimeout);
+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) {
@@ -2718,6 +2610,18 @@
 }
 
 
+// --- 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;
+}
+
+
 // --- InputDispatcher::Allocator ---
 
 InputDispatcher::Allocator::Allocator() {
@@ -2733,7 +2637,7 @@
     entry->injectionIsAsync = false;
     entry->injectorPid = -1;
     entry->injectorUid = -1;
-    entry->pendingSyncDispatches = 0;
+    entry->pendingForegroundDispatches = 0;
 }
 
 InputDispatcher::ConfigurationChangedEntry*
@@ -2797,14 +2701,13 @@
 
 InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
         EventEntry* eventEntry,
-        int32_t targetFlags, float xOffset, float yOffset, nsecs_t timeout) {
+        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->timeout = timeout;
     entry->inProgress = false;
     entry->headMotionSample = NULL;
     entry->tailMotionSample = NULL;
@@ -2896,7 +2799,7 @@
 void InputDispatcher::EventEntry::recycle() {
     injectionResult = INPUT_EVENT_INJECTION_PENDING;
     dispatchInProgress = false;
-    pendingSyncDispatches = 0;
+    pendingForegroundDispatches = 0;
 }
 
 
@@ -3106,9 +3009,7 @@
 
 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() {
@@ -3118,18 +3019,6 @@
     return inputPublisher.initialize();
 }
 
-void InputDispatcher::Connection::setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout) {
-    nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
-}
-
-void InputDispatcher::Connection::resetTimeout(nsecs_t currentTime) {
-    if (outboundQueue.isEmpty()) {
-        nextTimeoutTime = LONG_LONG_MAX;
-    } else {
-        setNextTimeoutTime(currentTime, outboundQueue.headSentinel.next->timeout);
-    }
-}
-
 const char* InputDispatcher::Connection::getStatusLabel() const {
     switch (status) {
     case STATUS_NORMAL:
@@ -3138,9 +3027,6 @@
     case STATUS_BROKEN:
         return "BROKEN";
 
-    case STATUS_NOT_RESPONDING:
-        return "NOT_RESPONDING";
-
     case STATUS_ZOMBIE:
         return "ZOMBIE";
 
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 88084c0..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,
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/Looper.cpp b/libs/utils/Looper.cpp
index fd287da..4aa50d6 100644
--- a/libs/utils/Looper.cpp
+++ b/libs/utils/Looper.cpp
@@ -162,9 +162,11 @@
     struct epoll_event eventItems[EPOLL_MAX_EVENTS];
     int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
     if (eventCount < 0) {
-        if (errno != EINTR) {
-            LOGW("Poll failed with an unexpected error, errno=%d", errno);
+        if (errno == EINTR) {
+            return ALOOPER_POLL_WAKE;
         }
+
+        LOGW("Poll failed with an unexpected error, errno=%d", errno);
         return ALOOPER_POLL_ERROR;
     }
 
@@ -196,7 +198,7 @@
                     ssize_t nRead;
                     do {
                         nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
-                    } while (nRead == sizeof(buffer));
+                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
                 } else {
                     LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
                 }
@@ -272,7 +274,11 @@
     LOGD("%p ~ wake", this);
 #endif
 
-    ssize_t nWrite = write(mWakeWritePipeFd, "W", 1);
+    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);
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/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..cc9f4b2
--- /dev/null
+++ b/media/java/android/media/videoeditor/TransitionSliding.java
@@ -0,0 +1,82 @@
+/*

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

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

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

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

+ * limitations under the License.

+ */

+

+

+package android.media.videoeditor;

+

+/**

+ * This class allows to create sliding transitions

+ * {@hide}

+ */

+public class TransitionSliding extends Transition {

+

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

+    public final static int DIRECTION_RIGHT_OUT_LEFT_IN = 0;

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

+    public static final int DIRECTION_LEFT_OUT_RIGHT_IN = 1;

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

+    public static final int DIRECTION_TOP_OUT_BOTTOM_IN = 2;

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

+    public static final int DIRECTION_BOTTOM_OUT_TOP_IN = 3;

+

+    // The sliding transitions

+    private final int mSlidingDirection;

+

+    /**

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

+     * constructor

+     */

+    @SuppressWarnings("unused")

+    private TransitionSliding() {

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

+    }

+

+    /**

+     * Constructor

+     *

+     * @param transitionId The transition id

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

+     *      media item

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

+     *      this media item

+     * @param durationMs duration of the transition in milliseconds

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

+     *            class

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

+     *            RIGHT_OUT_LEFT_IN

+     *

+     * @throws IllegalArgumentException if behavior is not supported.

+     */

+    public TransitionSliding(String transitionId, MediaItem afterMediaItem,

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

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

+        mSlidingDirection = direction;

+    }

+

+    /**

+     * @return The sliding direction

+     */

+    public int getDirection() {

+        return mSlidingDirection;

+    }

+

+    /*

+     * {@inheritDoc}

+     */

+    @Override

+    void generate() {

+    }

+}

diff --git a/media/java/android/media/videoeditor/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/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
index 90756d0..8f40130 100644
--- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
+++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
@@ -145,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){
@@ -303,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;
@@ -353,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) {
@@ -402,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);
@@ -2377,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
 
@@ -2426,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)&&
@@ -2843,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:
@@ -2907,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/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/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index fe5074b..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;
@@ -1325,6 +1328,11 @@
     LOGV("stop");
     status_t err = OK;
 
+    if (mCaptureTimeLapse && mCameraSourceTimeLapse != NULL) {
+        mCameraSourceTimeLapse->startQuickReadReturns();
+        mCameraSourceTimeLapse = NULL;
+    }
+
     if (mCaptureAuxVideo) {
         if (mWriterAux != NULL) {
             mWriterAux->stop();
@@ -1411,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 227e090..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,7 @@
         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);
@@ -224,12 +309,26 @@
         void *dummy;
         pthread_join(mThreadTimeLapse, &dummy);
 
-        // play the recording sound and restart preview.
+        // 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);
-        CHECK_EQ(OK, mCamera->startPreview());
     } else {
+        mCamera->setListener(NULL);
         mCamera->stopRecording();
     }
+    if (mLastReadBufferCopy) {
+        mLastReadBufferCopy->release();
+        mLastReadBufferCopy = NULL;
+    }
 }
 
 void CameraSourceTimeLapse::releaseRecordingFrame(const sp<IMemory>& frame) {
@@ -264,7 +363,9 @@
 
 void CameraSourceTimeLapse::threadStartPreview() {
     CHECK_EQ(OK, mCamera->startPreview());
+    Mutex::Autolock autoLock(mCameraIdleLock);
     mCameraIdle = true;
+    mCameraIdleCondition.signal();
 }
 
 void CameraSourceTimeLapse::restartPreview() {
@@ -358,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.
@@ -374,6 +491,7 @@
 
             mLastTimeLapseFrameRealTimestampUs = *timestampUs;
             *timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs;
+            return false;
         }
     }
     return false;
@@ -383,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/native/include/android/input.h b/native/include/android/input.h
index c1134bf..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,
 };
 
 /*
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/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/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
index 5dedcc9..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,19 +718,19 @@
             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
@@ -724,13 +738,14 @@
             } 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;
         }
     }
@@ -764,6 +779,7 @@
             mDataState = state;
             updateDataNetType(networkType, 0);
             updateDataIcon();
+            updateSignalStrength(); // apply the change in connection status
         }
 
         @Override
@@ -858,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.
@@ -925,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;
         }
     }
@@ -1104,7 +1120,7 @@
             if (newSignalLevel != mLastWifiSignalLevel) {
                 mLastWifiSignalLevel = newSignalLevel;
                 if (mIsWifiConnected) {
-                    iconId = sWifiSignalImages[mLastWifiInetConnectivityState][newSignalLevel];
+                    iconId = sWifiSignalImages[mInetCondition][newSignalLevel];
                 } else {
                     iconId = sWifiTemporarilyNotConnectedImage;
                 }
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 90d036a..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")
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/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/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index dacdd7d..fb8e888 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -5088,61 +5088,39 @@
             }
         }
         
-        /* Notifies the window manager about an input channel that is not responding.
+        /* 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 0; // window is unknown, abort dispatching
+        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.
-         * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
-         * 
-         * 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) {
                 }
@@ -5195,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;
                 
@@ -5294,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.
@@ -5322,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/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 c1841f6..d7747fb 100644
--- a/services/java/com/android/server/sip/SipService.java
+++ b/services/java/com/android/server/sip/SipService.java
@@ -59,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;
@@ -90,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,
@@ -137,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,
@@ -238,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);
@@ -260,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);
@@ -270,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;
         }
@@ -283,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"));
 
@@ -398,7 +400,7 @@
                 mSipGroup.openToReceiveCalls(this);
                 mAutoRegistration.start(mSipGroup);
             }
-            Log.v(TAG, "  openToReceiveCalls: " + getUri() + ": "
+            if (DEBUG) Log.d(TAG, "  openToReceiveCalls: " + getUri() + ": "
                     + mIncomingCallBroadcastAction);
         }
 
@@ -410,8 +412,8 @@
                 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();
             }
@@ -437,7 +439,7 @@
             mOpened = false;
             mSipGroup.closeToNotReceiveCalls();
             mAutoRegistration.stop();
-            Log.v(TAG, "   close: " + getUri() + ": "
+            if (DEBUG) Log.d(TAG, "   close: " + getUri() + ": "
                     + mIncomingCallBroadcastAction);
         }
 
@@ -456,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
@@ -474,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() {
@@ -506,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()) {
@@ -549,7 +552,7 @@
                 // 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());
             }
         }
@@ -581,7 +584,6 @@
         }
 
         public void setListener(ISipSessionListener listener) {
-            Log.v(TAG, "setListener(): " + listener);
             synchronized (SipService.this) {
                 mProxy.setListener(listener);
                 if (mSession == null) return;
@@ -619,7 +621,7 @@
         public void run() {
             mErrorCode = null;
             mErrorMessage = null;
-            Log.v(TAG, "  ~~~ registering");
+            if (DEBUG) Log.d(TAG, "~~~ registering");
             synchronized (SipService.this) {
                 if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME);
             }
@@ -642,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);
         }
@@ -659,7 +661,7 @@
 
         @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;
@@ -669,7 +671,7 @@
 
         @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;
 
@@ -703,7 +705,7 @@
                 } else {
                     mRegistered = false;
                     mExpiryTime = -1L;
-                    Log.v(TAG, "Refresh registration immediately");
+                    if (DEBUG) Log.d(TAG, "Refresh registration immediately");
                     run();
                 }
             }
@@ -714,8 +716,8 @@
                 String errorCodeString, String message) {
             SipErrorCode errorCode =
                     Enum.valueOf(SipErrorCode.class, errorCodeString);
-            Log.v(TAG, "onRegistrationFailed(): " + session + ": " + mSession
-                    + ": " + errorCode + ": " + message);
+            if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
+                    + errorCode + ": " + message);
             synchronized (SipService.this) {
                 if (!isStopped() && (session != mSession)) return;
                 mErrorCode = errorCode;
@@ -724,7 +726,7 @@
                         message);
 
                 if (errorCode == SipErrorCode.INVALID_CREDENTIALS) {
-                    Log.d(TAG, "   pause auto-registration");
+                    if (DEBUG) Log.d(TAG, "   pause auto-registration");
                     stopButKeepStates();
                 } else if (!isStopped()) {
                     onError();
@@ -734,7 +736,7 @@
 
         @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;
                 mErrorCode = SipErrorCode.TIME_OUT;
@@ -773,30 +775,33 @@
                     NetworkInfo.State state = netInfo.getState();
 
                     NetworkInfo activeNetInfo = getActiveNetworkInfo();
-                    if (activeNetInfo != null) {
-                        Log.v(TAG, "active network: " + activeNetInfo.getTypeName()
-                                + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED)
-                                        ? " CONNECTED" : " DISCONNECTED"));
-                    } else {
-                        Log.v(TAG, "active network: null");
+                    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())) {
-                        Log.d(TAG, "ignore connect event: " + type
+                        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);
                     }
                 }
             }
@@ -847,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);
                 }
@@ -929,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
@@ -984,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();
+            }
         }
 
         /**
@@ -997,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();
@@ -1005,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()) {
@@ -1019,8 +1028,10 @@
                 recalculatePeriods();
                 scheduleNext();
             }
-            Log.d(TAG, "after cancel:");
-            printQueue();
+            if (DEBUG_TIMER) {
+                Log.d(TAG, "after cancel:");
+                printQueue();
+            }
         }
 
         private void scheduleNext() {
@@ -1069,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;
@@ -1083,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 a3bf3eb..baf9a8e 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -80,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");
@@ -218,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));
+            }
         }
     }
 
@@ -254,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));
+            }
         }
     }
 
@@ -288,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);
@@ -321,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);
@@ -355,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);
@@ -374,6 +417,8 @@
             mServerTransaction = null;
             mClientTransaction = null;
             mPeerSessionDescription = null;
+
+            cancelSessionTimer();
         }
 
         public boolean isInCall() {
@@ -426,16 +471,16 @@
             }).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);
             }
@@ -445,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) {
@@ -503,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;
 
@@ -672,14 +723,14 @@
                     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");
                 }
                 reset();
                 return true;
@@ -792,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();
@@ -823,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,
@@ -865,6 +918,7 @@
                     if (mState == SipSessionState.OUTGOING_CALL) {
                         mState = SipSessionState.OUTGOING_CALL_RING_BACK;
                         mProxy.onRingingBack(this);
+                        cancelSessionTimer();
                     }
                     return true;
                 case Response.OK:
@@ -872,14 +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) {
-                        endCallOnError(SipErrorCode.SERVER_ERROR,
+                        onError(SipErrorCode.SERVER_ERROR,
                                 "server does not provide challenge");
                     } else {
-                        endCallOnError(SipErrorCode.INVALID_CREDENTIALS,
+                        onError(SipErrorCode.INVALID_CREDENTIALS,
                                 "incorrect username or password");
                     }
                     return true;
@@ -905,6 +960,7 @@
                 // response.
                 mSipHelper.sendCancel(mClientTransaction);
                 mState = SipSessionState.OUTGOING_CALL_CANCELING;
+                startSessionTimer(CANCEL_CALL_TIMER);
                 return true;
             }
             return false;
@@ -917,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) {
@@ -969,11 +1029,27 @@
                 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());
@@ -982,15 +1058,10 @@
         private void establishCall() {
             mState = SipSessionState.IN_CALL;
             mInCall = true;
+            cancelSessionTimer();
             mProxy.onCallEstablished(this, mPeerSessionDescription);
         }
 
-        private void fallbackToPreviousInCall(Throwable exception) {
-            exception = getRootCause(exception);
-            fallbackToPreviousInCall(getErrorCode(exception),
-                    exception.toString());
-        }
-
         private void fallbackToPreviousInCall(SipErrorCode errorCode,
                 String message) {
             mState = SipSessionState.IN_CALL;
@@ -1013,6 +1084,7 @@
         }
 
         private void onError(SipErrorCode errorCode, String message) {
+            cancelSessionTimer();
             switch (mState) {
                 case REGISTERING:
                 case DEREGISTERING:
@@ -1187,6 +1259,34 @@
         }
     }
 
+    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();
@@ -1218,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() {
@@ -1232,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 6dd619c5..d27c2c6 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -49,8 +49,6 @@
     jmethodID notifyConfigurationChanged;
     jmethodID notifyLidSwitchChanged;
     jmethodID notifyInputChannelBroken;
-    jmethodID notifyInputChannelANR;
-    jmethodID notifyInputChannelRecoveredFromANR;
     jmethodID notifyANR;
     jmethodID virtualKeyDownFeedback;
     jmethodID interceptKeyBeforeQueueing;
@@ -85,6 +83,7 @@
     jclass clazz;
 
     jfieldID inputChannel;
+    jfieldID name;
     jfieldID layoutParamsFlags;
     jfieldID layoutParamsType;
     jfieldID dispatchingTimeoutNanos;
@@ -101,9 +100,11 @@
     jfieldID touchableAreaRight;
     jfieldID touchableAreaBottom;
     jfieldID visible;
+    jfieldID canReceiveKeys;
     jfieldID hasFocus;
     jfieldID hasWallpaper;
     jfieldID paused;
+    jfieldID layer;
     jfieldID ownerPid;
     jfieldID ownerUid;
 } gInputWindowClassInfo;
@@ -168,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 --- */
 
@@ -191,10 +191,9 @@
     /* --- InputDispatcherPolicyInterface implementation --- */
 
     virtual void notifyConfigurationChanged(nsecs_t when);
-    virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle);
+    virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+            const sp<InputChannel>& inputChannel);
     virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel);
-    virtual nsecs_t notifyInputChannelANR(const sp<InputChannel>& inputChannel);
-    virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel);
     virtual nsecs_t getKeyRepeatTimeout();
     virtual nsecs_t getKeyRepeatDelay();
     virtual int32_t getMaxEventsPerSecond();
@@ -409,10 +408,15 @@
 
 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;
         }
@@ -702,32 +706,34 @@
     checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged");
 }
 
-nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle) {
+nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+        const sp<InputChannel>& inputChannel) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     LOGD("notifyANR");
 #endif
 
     JNIEnv* env = jniEnv();
 
-    ApplicationToken* token = static_cast<ApplicationToken*>(inputApplicationHandle.get());
-    jweak tokenObjWeak = token->getTokenObj();
-
-    jlong newTimeout;
-    jobject tokenObjLocal = env->NewLocalRef(tokenObjWeak);
-    if (tokenObjLocal) {
-        newTimeout = env->CallLongMethod(mCallbacksObj,
-                gCallbacksClassInfo.notifyANR, tokenObjLocal);
-        if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
-            newTimeout = 0; // abort dispatch
-        } else {
-            assert(newTimeout >= 0);
-        }
-
-        env->DeleteLocalRef(tokenObjLocal);
+    jobject tokenObjLocal;
+    if (inputApplicationHandle.get()) {
+        ApplicationToken* token = static_cast<ApplicationToken*>(inputApplicationHandle.get());
+        jweak tokenObjWeak = token->getTokenObj();
+        tokenObjLocal = env->NewLocalRef(tokenObjWeak);
     } else {
-        newTimeout = 0; // abort dispatch
+        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;
 }
 
@@ -748,51 +754,6 @@
     }
 }
 
-nsecs_t NativeInputManager::notifyInputChannelANR(const sp<InputChannel>& inputChannel) {
-#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 = 0; // abort dispatch
-        } else {
-            assert(newTimeout >= 0);
-        }
-
-        env->DeleteLocalRef(inputChannelObjLocal);
-    } else {
-        newTimeout = 0; // abort dispatch
-    }
-
-    return newTimeout;
-}
-
-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);
-    }
-}
-
 nsecs_t NativeInputManager::getKeyRepeatTimeout() {
     if (! isScreenOn()) {
         // Disable key repeat when the screen is off.
@@ -855,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,
@@ -887,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;
@@ -915,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.");
@@ -973,10 +947,6 @@
     mInputManager->getDispatcher()->setInputDispatchMode(enabled, frozen);
 }
 
-void NativeInputManager::preemptInputDispatch() {
-    mInputManager->getDispatcher()->preemptInputDispatch();
-}
-
 bool NativeInputManager::interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
         const KeyEvent* keyEvent, uint32_t policyFlags) {
     bool isScreenOn = this->isScreenOn();
@@ -986,23 +956,17 @@
 
     JNIEnv* env = jniEnv();
 
+    // Note: inputChannel may be null.
     jobject inputChannelObj = getInputChannelObjLocal(env, 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");
+    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.", inputChannel->getName().string());
-        return false;
-    }
+    env->DeleteLocalRef(inputChannelObj);
+    return consumed && ! error;
 }
 
 void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType) {
@@ -1246,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)) {
@@ -1357,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",
@@ -1398,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");
@@ -1477,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");
 
@@ -1525,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");
 
@@ -1534,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/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 5a27fc5..b45f6fe 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1003,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);
         }
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/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index 8bd789c..83496dc 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -197,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;
@@ -290,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
      */
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/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index 5ef61a8..4af35a6 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -73,6 +73,7 @@
 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();
@@ -165,6 +166,7 @@
             } else {
                 throw new CallStateException("phone not ringing");
             }
+            updatePhoneState();
         }
     }
 
@@ -395,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();
@@ -445,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);
 
@@ -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);
@@ -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) {
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/DumpRenderTree2/AndroidManifest.xml b/tests/DumpRenderTree2/AndroidManifest.xml
index 5233677..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>
 
@@ -57,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 d5bf8b3..fd76e4a9 100755
--- a/tests/DumpRenderTree2/assets/run_layout_tests.py
+++ b/tests/DumpRenderTree2/assets/run_layout_tests.py
@@ -32,8 +32,15 @@
 
   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") + " restart"
+  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
@@ -73,5 +80,7 @@
   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/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/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
index 9a82f84..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;
@@ -166,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() {
@@ -412,6 +426,7 @@
         assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
 
         Log.i(LOG_TAG, "onTestFinished(): " + mCurrentTestRelativePath);
+        mResultHandler.removeMessages(MSG_TEST_TIMED_OUT);
         obtainActualResultsFromWebView();
     }
 
@@ -427,6 +442,9 @@
 
         mCurrentState = CurrentState.OBTAINING_RESULT;
 
+        if (mCurrentTestTimedOut) {
+            mCurrentResult.setDidTimeOut();
+        }
         mCurrentResult.obtainActualResults(mCurrentWebView,
                 mResultHandler.obtainMessage(MSG_ACTUAL_RESULT_OBTAINED));
     }
@@ -438,7 +456,6 @@
         Log.i(LOG_TAG, "onActualResultsObtained(): " + mCurrentTestRelativePath);
         mCurrentState = CurrentState.IDLE;
 
-        mResultHandler.removeMessages(MSG_TEST_TIMED_OUT);
         reportResultToService();
         mCurrentTestIndex++;
         updateProgressBar();
@@ -456,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));
             }
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
index 9caabdb..17e19d0 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
@@ -39,6 +39,7 @@
     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;
 
@@ -86,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;
             }
         }
     };
@@ -134,7 +147,7 @@
         super.onCreate();
 
         mFileFilter = new FileFilter();
-        mSummarizer = new Summarizer(mFileFilter, RESULTS_ROOT_DIR_PATH);
+        mSummarizer = new Summarizer(mFileFilter, RESULTS_ROOT_DIR_PATH, getApplicationContext());
     }
 
     @Override
@@ -150,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 =
@@ -168,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);
     }
 
     /**
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
index 2cde296..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,20 +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() {
@@ -217,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 {
@@ -234,26 +251,78 @@
                 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() {
+    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 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();
 
@@ -266,10 +335,10 @@
 
         txt.append("TOTAL:                     " + getTotalTestCount() + "\n");
         txt.append("CRASHED (among all tests): " + mCrashedTestsCount + "\n");
-        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("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);
@@ -284,26 +353,22 @@
         html.append("</head><body>");
 
         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() {
@@ -352,10 +417,10 @@
         html.append("<table class=\"summary\">");
         createSummaryTableRow(html, "TOTAL", getTotalTestCount());
         createSummaryTableRow(html, "CRASHED (among all tests)", 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, "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>");
     }
 
@@ -366,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>");
 
@@ -387,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/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/ForwarderManager.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ForwarderManager.java
index 7317a27..cff436ff 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/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 5597415..8a08c48 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -214,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/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/ThinPatchesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ThinPatchesActivity.java
index d374c32..4f605fa 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ThinPatchesActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ThinPatchesActivity.java
@@ -41,13 +41,14 @@
     }
 
     private class PatchView extends View {
-        private Drawable mPatch;
+        private Drawable mPatch1, mPatch2;
 
         public PatchView(Activity activity) {
             super(activity);
 
             final Resources resources = activity.getResources();
-            mPatch = resources.getDrawable(R.drawable.btn_toggle_on);
+            mPatch1 = resources.getDrawable(R.drawable.patch);
+            mPatch2 = resources.getDrawable(R.drawable.btn_toggle_on);
         }
 
         @Override
@@ -58,8 +59,13 @@
             final int left = (getWidth() - width) / 2;
             final int top  = (getHeight() - height) / 2;
 
-            mPatch.setBounds(left, top, left + width, top + height);
-            mPatch.draw(canvas);
+            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 29fcb32..5661b8f 100644
--- a/voip/java/android/net/sip/ISipSession.aidl
+++ b/voip/java/android/net/sip/ISipSession.aidl
@@ -20,8 +20,8 @@
 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 {
@@ -112,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
@@ -122,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
@@ -140,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 1d1be69..4abea20 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -96,8 +96,8 @@
     }
 
     /**
-     * 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) {
@@ -134,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)
@@ -153,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.
@@ -178,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 dd275da..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,6 +37,7 @@
 
 import java.io.IOException;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -54,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;
@@ -140,12 +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() {
@@ -215,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();
@@ -225,24 +237,18 @@
         }
     }
 
-    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 {
@@ -342,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;
@@ -372,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);
@@ -385,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);
         }
@@ -572,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);
@@ -584,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 ee985de..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 {
diff --git a/voip/java/android/net/sip/SipException.java b/voip/java/android/net/sip/SipException.java
index d615342..f0d846b 100644
--- a/voip/java/android/net/sip/SipException.java
+++ b/voip/java/android/net/sip/SipException.java
@@ -17,6 +17,7 @@
 package android.net.sip;
 
 /**
+ * General SIP-related exception class.
  * @hide
  */
 public class SipException extends Exception {
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 149053c..36895cd 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -28,25 +28,22 @@
 
 /**
  * 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
  */
@@ -113,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.
@@ -134,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.
@@ -160,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
@@ -225,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);
         }
@@ -283,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
@@ -377,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
      */
@@ -419,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
diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java
index 1a7d8bf..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)
@@ -309,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();
@@ -327,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 e751e46..705f271 100644
--- a/voip/java/android/net/sip/SipRegistrationListener.java
+++ b/voip/java/android/net/sip/SipRegistrationListener.java
@@ -29,15 +29,15 @@
     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 of this error
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 26ed878..4435110 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -307,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
@@ -316,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)
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)) {