Merge branch 'master' into honeycomb-release
diff --git a/api/current.xml b/api/current.xml
index 6da4a33..e3fcfcf 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -1773,7 +1773,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843511"
+ value="16843513"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1784,7 +1784,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843480"
+ value="16843482"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1795,7 +1795,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843520"
+ value="16843522"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1806,7 +1806,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843519"
+ value="16843521"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1817,7 +1817,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843521"
+ value="16843523"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1828,7 +1828,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843490"
+ value="16843492"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1839,7 +1839,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843489"
+ value="16843491"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1850,7 +1850,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843527"
+ value="16843529"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1861,7 +1861,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843493"
+ value="16843495"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1872,7 +1872,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843523"
+ value="16843525"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1883,7 +1883,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843494"
+ value="16843496"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1894,7 +1894,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843547"
+ value="16843549"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1905,7 +1905,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843546"
+ value="16843548"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1916,7 +1916,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843548"
+ value="16843550"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1927,7 +1927,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843522"
+ value="16843524"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1938,7 +1938,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843528"
+ value="16843530"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -1949,7 +1949,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843529"
+ value="16843531"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2004,7 +2004,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843466"
+ value="16843468"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2037,7 +2037,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843545"
+ value="16843547"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2059,7 +2059,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843541"
+ value="16843543"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2070,7 +2070,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843478"
+ value="16843480"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2169,7 +2169,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843487"
+ value="16843489"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2180,7 +2180,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843518"
+ value="16843520"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2279,7 +2279,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843472"
+ value="16843474"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2521,7 +2521,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843536"
+ value="16843538"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2532,7 +2532,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843535"
+ value="16843537"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2565,7 +2565,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843544"
+ value="16843546"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2983,7 +2983,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843475"
+ value="16843477"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -3137,7 +3137,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843484"
+ value="16843486"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -3346,7 +3346,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843540"
+ value="16843542"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -3423,7 +3423,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843482"
+ value="16843484"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -3467,7 +3467,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843543"
+ value="16843545"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -3643,7 +3643,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843488"
+ value="16843490"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4215,7 +4215,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843501"
+ value="16843503"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4226,7 +4226,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843505"
+ value="16843507"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4237,7 +4237,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843506"
+ value="16843508"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4248,7 +4248,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843507"
+ value="16843509"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4259,7 +4259,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843508"
+ value="16843510"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4270,7 +4270,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843503"
+ value="16843505"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4281,7 +4281,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843504"
+ value="16843506"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4292,7 +4292,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843509"
+ value="16843511"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4303,7 +4303,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843510"
+ value="16843512"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4325,7 +4325,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843470"
+ value="16843472"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4358,7 +4358,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843473"
+ value="16843475"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4655,7 +4655,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843485"
+ value="16843487"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4787,7 +4787,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843526"
+ value="16843528"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4908,7 +4908,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843514"
+ value="16843516"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4919,7 +4919,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843512"
+ value="16843514"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4930,7 +4930,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843513"
+ value="16843515"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -5953,7 +5953,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843516"
+ value="16843518"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -5997,7 +5997,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843537"
+ value="16843539"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6008,7 +6008,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843531"
+ value="16843533"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6096,7 +6096,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843539"
+ value="16843541"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6261,7 +6261,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843486"
+ value="16843488"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6404,7 +6404,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843481"
+ value="16843483"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6580,7 +6580,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843500"
+ value="16843502"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6910,7 +6910,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843532"
+ value="16843534"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -7009,7 +7009,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843492"
+ value="16843494"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -7174,7 +7174,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843499"
+ value="16843501"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -7621,6 +7621,17 @@
  visibility="public"
 >
 </field>
+<field name="screenDensity"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843467"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="screenOrientation"
  type="int"
  transient="false"
@@ -7632,6 +7643,17 @@
  visibility="public"
 >
 </field>
+<field name="screenSize"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843466"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="scrollHorizontally"
  type="int"
  transient="false"
@@ -7955,7 +7977,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843467"
+ value="16843469"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -8076,7 +8098,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843491"
+ value="16843493"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -8164,7 +8186,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843468"
+ value="16843470"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -8219,7 +8241,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843517"
+ value="16843519"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -8241,7 +8263,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843515"
+ value="16843517"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -8340,7 +8362,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843530"
+ value="16843532"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -8560,7 +8582,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843483"
+ value="16843485"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -8571,7 +8593,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843525"
+ value="16843527"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -8956,7 +8978,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843533"
+ value="16843535"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9033,7 +9055,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843534"
+ value="16843536"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9077,7 +9099,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843538"
+ value="16843540"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9275,7 +9297,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843542"
+ value="16843544"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9506,7 +9528,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843524"
+ value="16843526"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9517,7 +9539,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843471"
+ value="16843473"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9550,7 +9572,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843474"
+ value="16843476"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9737,7 +9759,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843469"
+ value="16843471"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9781,7 +9803,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843496"
+ value="16843498"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9792,7 +9814,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843497"
+ value="16843499"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9803,7 +9825,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843498"
+ value="16843500"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -10122,7 +10144,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843479"
+ value="16843481"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -10133,7 +10155,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843502"
+ value="16843504"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -10144,7 +10166,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843495"
+ value="16843497"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -10364,7 +10386,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843477"
+ value="16843479"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -10375,7 +10397,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843476"
+ value="16843478"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -22935,17 +22957,6 @@
  visibility="public"
 >
 </field>
-<field name="IMPORTANCE_HEAVY_WEIGHT"
- type="int"
- transient="false"
- volatile="false"
- value="170"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="IMPORTANCE_PERCEPTIBLE"
  type="int"
  transient="false"
@@ -30357,11 +30368,11 @@
 <parameter name="holder" type="android.view.SurfaceHolder">
 </parameter>
 </method>
-<field name="KEY_NATIVE_SAVED_STATE"
+<field name="META_DATA_FUNC_NAME"
  type="java.lang.String"
  transient="false"
  volatile="false"
- value="&quot;android:native_state&quot;"
+ value="&quot;android.app.func_name&quot;"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -36131,7 +36142,7 @@
 <implements name="android.bluetooth.BluetoothProfile">
 </implements>
 <method name="getConnectedDevices"
- return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ return="java.util.List&lt;android.bluetooth.BluetoothDevice&gt;"
  abstract="false"
  native="false"
  synchronized="false"
@@ -36155,7 +36166,7 @@
 </parameter>
 </method>
 <method name="getDevicesMatchingConnectionStates"
- return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ return="java.util.List&lt;android.bluetooth.BluetoothDevice&gt;"
  abstract="false"
  native="false"
  synchronized="false"
@@ -37969,7 +37980,7 @@
 <implements name="android.bluetooth.BluetoothProfile">
 </implements>
 <method name="getConnectedDevices"
- return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ return="java.util.List&lt;android.bluetooth.BluetoothDevice&gt;"
  abstract="false"
  native="false"
  synchronized="false"
@@ -37993,7 +38004,7 @@
 </parameter>
 </method>
 <method name="getDevicesMatchingConnectionStates"
- return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ return="java.util.List&lt;android.bluetooth.BluetoothDevice&gt;"
  abstract="false"
  native="false"
  synchronized="false"
@@ -38097,7 +38108,7 @@
  visibility="public"
 >
 <method name="getConnectedDevices"
- return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ return="java.util.List&lt;android.bluetooth.BluetoothDevice&gt;"
  abstract="true"
  native="false"
  synchronized="false"
@@ -38121,7 +38132,7 @@
 </parameter>
 </method>
 <method name="getDevicesMatchingConnectionStates"
- return="java.util.Set&lt;android.bluetooth.BluetoothDevice&gt;"
+ return="java.util.List&lt;android.bluetooth.BluetoothDevice&gt;"
  abstract="true"
  native="false"
  synchronized="false"
@@ -108717,32 +108728,6 @@
 <parameter name="callbackImmediately" type="boolean">
 </parameter>
 </method>
-<method name="setRingbackToneEnabled"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="enabled" type="boolean">
-</parameter>
-</method>
-<method name="setRingtoneEnabled"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="enabled" type="boolean">
-</parameter>
-</method>
 <method name="setSpeakerMode"
  return="void"
  abstract="false"
@@ -109441,25 +109426,6 @@
 <exception name="SipException" type="android.net.sip.SipException">
 </exception>
 </method>
-<method name="takeAudioCall"
- return="android.net.sip.SipAudioCall"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="incomingCallIntent" type="android.content.Intent">
-</parameter>
-<parameter name="listener" type="android.net.sip.SipAudioCall.Listener">
-</parameter>
-<parameter name="ringtoneEnabled" type="boolean">
-</parameter>
-<exception name="SipException" type="android.net.sip.SipException">
-</exception>
-</method>
 <method name="unregister"
  return="void"
  abstract="false"
@@ -112026,6 +111992,18 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<parameter name="data" type="byte[]">
+</parameter>
+<exception name="FormatException" type="android.nfc.FormatException">
+</exception>
+</constructor>
+<constructor name="NdefMessage"
+ type="android.nfc.NdefMessage"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
 <parameter name="records" type="android.nfc.NdefRecord[]">
 </parameter>
 </constructor>
@@ -112051,6 +112029,17 @@
  visibility="public"
 >
 </method>
+<method name="toByteArray"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="writeToParcel"
  return="void"
  abstract="false"
@@ -112170,6 +112159,17 @@
  visibility="public"
 >
 </method>
+<method name="toByteArray"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="writeToParcel"
  return="void"
  abstract="false"
@@ -112360,6 +112360,29 @@
 >
 <implements name="android.os.Parcelable">
 </implements>
+<method name="createMockNdefTag"
+ return="android.nfc.NdefTag"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="byte[]">
+</parameter>
+<parameter name="rawTargets" type="java.lang.String[]">
+</parameter>
+<parameter name="pollBytes" type="byte[]">
+</parameter>
+<parameter name="activationBytes" type="byte[]">
+</parameter>
+<parameter name="ndefTargets" type="java.lang.String[]">
+</parameter>
+<parameter name="messages" type="android.nfc.NdefMessage[][]">
+</parameter>
+</method>
 <method name="getNdefMessages"
  return="android.nfc.NdefMessage[]"
  abstract="false"
@@ -112680,7 +112703,7 @@
  visibility="public"
 >
 </method>
-<method name="isTagDiscoveryEnabled"
+<method name="isEnabled"
  return="boolean"
  abstract="false"
  native="false"
@@ -112829,6 +112852,25 @@
 >
 <implements name="android.os.Parcelable">
 </implements>
+<method name="createMockTag"
+ return="android.nfc.Tag"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="byte[]">
+</parameter>
+<parameter name="rawTargets" type="java.lang.String[]">
+</parameter>
+<parameter name="pollBytes" type="byte[]">
+</parameter>
+<parameter name="activationBytes" type="byte[]">
+</parameter>
+</method>
 <method name="describeContents"
  return="int"
  abstract="false"
@@ -112840,6 +112882,17 @@
  visibility="public"
 >
 </method>
+<method name="getActivationBytes"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getId"
  return="byte[]"
  abstract="false"
@@ -112851,6 +112904,17 @@
  visibility="public"
 >
 </method>
+<method name="getPollBytes"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getRawTargets"
  return="java.lang.String[]"
  abstract="false"
@@ -112909,17 +112973,6 @@
  visibility="public"
 >
 </field>
-<field name="TARGET_ISO_14443_3B_PRIME"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value="&quot;iso14443_3b&quot;"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="TARGET_ISO_14443_4"
  type="java.lang.String"
  transient="false"
@@ -112964,17 +113017,6 @@
  visibility="public"
 >
 </field>
-<field name="TARGET_TOPAZ"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value="&quot;topaz&quot;"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 </class>
 </package>
 <package name="android.opengl"
@@ -152842,6 +152884,17 @@
  visibility="public"
 >
 </field>
+<field name="DATA"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;data&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="EMAIL"
  type="java.lang.String"
  transient="false"
@@ -186762,6 +186815,17 @@
  visibility="public"
 >
 </field>
+<field name="DENSITY_XHIGH"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="320"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="density"
  type="float"
  transient="false"
@@ -190804,6 +190868,17 @@
  visibility="public"
 >
 </method>
+<method name="getResult"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getX"
  return="float"
  abstract="false"
@@ -190846,6 +190921,8 @@
 </parameter>
 <parameter name="data" type="android.content.ClipData">
 </parameter>
+<parameter name="result" type="boolean">
+</parameter>
 </method>
 <method name="obtain"
  return="android.view.DragEvent"
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 67bd9f7..d309ef9 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -84,7 +84,7 @@
         LOGI("wrote screenshot: %s\n", screenshot_path);
     }
 
-    run_command("SYSTEM LOG", 20, "logcat", "-v", "time", "-d", "*:v", NULL);
+    run_command("SYSTEM LOG", 20, "logcat", "-v", "threadtime", "-d", "*:v", NULL);
 
     /* show the traces we collected in main(), if that was done */
     if (dump_traces_path != NULL) {
@@ -104,8 +104,8 @@
     }
 
     // dump_file("EVENT LOG TAGS", "/etc/event-log-tags");
-    run_command("EVENT LOG", 20, "logcat", "-b", "events", "-v", "time", "-d", "*:v", NULL);
-    run_command("RADIO LOG", 20, "logcat", "-b", "radio", "-v", "time", "-d", "*:v", NULL);
+    run_command("EVENT LOG", 20, "logcat", "-b", "events", "-v", "threadtime", "-d", "*:v", NULL);
+    run_command("RADIO LOG", 20, "logcat", "-b", "radio", "-v", "threadtime", "-d", "*:v", NULL);
 
     run_command("NETWORK INTERFACES", 10, "netcfg", NULL);
     dump_file("NETWORK ROUTES", "/proc/net/route");
@@ -174,6 +174,14 @@
        to increase its timeout.  we really need to do the timeouts in
        dumpsys itself... */
     run_command("DUMPSYS", 60, "dumpsys", NULL);
+
+    printf("========================================================\n");
+    printf("== Application Services\n");
+    printf("========================================================\n");
+
+    /* Instead of a 60s timeout, we should give each service a 5 second timeout */
+    run_command("APP SERVICES", 60, "dumpsys", "activity", "service", NULL);
+
 }
 
 static void usage() {
@@ -239,19 +247,21 @@
         fclose(cmdline);
     }
 
-    /* switch to non-root user and group */
-    gid_t groups[] = { AID_LOG, AID_SDCARD_RW, AID_MOUNT };
-    if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) {
-        LOGE("Unable to setgroups, aborting: %s\n", strerror(errno));
-        return -1;
-    }
-    if (setgid(AID_SHELL) != 0) {
-        LOGE("Unable to setgid, aborting: %s\n", strerror(errno));
-        return -1;
-    }
-    if (setuid(AID_SHELL) != 0) {
-        LOGE("Unable to setuid, aborting: %s\n", strerror(errno));
-        return -1;
+    if (getuid() == 0) {
+        /* switch to non-root user and group */
+        gid_t groups[] = { AID_LOG, AID_SDCARD_RW, AID_MOUNT };
+        if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) {
+            LOGE("Unable to setgroups, aborting: %s\n", strerror(errno));
+            return -1;
+        }
+        if (setgid(AID_SHELL) != 0) {
+            LOGE("Unable to setgid, aborting: %s\n", strerror(errno));
+            return -1;
+        }
+        if (setuid(AID_SHELL) != 0) {
+            LOGE("Unable to setuid, aborting: %s\n", strerror(errno));
+            return -1;
+        }
     }
 
     char path[PATH_MAX], tmp_path[PATH_MAX];
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index a5b3e0e..2f03c7a 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -946,14 +946,12 @@
     const size_t libdirLen = strlen(dataDir) + strlen(PKG_LIB_POSTFIX);
     if (libdirLen >= PKG_PATH_MAX) {
         LOGE("library dir len too large");
-        rc = -1;
-        goto out;
+        return -1;
     }
 
     if (snprintf(libdir, sizeof(libdir), "%s%s", dataDir, PKG_LIB_POSTFIX) != (ssize_t)libdirLen) {
         LOGE("library dir not written successfully: %s\n", strerror(errno));
-        rc = -1;
-        goto out;
+        return -1;
     }
 
     if (stat(dataDir, &s) < 0) return -1;
diff --git a/core/java/android/animation/TimeInterpolator.java b/core/java/android/animation/TimeInterpolator.java
index 8d795a8..0f5d8bf 100644
--- a/core/java/android/animation/TimeInterpolator.java
+++ b/core/java/android/animation/TimeInterpolator.java
@@ -23,7 +23,7 @@
 public interface TimeInterpolator {
 
     /**
-     * Maps a value representing the elapsed fraciton of an animation to a value that represents
+     * Maps a value representing the elapsed fraction of an animation to a value that represents
      * the interpolated fraction. This interpolated value is then multiplied by the change in
      * value of an animation to derive the animated value at the current elapsed animation time.
      *
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 79754b8..e269c31 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -523,7 +523,6 @@
             for (int i = 0; i < numValues; ++i) {
                 mValues[i].init();
             }
-            mCurrentIteration = 0;
             mInitialized = true;
         }
     }
@@ -933,6 +932,7 @@
             // This sets the initial value of the animation, prior to actually starting it running
             setCurrentPlayTime(getCurrentPlayTime());
         }
+        mCurrentIteration = 0;
         mPlayingState = STOPPED;
         mStartedDelay = false;
         sPendingAnimations.add(this);
@@ -1215,4 +1215,16 @@
         void onAnimationUpdate(ValueAnimator animation);
 
     }
-}
\ No newline at end of file
+
+    /**
+     * Return the number of animations currently running.
+     *
+     * Used by StrictMode internally to annotate violations.  Only
+     * called on the main thread.
+     *
+     * @hide
+     */
+    public static int getCurrentAnimationsCount() {
+        return sAnimations.size();
+    }
+}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 5ae8a1f..fe1e7d7 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -797,10 +797,12 @@
         public static final int IMPORTANCE_PERCEPTIBLE = 130;
         
         /**
-         * Constant for {@link #importance}: this process is running a
-         * heavy-weight application and thus should not be killed.
+         * Constant for {@link #importance}: this process is running an
+         * application that can not save its state, and thus can't be killed
+         * while in the background.
+         * @hide
          */
-        public static final int IMPORTANCE_HEAVY_WEIGHT = 170;
+        public static final int IMPORTANCE_CANT_SAVE_STATE = 170;
         
         /**
          * Constant for {@link #importance}: this process is contains services
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index d7a0412..de36f27 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -5,7 +5,6 @@
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
@@ -48,9 +47,22 @@
  */
 public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
         InputQueue.Callback, OnGlobalLayoutListener {
+    /**
+     * Optional meta-that can be in the manifest for this component, specifying
+     * the name of the native shared library to load.  If not specified,
+     * "main" is used.
+     */
     public static final String META_DATA_LIB_NAME = "android.app.lib_name";
     
-    public static final String KEY_NATIVE_SAVED_STATE = "android:native_state";
+    /**
+     * Optional meta-that can be in the manifest for this component, specifying
+     * the name of the main entry point for this native activity in the
+     * {@link #META_DATA_LIB_NAME} native code.  If not specified,
+     * "ANativeActivity_onCreate" is used.
+     */
+    public static final String META_DATA_FUNC_NAME = "android.app.func_name";
+    
+    private static final String KEY_NATIVE_SAVED_STATE = "android:native_state";
 
     private NativeContentView mNativeContentView;
     private InputMethodManager mIMM;
@@ -71,7 +83,7 @@
 
     private boolean mDestroyed;
     
-    private native int loadNativeCode(String path, MessageQueue queue,
+    private native int loadNativeCode(String path, String funcname, MessageQueue queue,
             String internalDataPath, String externalDataPath, int sdkVersion,
             AssetManager assetMgr, byte[] savedState);
     private native void unloadNativeCode(int handle);
@@ -131,6 +143,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         String libname = "main";
+        String funcname = "ANativeActivity_onCreate";
         ActivityInfo ai;
         
         mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -155,6 +168,8 @@
             if (ai.metaData != null) {
                 String ln = ai.metaData.getString(META_DATA_LIB_NAME);
                 if (ln != null) libname = ln;
+                ln = ai.metaData.getString(META_DATA_FUNC_NAME);
+                if (ln != null) funcname = ln;
             }
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException("Error getting activity info", e);
@@ -162,15 +177,10 @@
         
         String path = null;
         
-        if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) == 0) {
-            // If the application does not have (Java) code, then no ClassLoader
-            // has been set up for it.  We will need to do our own search for
-            // the native code.
-            File libraryFile = new File(ai.applicationInfo.nativeLibraryDir,
-                    System.mapLibraryName(libname));
-            if (libraryFile.exists()) {
-                path = libraryFile.getPath();
-            }
+        File libraryFile = new File(ai.applicationInfo.nativeLibraryDir,
+                System.mapLibraryName(libname));
+        if (libraryFile.exists()) {
+            path = libraryFile.getPath();
         }
         
         if (path == null) {
@@ -180,7 +190,7 @@
         byte[] nativeSavedState = savedInstanceState != null
                 ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;
 
-        mNativeHandle = loadNativeCode(path, Looper.myQueue(),
+        mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(),
                  getFilesDir().toString(),
                  Environment.getExternalStorageAppFilesDirectory(ai.packageName).toString(),
                  Build.VERSION.SDK_INT, getAssets(), nativeSavedState);
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 920ef89..61b4303 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -26,11 +26,8 @@
 import android.server.BluetoothA2dpService;
 import android.util.Log;
 
-import java.util.Collections;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
 
 
 /**
@@ -167,35 +164,35 @@
     /**
      * {@inheritDoc}
      */
-    public Set<BluetoothDevice> getConnectedDevices() {
+    public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         if (mService != null && isEnabled()) {
             try {
-                return toDeviceSet(mService.getConnectedDevices());
+                return mService.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return toDeviceSet(new BluetoothDevice[0]);
+                return new ArrayList<BluetoothDevice>();
             }
         }
         if (mService == null) Log.w(TAG, "Proxy not attached to service");
-        return toDeviceSet(new BluetoothDevice[0]);
+        return new ArrayList<BluetoothDevice>();
     }
 
     /**
      * {@inheritDoc}
      */
-    public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         if (mService != null && isEnabled()) {
             try {
-                return toDeviceSet(mService.getDevicesMatchingConnectionStates(states));
+                return mService.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return toDeviceSet(new BluetoothDevice[0]);
+                return new ArrayList<BluetoothDevice>();
             }
         }
         if (mService == null) Log.w(TAG, "Proxy not attached to service");
-        return toDeviceSet(new BluetoothDevice[0]);
+        return new ArrayList<BluetoothDevice>();
     }
 
     /**
@@ -396,11 +393,6 @@
        return false;
     }
 
-    private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) {
-       return Collections.unmodifiableSet(
-          new HashSet<BluetoothDevice>(Arrays.asList(devices)));
-    }
-
     private static void log(String msg) {
       Log.d(TAG, msg);
     }
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index b33ab21..fd8f930 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -321,7 +321,7 @@
                               mA2dpService.getDevicesMatchingConnectionStates(
                                   new int[] {BluetoothA2dp.STATE_CONNECTED,
                                              BluetoothProfile.STATE_CONNECTING,
-                                             BluetoothProfile.STATE_DISCONNECTING}).length == 0) {
+                                             BluetoothProfile.STATE_DISCONNECTING}).size() == 0) {
                             mA2dpService.connect(mDevice);
                         }
                         if (mService.getInputDevicePriority(mDevice) ==
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 0496b1f..c64fdbe 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -18,20 +18,16 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.os.RemoteException;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Public API for controlling the Bluetooth Headset Service. This includes both
@@ -218,35 +214,35 @@
     /**
      * {@inheritDoc}
      */
-    public Set<BluetoothDevice> getConnectedDevices() {
+    public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         if (mService != null && isEnabled()) {
             try {
-                return toDeviceSet(mService.getConnectedDevices());
+                return mService.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return toDeviceSet(new BluetoothDevice[0]);
+                return new ArrayList<BluetoothDevice>();
             }
         }
         if (mService == null) Log.w(TAG, "Proxy not attached to service");
-        return toDeviceSet(new BluetoothDevice[0]);
+        return new ArrayList<BluetoothDevice>();
     }
 
     /**
      * {@inheritDoc}
      */
-    public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         if (mService != null && isEnabled()) {
             try {
-                return toDeviceSet(mService.getDevicesMatchingConnectionStates(states));
+                return mService.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return toDeviceSet(new BluetoothDevice[0]);
+                return new ArrayList<BluetoothDevice>();
             }
         }
         if (mService == null) Log.w(TAG, "Proxy not attached to service");
-        return toDeviceSet(new BluetoothDevice[0]);
+        return new ArrayList<BluetoothDevice>();
     }
 
     /**
@@ -569,11 +565,6 @@
        return false;
     }
 
-    private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) {
-       return Collections.unmodifiableSet(
-          new HashSet<BluetoothDevice>(Arrays.asList(devices)));
-    }
-
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index 1793838..bc8a836 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -24,10 +24,8 @@
 import android.os.ServiceManager;
 import android.util.Log;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Public API for controlling the Bluetooth HID (Input Device) Profile
@@ -167,18 +165,16 @@
 
     /** Check if any Input Device is connected.
      *
-     * @return a unmodifiable set of connected Input Devices, or null on error.
+     * @return List of devices, empty List on error.
      * @hide
      */
-    public Set<BluetoothDevice> getConnectedInputDevices() {
+    public List<BluetoothDevice> getConnectedInputDevices() {
         if (DBG) log("getConnectedInputDevices()");
         try {
-            return Collections.unmodifiableSet(
-                    new HashSet<BluetoothDevice>(
-                        Arrays.asList(mService.getConnectedInputDevices())));
+            return mService.getConnectedInputDevices();
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
-            return null;
+            return new ArrayList<BluetoothDevice>();
         }
     }
 
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 9d0b3f2..f55e96a 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -19,15 +19,13 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
-import android.os.ServiceManager;
-import android.os.RemoteException;
 import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @hide
@@ -154,18 +152,16 @@
    *
    * Does not include devices that are currently connecting or disconnecting
    *
-   * @return a unmodifiable set of connected PAN Devices, or null on error.
+   * @return List of PAN devices or empty on Error
    * @hide
    */
-   public Set<BluetoothDevice> getConnectedDevices() {
+   public List<BluetoothDevice> getConnectedDevices() {
       if (DBG) log("getConnectedDevices");
       try {
-          return Collections.unmodifiableSet(
-                  new HashSet<BluetoothDevice>(
-                      Arrays.asList(mService.getConnectedPanDevices())));
+          return mService.getConnectedPanDevices();
       } catch (RemoteException e) {
           Log.e(TAG, "", e);
-          return null;
+          return new ArrayList<BluetoothDevice>();
       }
    }
 
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 3b4c84c..3949b26 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -17,10 +17,7 @@
 
 package android.bluetooth;
 
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-
-import java.util.Set;
+import java.util.List;
 
 /**
  * Public APIs for the Bluetooth Profiles.
@@ -150,9 +147,9 @@
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
-     * @return An unmodifiable set of devices. The set will be empty on error.
+     * @return List of devices. The list will be empty on error.
      */
-    public Set<BluetoothDevice> getConnectedDevices();
+    public List<BluetoothDevice> getConnectedDevices();
 
     /**
      * Get a set of devices that match any of the given connection
@@ -166,9 +163,9 @@
      * @param states Array of states. States can be one of
      *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
      *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
-     * @return An unmodifiable set of devices. The set will be empty on error.
+     * @return List of devices. The list will be empty on error.
      */
-    public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states);
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states);
 
     /**
      * Get the current connection state of the profile
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index cc23146..f0252b7 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -81,7 +81,7 @@
     // HID profile APIs
     boolean connectInputDevice(in BluetoothDevice device);
     boolean disconnectInputDevice(in BluetoothDevice device);
-    BluetoothDevice[] getConnectedInputDevices();  // change to Set<> once AIDL supports
+    List<BluetoothDevice> getConnectedInputDevices();
     int getInputDeviceState(in BluetoothDevice device);
     boolean setInputDevicePriority(in BluetoothDevice device, int priority);
     int getInputDevicePriority(in BluetoothDevice device);
@@ -89,7 +89,7 @@
     boolean isTetheringOn();
     void setBluetoothTethering(boolean value);
     int getPanDeviceState(in BluetoothDevice device);
-    BluetoothDevice[] getConnectedPanDevices();
+    List<BluetoothDevice> getConnectedPanDevices();
     boolean connectPanDevice(in BluetoothDevice device);
     boolean disconnectPanDevice(in BluetoothDevice device);
 }
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index c5044c2..b4fc366 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -27,9 +27,8 @@
     // Public API
     boolean connect(in BluetoothDevice device);
     boolean disconnect(in BluetoothDevice device);
-    // change to Set<> once AIDL supports
-    BluetoothDevice[] getConnectedDevices();
-    BluetoothDevice[] getDevicesMatchingConnectionStates(in int[] states);
+    List<BluetoothDevice> getConnectedDevices();
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
     int getConnectionState(in BluetoothDevice device);
     boolean setPriority(in BluetoothDevice device, int priority);
     int getPriority(in BluetoothDevice device);
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 8bcf103..3e4c7b4 100644
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -27,9 +27,8 @@
     // Public API
     boolean connect(in BluetoothDevice device);
     boolean disconnect(in BluetoothDevice device);
-    // Change to Set<> when AIDL supports
-    BluetoothDevice[] getConnectedDevices();
-    BluetoothDevice[] getDevicesMatchingConnectionStates(in int[] states);
+    List<BluetoothDevice> getConnectedDevices();
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
     int getConnectionState(in BluetoothDevice device);
     boolean setPriority(in BluetoothDevice device, int priority);
     int getPriority(in BluetoothDevice device);
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 643e747..bb0ed6a 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -252,9 +252,6 @@
     public static final int FLAG_RESTORE_ANY_VERSION = 1<<17;
 
     /**
-     * Value for {@link #flags}: Set to true if the application has been
-     * installed using the forward lock option.
-     *
      * Value for {@link #flags}: Set to true if the application is
      * currently installed on external/removable/unprotected storage.  Such
      * applications may not be available if their storage is not currently
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 89c9d25..54dbe37 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1076,6 +1076,16 @@
 
                 XmlUtils.skipCurrentTag(parser);
                 
+            } else if (tagName.equals("uses-gl-texture")) {
+                // Just skip this tag
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+                
+            } else if (tagName.equals("compatible-screens")) {
+                // Just skip this tag
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+                
             } else if (tagName.equals("eat-comment")) {
                 // Just skip this tag
                 XmlUtils.skipCurrentTag(parser);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 6ff5a40..275e2eb 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -779,9 +779,9 @@
      * Set the clockwise rotation of preview display in degrees. This affects
      * the preview frames and the picture displayed after snapshot. This method
      * is useful for portrait mode applications. Note that preview display of
-     * front-facing cameras is flipped horizontally, that is, the image is
-     * reflected along the central vertical axis of the camera sensor. So the
-     * users can see themselves as looking into a mirror.
+     * front-facing cameras is flipped horizontally before the rotation, that
+     * is, the image is reflected along the central vertical axis of the camera
+     * sensor. So the users can see themselves as looking into a mirror.
      *
      * This does not affect the order of byte array passed in {@link
      * PreviewCallback#onPreviewFrame}, JPEG pictures, or recorded videos. This
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 2c5c909..98bf632 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -177,12 +177,12 @@
      *  <h4>{@link android.hardware.Sensor#TYPE_ROTATION_VECTOR Sensor.TYPE_ROTATION_VECTOR}:</h4>
      *  The rotation vector represents the orientation of the device as a combination of an angle
      *  and an axis, in which the device has rotated through an angle theta around an axis
-     *  <x, y, z>. The three elements of the rotation vector are
-     *  <x*sin(theta/2), y*sin(theta/2), z*sin(theta/2)>, such that the magnitude of the rotation
+     *  &lt;x, y, z>. The three elements of the rotation vector are
+     *  &lt;x*sin(theta/2), y*sin(theta/2), z*sin(theta/2)>, such that the magnitude of the rotation
      *  vector is equal to sin(theta/2), and the direction of the rotation vector is equal to the
      *  direction of the axis of rotation. The three elements of the rotation vector are equal to
      *  the last three components of a unit quaternion
-     *  <cos(theta/2), x*sin(theta/2), y*sin(theta/2), z*sin(theta/2)>.  Elements of the rotation
+     *  &lt;cos(theta/2), x*sin(theta/2), y*sin(theta/2), z*sin(theta/2)>.  Elements of the rotation
      *  vector are unitless.  The x,y, and z axis are defined in the same way as the acceleration
      *  sensor.
      *
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index 503c470..218df75 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -129,57 +129,6 @@
             }
         }
 
-        // Clean up the certificates chain and build a new one.
-        // Theoretically, we shouldn't have to do this, but various web servers
-        // in practice are mis-configured to have out-of-order certificates or
-        // expired self-issued root certificate.
-        int chainLength = serverCertificates.length;
-        if (serverCertificates.length > 1) {
-          // 1. we clean the received certificates chain.
-          // We start from the end-entity certificate, tracing down by matching
-          // the "issuer" field and "subject" field until we can't continue.
-          // This helps when the certificates are out of order or
-          // some certificates are not related to the site.
-          int currIndex;
-          for (currIndex = 0; currIndex < serverCertificates.length; ++currIndex) {
-            boolean foundNext = false;
-            for (int nextIndex = currIndex + 1;
-                 nextIndex < serverCertificates.length;
-                 ++nextIndex) {
-              if (serverCertificates[currIndex].getIssuerDN().equals(
-                  serverCertificates[nextIndex].getSubjectDN())) {
-                foundNext = true;
-                // Exchange certificates so that 0 through currIndex + 1 are in proper order
-                if (nextIndex != currIndex + 1) {
-                  X509Certificate tempCertificate = serverCertificates[nextIndex];
-                  serverCertificates[nextIndex] = serverCertificates[currIndex + 1];
-                  serverCertificates[currIndex + 1] = tempCertificate;
-                }
-                break;
-              }
-            }
-            if (!foundNext) break;
-          }
-
-          // 2. we exam if the last traced certificate is self issued and it is expired.
-          // If so, we drop it and pass the rest to checkServerTrusted(), hoping we might
-          // have a similar but unexpired trusted root.
-          chainLength = currIndex + 1;
-          X509Certificate lastCertificate = serverCertificates[chainLength - 1];
-          Date now = new Date();
-          if (lastCertificate.getSubjectDN().equals(lastCertificate.getIssuerDN())
-              && now.after(lastCertificate.getNotAfter())) {
-            --chainLength;
-          }
-        }
-
-        // 3. Now we copy the newly built chain into an appropriately sized array.
-        X509Certificate[] newServerCertificates = null;
-        newServerCertificates = new X509Certificate[chainLength];
-        for (int i = 0; i < chainLength; ++i) {
-          newServerCertificates[i] = serverCertificates[i];
-        }
-
         // first, we validate the new chain using the standard validation
         // solution; if we do not find any errors, we are done; if we
         // fail the standard validation, we re-validate again below,
@@ -188,7 +137,7 @@
         //
         try {
             SSLParametersImpl.getDefaultTrustManager().checkServerTrusted(
-                newServerCertificates, "RSA");
+                serverCertificates, "RSA");
 
             // no errors!!!
             return null;
diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl
index 2171434..13b97d6 100644
--- a/core/java/android/nfc/INfcTag.aidl
+++ b/core/java/android/nfc/INfcTag.aidl
@@ -28,6 +28,7 @@
     String getType(int nativeHandle);
     byte[] getUid(int nativeHandle);
     boolean isNdef(int nativeHandle);
+    boolean isPresent(int nativeHandle);
     byte[] transceive(int nativeHandle, in byte[] data);
 
     int getLastError(int nativeHandle);
diff --git a/core/java/android/nfc/NdefMessage.java b/core/java/android/nfc/NdefMessage.java
index 378304e..feca94e 100644
--- a/core/java/android/nfc/NdefMessage.java
+++ b/core/java/android/nfc/NdefMessage.java
@@ -34,15 +34,12 @@
 
     private final NdefRecord[] mRecords;
 
-    //TODO(npelly) FormatException
     /**
      * Create an NDEF message from raw bytes.
      * <p>
      * Validation is performed to make sure the Record format headers are valid,
      * and the ID + TYPE + PAYLOAD fields are of the correct size.
      * @throws FormatException
-     *
-     * @hide
      */
     public NdefMessage(byte[] data) throws FormatException {
         mRecords = null;  // stop compiler complaints about final field
@@ -69,10 +66,7 @@
     }
 
     /**
-     * Get a byte array representation of this NDEF message.
-     *
-     * @return byte array
-     * @hide
+     * Returns a byte array representation of this entire NDEF message.
      */
     public byte[] toByteArray() {
         //TODO(nxp): do not return null
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index edc5ab9..557e44d 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -247,8 +247,7 @@
     }
 
     /**
-     * Return this NDEF Record as a byte array.
-     * @hide
+     * Returns this entire NDEF Record as a byte array.
      */
     public byte[] toByteArray() {
         return generate(mFlags, mTnf, mType, mId, mPayload);
diff --git a/core/java/android/nfc/NdefTag.java b/core/java/android/nfc/NdefTag.java
index 45cdc31..d8681dc 100644
--- a/core/java/android/nfc/NdefTag.java
+++ b/core/java/android/nfc/NdefTag.java
@@ -16,8 +16,6 @@
 
 package android.nfc;
 
-import java.util.HashMap;
-
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -33,19 +31,92 @@
  * is possible for {@link NdefTag}s to contain multiple {@link NdefMessage}s.
  * <p>{@link NfcAdapter#createNdefTagConnection createNdefTagConnection()} can be used to modify the
  * contents of some tags.
- * <p>This is an immutable data class.
+ * <p>This is an immutable data class. All properties are set at Tag discovery
+ * time and calls on this class will retrieve those read-only properties, and
+ * not cause any further RF activity or block. Note however that arrays passed to and
+ * returned by this class are *not* cloned, so be careful not to modify them.
  */
 public class NdefTag extends Tag implements Parcelable {
-    private final NdefMessage[] mMessages;
+    /**
+     * Target for NFC Forum Type 1 compliant tag.
+     * <p>This is based on Jewel/Topaz technology
+     */
+    public static final String TARGET_TYPE_1 = "type_1";
 
     /**
-     * Hidden constructor to be used by NFC service when a
-     * tag is discovered and by Parcelable methods.
+     * Target for NFC Forum Type 2 compliant tag.
+     * <p>This is based on Mifare Ultralight technology.
+     */
+    public static final String TARGET_TYPE_2 = "type_2";
+
+    /**
+     * Target for NFC Forum Type 3 compliant tag.
+     * <p>This is based on Felica technology.
+     */
+    public static final String TARGET_TYPE_3 = "type_3";
+
+    /**
+     * Target for NFC Forum Type 4 compliant tag.
+     * <p>This is based on Mifare Desfire technology.
+     */
+    public static final String TARGET_TYPE_4 = "type_4";
+
+    /**
+     * Target for NFC Forum Enabled: Mifare Classic tag.
+     * <p>This is not strictly a NFC Forum tag type, but is a common
+     * NDEF message container.
+     */
+    public static final String TARGET_MIFARE_CLASSIC = "type_mifare_classic";
+
+    /**
+     * Any other target.
+     */
+    public static final String TARGET_OTHER = "other";
+
+    private final String[] mNdefTargets;
+    private final NdefMessage[][] mMessages;  // one NdefMessage[] per NDEF target
+    private NdefMessage[] mFlatMessages;  // collapsed mMessages, built lazily, protected by (this)
+
+    /**
+     * Hidden constructor to be used by NFC service only.
      * @hide
      */
-    public NdefTag(String typeName, byte[] uid, int nativeHandle, NdefMessage[] messages) {
-        super(typeName, true, uid, nativeHandle);
-        mMessages = messages.clone();
+    public NdefTag(byte[] id, String[] rawTargets, byte[] pollBytes, byte[] activationBytes,
+            int serviceHandle, String[] ndefTargets, NdefMessage[][] messages) {
+        super(id, true, rawTargets, pollBytes, activationBytes, serviceHandle);
+        if (ndefTargets == null || messages == null) {
+            throw new IllegalArgumentException("ndefTargets or messages cannot be null");
+        }
+        if (ndefTargets.length != messages.length){
+            throw new IllegalArgumentException("ndefTargets and messages arrays must match");
+        }
+        for (NdefMessage[] ms : messages) {
+            if (ms == null) {
+                throw new IllegalArgumentException("messages elements cannot be null");
+            }
+        }
+        mNdefTargets = ndefTargets;
+        mMessages = messages;
+    }
+
+    /**
+     * Construct a mock NdefTag.
+     * <p>This is an application constructed tag, so NfcAdapter methods on this
+     * Tag such as {@link NfcAdapter#createRawTagConnection} will fail with
+     * {@link IllegalArgumentException} since it does not represent a physical Tag.
+     * <p>This constructor might be useful for mock testing.
+     * @param id The tag identifier, can be null
+     * @param rawTargets must not be null
+     * @param pollBytes can be null
+     * @param activationBytes can be null
+     * @param ndefTargets NDEF target array, such as {TARGET_TYPE_2}, cannot be null
+     * @param messages messages, one array per NDEF target, cannot be null
+     * @return freshly constructed NdefTag
+     */
+    public static NdefTag createMockNdefTag(byte[] id, String[] rawTargets, byte[] pollBytes,
+            byte[] activationBytes, String[] ndefTargets, NdefMessage[][] messages) {
+        // set serviceHandle to 0 to indicate mock tag
+        return new NdefTag(id, rawTargets, pollBytes, activationBytes, 0, ndefTargets, messages);
     }
 
     /**
@@ -59,7 +130,29 @@
      * @return NDEF Messages found at Tag discovery
      */
     public NdefMessage[] getNdefMessages() {
-        return mMessages.clone();
+        // common-case optimization
+        if (mMessages.length == 1) {
+            return mMessages[0];
+        }
+
+        // return cached flat array
+        synchronized(this) {
+            if (mFlatMessages != null) {
+                return mFlatMessages;
+            }
+            // not cached - build a flat array
+            int sz = 0;
+            for (NdefMessage[] ms : mMessages) {
+                sz += ms.length;
+            }
+            mFlatMessages = new NdefMessage[sz];
+            int i = 0;
+            for (NdefMessage[] ms : mMessages) {
+                System.arraycopy(ms, 0, mFlatMessages, i, ms.length);
+                i += ms.length;
+            }
+            return mFlatMessages;
+        }
     }
 
     /**
@@ -70,58 +163,25 @@
      * <p>
      * Most tags only contain a single NDEF message.
      *
-     * @param target One of targets strings provided by getNdefTargets()
+     * @param target one of targets strings provided by getNdefTargets()
      * @return NDEF Messages found at Tag discovery
      */
     public NdefMessage[] getNdefMessages(String target) {
-        // TODO: handle multiprotocol
-        String[] localTypes = convertToNdefType(mTypeName);
-        if (!target.equals(localTypes[0])) {
-            throw new IllegalArgumentException();
+        for (int i=0; i<mNdefTargets.length; i++) {
+            if (target.equals(mNdefTargets[i])) {
+                return mMessages[i];
+            }
         }
-        return getNdefMessages();
-    }
-
-    /** TODO(npelly):
-     * - check that any single tag can only have one of each NDEF type
-     * - ok to include mifare_classic?
-     */
-    public static final String TARGET_TYPE_1 = "type_1";
-    public static final String TARGET_TYPE_2 = "type_2";
-    public static final String TARGET_TYPE_3 = "type_3";
-    public static final String TARGET_TYPE_4 = "type_4";
-    public static final String TARGET_MIFARE_CLASSIC = "type_mifare_classic";
-    public static final String TARGET_OTHER = "other";
-
-    private static final HashMap<String, String[]> NDEF_TYPES_CONVERTION_TABLE = new HashMap<String, String[]>() {
-        {
-            // TODO: handle multiprotocol
-            // TODO: move INTERNAL_TARGET_Type to TARGET_TYPE mapping to NFC service
-            put(Tag.INTERNAL_TARGET_TYPE_JEWEL, new String[] { NdefTag.TARGET_TYPE_1 });
-            put(Tag.INTERNAL_TARGET_TYPE_MIFARE_UL, new String[] { NdefTag.TARGET_TYPE_2 });
-            put(Tag.INTERNAL_TARGET_TYPE_MIFARE_1K, new String[] { NdefTag.TARGET_MIFARE_CLASSIC });
-            put(Tag.INTERNAL_TARGET_TYPE_MIFARE_4K, new String[] { NdefTag.TARGET_MIFARE_CLASSIC });
-            put(Tag.INTERNAL_TARGET_TYPE_FELICA, new String[] { NdefTag.TARGET_TYPE_3 });
-            put(Tag.INTERNAL_TARGET_TYPE_ISO14443_4, new String[] { NdefTag.TARGET_TYPE_4 });
-            put(Tag.INTERNAL_TARGET_TYPE_MIFARE_DESFIRE, new String[] { NdefTag.TARGET_TYPE_4 });
-        }
-    };
-
-    private String[] convertToNdefType(String internalTypeName) {
-        String[] result =  NDEF_TYPES_CONVERTION_TABLE.get(internalTypeName);
-        if (result == null) {
-            return new String[] { NdefTag.TARGET_OTHER };
-        }
-        return result;
+        throw new IllegalArgumentException("target (" + target + ") not found");
     }
 
     /**
-     * Return the
+     * Return the NDEF targets on this Tag that support NDEF messages.
      *
      * @return
      */
     public String[] getNdefTargets() {
-        return convertToNdefType(mTypeName);
+        return mNdefTargets;
     }
 
     @Override
@@ -131,19 +191,50 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
+        // Tag fields
+        dest.writeInt(mIsNdef ? 1 : 0);
+        writeBytesWithNull(dest, mId);
+        dest.writeInt(mRawTargets.length);
+        dest.writeStringArray(mRawTargets);
+        writeBytesWithNull(dest, mPollBytes);
+        writeBytesWithNull(dest, mActivationBytes);
+        dest.writeInt(mServiceHandle);
+
+        // NdefTag fields
+        dest.writeInt(mNdefTargets.length);
+        dest.writeStringArray(mNdefTargets);
         dest.writeInt(mMessages.length);
-        dest.writeTypedArray(mMessages, flags);
+        for (NdefMessage[] ms : mMessages) {
+            dest.writeTypedArray(ms, flags);
+        }
     }
 
     public static final Parcelable.Creator<NdefTag> CREATOR =
             new Parcelable.Creator<NdefTag>() {
         public NdefTag createFromParcel(Parcel in) {
-            Tag tag = Tag.CREATOR.createFromParcel(in);
-            int messagesLength = in.readInt();
-            NdefMessage[] messages = new NdefMessage[messagesLength];
-            in.readTypedArray(messages, NdefMessage.CREATOR);
-            return new NdefTag(tag.mTypeName, tag.mUid, tag.mNativeHandle, messages);
+            boolean isNdef = (in.readInt() == 1);
+            if (!isNdef) {
+                throw new IllegalArgumentException("Creating NdefTag from Tag parcel");
+            }
+
+            // Tag fields
+            byte[] id = readBytesWithNull(in);
+            String[] rawTargets = new String[in.readInt()];
+            in.readStringArray(rawTargets);
+            byte[] pollBytes = readBytesWithNull(in);
+            byte[] activationBytes = readBytesWithNull(in);
+            int serviceHandle = in.readInt();
+
+            // NdefTag fields
+            String[] ndefTargets = new String[in.readInt()];
+            in.readStringArray(ndefTargets);
+            NdefMessage[][] messages = new NdefMessage[in.readInt()][];
+            for (int i=0; i<messages.length; i++) {
+                messages[i] = new NdefMessage[in.readInt()];
+                in.readTypedArray(messages[i], NdefMessage.CREATOR);
+            }
+            return new NdefTag(id, rawTargets, pollBytes, activationBytes, serviceHandle,
+                    ndefTargets, messages);
         }
         public NdefTag[] newArray(int size) {
             return new NdefTag[size];
diff --git a/core/java/android/nfc/NdefTagConnection.java b/core/java/android/nfc/NdefTagConnection.java
index 4795fa7..321b0ec 100644
--- a/core/java/android/nfc/NdefTagConnection.java
+++ b/core/java/android/nfc/NdefTagConnection.java
@@ -81,9 +81,9 @@
         //TODO(nxp): do not use getLastError(), it is racy
         try {
             NdefMessage[] msgArray = new NdefMessage[1];
-            NdefMessage msg = mTagService.read(mTag.mNativeHandle);
+            NdefMessage msg = mTagService.read(mTag.mServiceHandle);
             if (msg == null) {
-                int errorCode = mTagService.getLastError(mTag.mNativeHandle);
+                int errorCode = mTagService.getLastError(mTag.mServiceHandle);
                 switch (errorCode) {
                     case ErrorCodes.ERROR_IO:
                         throw new IOException();
@@ -121,7 +121,7 @@
      */
     public void writeNdefMessage(NdefMessage message) throws IOException, FormatException {
         try {
-            int errorCode = mTagService.write(mTag.mNativeHandle, message);
+            int errorCode = mTagService.write(mTag.mServiceHandle, message);
             switch (errorCode) {
                 case ErrorCodes.SUCCESS:
                     break;
@@ -148,7 +148,7 @@
      */
     public boolean makeReadOnly() throws IOException {
         try {
-            int errorCode = mTagService.makeReadOnly(mTag.mNativeHandle);
+            int errorCode = mTagService.makeReadOnly(mTag.mServiceHandle);
             switch (errorCode) {
                 case ErrorCodes.SUCCESS:
                     return true;
@@ -175,7 +175,7 @@
      */
     public int getModeHint() throws IOException {
         try {
-            int result = mTagService.getModeHint(mTag.mNativeHandle);
+            int result = mTagService.getModeHint(mTag.mServiceHandle);
             if (ErrorCodes.isError(result)) {
                 switch (result) {
                     case ErrorCodes.ERROR_IO:
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index bc3c6d9..7f4b4a2 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -227,14 +227,17 @@
     }
 
     /**
-     * Return true if this NFC Adapter is enabled to discover new tags.
+     * Return true if this NFC Adapter has any features enabled.
      * <p>
      * If this method returns false, then applications should request the user
      * turn on NFC tag discovery in Settings.
+     * <p>
+     * If this method returns false, the NFC hardware is guaranteed not to
+     * perform or respond to any NFC communication.
      *
      * @return true if this NFC Adapter is enabled to discover new tags
      */
-    public boolean isTagDiscoveryEnabled() {
+    public boolean isEnabled() {
         try {
             return mService.isEnabled();
         } catch (RemoteException e) {
@@ -244,12 +247,14 @@
     }
 
     /**
+     * Enable NFC hardware.
+     * <p>
      * NOTE: may block for ~second or more.  Poor API.  Avoid
      * calling from the UI thread.
      *
      * @hide
      */
-    public boolean enableTagDiscovery() {
+    public boolean enable() {
         try {
             return mService.enable();
         } catch (RemoteException e) {
@@ -259,12 +264,16 @@
     }
 
     /**
+     * Disable NFC hardware.
+     * No NFC features will work after this call, and the hardware
+     * will not perform or respond to any NFC communication.
+     * <p>
      * NOTE: may block for ~second or more.  Poor API.  Avoid
      * calling from the UI thread.
      *
      * @hide
      */
-    public boolean disableTagDiscovery() {
+    public boolean disable() {
         try {
             return mService.disable();
         } catch (RemoteException e) {
@@ -318,6 +327,9 @@
      * <p>Requires {@link android.Manifest.permission#NFC} permission.
      */
     public RawTagConnection createRawTagConnection(Tag tag) {
+        if (tag.mServiceHandle == 0) {
+            throw new IllegalArgumentException("mock tag cannot be used for connections");
+        }
         try {
             return new RawTagConnection(mService, tag);
         } catch (RemoteException e) {
@@ -331,6 +343,9 @@
      * <p>Requires {@link android.Manifest.permission#NFC} permission.
      */
     public RawTagConnection createRawTagConnection(Tag tag, String target) {
+        if (tag.mServiceHandle == 0) {
+            throw new IllegalArgumentException("mock tag cannot be used for connections");
+        }
         try {
             return new RawTagConnection(mService, tag, target);
         } catch (RemoteException e) {
@@ -344,6 +359,9 @@
      * <p>Requires {@link android.Manifest.permission#NFC} permission.
      */
     public NdefTagConnection createNdefTagConnection(NdefTag tag) {
+        if (tag.mServiceHandle == 0) {
+            throw new IllegalArgumentException("mock tag cannot be used for connections");
+        }
         try {
             return new NdefTagConnection(mService, tag);
         } catch (RemoteException e) {
@@ -357,6 +375,9 @@
      * <p>Requires {@link android.Manifest.permission#NFC} permission.
      */
     public NdefTagConnection createNdefTagConnection(NdefTag tag, String target) {
+        if (tag.mServiceHandle == 0) {
+            throw new IllegalArgumentException("mock tag cannot be used for connections");
+        }
         try {
             return new NdefTagConnection(mService, tag, target);
         } catch (RemoteException e) {
diff --git a/core/java/android/nfc/RawTagConnection.java b/core/java/android/nfc/RawTagConnection.java
index 265eb1b..cf8283b 100644
--- a/core/java/android/nfc/RawTagConnection.java
+++ b/core/java/android/nfc/RawTagConnection.java
@@ -95,10 +95,16 @@
      * returns true.
      */
     public boolean isConnected() {
-        // TODO(nxp): update mIsConnected when tag goes out of range -
-        //            but do not do an active prescence check in
-        //            isConnected()
-        return mIsConnected;
+        if (!mIsConnected) {
+            return false;
+        }
+
+        try {
+            return mTagService.isPresent(mTag.mServiceHandle);
+        } catch (RemoteException e) {
+            Log.e(TAG, "NFC service died", e);
+            return false;
+        }
     }
 
     /**
@@ -129,7 +135,7 @@
     public void close() {
         mIsConnected = false;
         try {
-            mTagService.close(mTag.mNativeHandle);
+            mTagService.close(mTag.mServiceHandle);
         } catch (RemoteException e) {
             Log.e(TAG, "NFC service died", e);
         }
@@ -148,7 +154,7 @@
      */
     public byte[] transceive(byte[] data) throws IOException {
         try {
-            byte[] response = mTagService.transceive(mTag.mNativeHandle, data);
+            byte[] response = mTagService.transceive(mTag.mServiceHandle, data);
             if (response == null) {
                 throw new IOException("transcieve failed");
             }
diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java
index abf02b5..f9205a6 100644
--- a/core/java/android/nfc/Tag.java
+++ b/core/java/android/nfc/Tag.java
@@ -16,8 +16,6 @@
 
 package android.nfc;
 
-import java.util.HashMap;
-
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -39,202 +37,180 @@
  * range. If it is removed and then returned to range, then the most recent
  * {@link Tag} object (in {@link NfcAdapter#ACTION_TAG_DISCOVERED}) should be used to create a
  * {@link RawTagConnection}.
- * <p>This is an immutable data class.
+ * <p>This is an immutable data class. All properties are set at Tag discovery
+ * time and calls on this class will retrieve those read-only properties, and
+ * not cause any further RF activity or block. Note however that arrays passed to and
+ * returned by this class are *not* cloned, so be careful not to modify them.
  */
 public class Tag implements Parcelable {
-
     /**
-     * @hide
+     * ISO 14443-3A technology.
+     * <p>
+     * Includes Topaz (which is -3A compatible)
      */
-    public static final int NFC_TAG_ISO14443_A = 1; /* phNfc_eISO14443_A_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_ISO14443_4A = 2; /* phNfc_eISO14443_4A_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_ISO14443_3A = 3; /* phNfc_eISO14443_3A_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_MIFARE = 4; /* phNfc_eMifare_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_ISO14443_B = 5; /* phNfc_eISO14443_B_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_ISO14443_4B = 6; /* phNfc_eISO14443_4B_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_ISO14443_B_PRIME = 7; /* phNfc_eISO14443_BPrime_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_FELICA = 8; /* phNfc_eFelica_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_JEWEL = 9; /* phNfc_eJewel_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_ISO15693 = 10; /* phNfc_eISO15693_PICC */
-
-    /**
-     * @hide
-     */
-    public static final int NFC_TAG_OTHER = 11; /* phNfc_ePICC_DevType */
-
-
     public static final String TARGET_ISO_14443_3A = "iso14443_3a";
 
+    /**
+     * ISO 14443-3B technology.
+     */
     public static final String TARGET_ISO_14443_3B = "iso14443_3b";
 
-    public static final String TARGET_ISO_14443_3B_PRIME = "iso14443_3b";
-
+    /**
+     * ISO 14443-4 technology.
+     */
     public static final String TARGET_ISO_14443_4 = "iso14443_4";
 
+    /**
+     * ISO 15693 technology, commonly known as RFID.
+     */
     public static final String TARGET_ISO_15693 = "iso15693";
 
+    /**
+     * JIS X-6319-4 technology, commonly known as Felica.
+     */
     public static final String TARGET_JIS_X_6319_4 = "jis_x_6319_4";
 
-    public static final String TARGET_TOPAZ = "topaz";
-
+    /**
+     * Any other technology.
+     */
     public static final String TARGET_OTHER = "other";
 
-    /*package*/ final String mTypeName;
     /*package*/ final boolean mIsNdef;
-    /*package*/ final byte[] mUid;
-    /*package*/ final int mNativeHandle;
-
-    /*package*/ static final String INTERNAL_TARGET_TYPE_ISO14443_3A = "Iso14443-3A";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_ISO14443_3B = "Iso14443-3B";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_ISO14443_4 = "Iso14443-4";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_MIFARE_UL = "MifareUL";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_MIFARE_1K = "Mifare1K";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_MIFARE_4K = "Mifare4K";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_MIFARE_DESFIRE = "MifareDESFIRE";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_MIFARE_UNKNOWN = "Unknown Mifare";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_FELICA = "Felica";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_JEWEL = "Jewel";
-    /*package*/ static final String INTERNAL_TARGET_TYPE_UNKNOWN = "Unknown Type";
-
-	private static final HashMap<String, Integer> INT_TYPES_CONVERTION_TABLE = new HashMap<String, Integer>() {
-		{
-			put(Tag.INTERNAL_TARGET_TYPE_ISO14443_3A, Tag.NFC_TAG_ISO14443_A );
-			put(Tag.INTERNAL_TARGET_TYPE_ISO14443_3B, Tag.NFC_TAG_ISO14443_B );
-			put(Tag.INTERNAL_TARGET_TYPE_MIFARE_UL, Tag.NFC_TAG_MIFARE );
-			put(Tag.INTERNAL_TARGET_TYPE_MIFARE_1K, Tag.NFC_TAG_MIFARE );
-			put(Tag.INTERNAL_TARGET_TYPE_MIFARE_4K, Tag.NFC_TAG_MIFARE );
-			put(Tag.INTERNAL_TARGET_TYPE_MIFARE_DESFIRE, Tag.NFC_TAG_MIFARE );
-			put(Tag.INTERNAL_TARGET_TYPE_FELICA, Tag.NFC_TAG_FELICA );
-			put(Tag.INTERNAL_TARGET_TYPE_JEWEL, Tag.NFC_TAG_JEWEL );
-		}
-	};
-
-	private int convertToInt(String internalTypeName) {
-		Integer result = INT_TYPES_CONVERTION_TABLE.get(internalTypeName);
-		if (result == null) {
-		    return Tag.NFC_TAG_OTHER;
-		}
-		return result;
-    }
-
-	private static final HashMap<String, String[]> RAW_TYPES_CONVERTION_TABLE = new HashMap<String, String[]>() {
-		{
-			/* TODO: handle multiprotocol */
-			put(Tag.INTERNAL_TARGET_TYPE_ISO14443_3A, new String[] { Tag.TARGET_ISO_14443_3A });
-			put(Tag.INTERNAL_TARGET_TYPE_ISO14443_3B, new String[] { Tag.TARGET_ISO_14443_3B });
-			put(Tag.INTERNAL_TARGET_TYPE_MIFARE_UL, new String[] { Tag.TARGET_ISO_14443_3A });
-			put(Tag.INTERNAL_TARGET_TYPE_MIFARE_1K, new String[] { Tag.TARGET_ISO_14443_3A });
-			put(Tag.INTERNAL_TARGET_TYPE_MIFARE_4K, new String[] { Tag.TARGET_ISO_14443_3A });
-			put(Tag.INTERNAL_TARGET_TYPE_MIFARE_DESFIRE, new String[] { Tag.TARGET_ISO_14443_3A });
-			put(Tag.INTERNAL_TARGET_TYPE_MIFARE_UNKNOWN, new String[] { Tag.TARGET_ISO_14443_3A });
-			put(Tag.INTERNAL_TARGET_TYPE_FELICA, new String[] { Tag.TARGET_JIS_X_6319_4 });
-			put(Tag.INTERNAL_TARGET_TYPE_JEWEL, new String[] { Tag.TARGET_TOPAZ });
-		}
-	};
-
-	private String[] convertToRaw(String internalTypeName) {
-	    String[] result =  RAW_TYPES_CONVERTION_TABLE.get(internalTypeName);
-	    if (result == null) {
-	        return new String[] { Tag.TARGET_OTHER };
-	    }
-	    return result;
-	}
+    /*package*/ final byte[] mId;
+    /*package*/ final String[] mRawTargets;
+    /*package*/ final byte[] mPollBytes;
+    /*package*/ final byte[] mActivationBytes;
+    /*package*/ final int mServiceHandle;  // for use by NFC service, 0 indicates a mock
 
     /**
-     * Hidden constructor to be used by NFC service only.
+     * Hidden constructor to be used by NFC service and internal classes.
      * @hide
      */
-    public Tag(String typeName, boolean isNdef, byte[] uid, int nativeHandle) {
-        mTypeName = typeName;
+    public Tag(byte[] id, boolean isNdef, String[] rawTargets, byte[] pollBytes,
+            byte[] activationBytes, int serviceHandle) {
+        if (rawTargets == null) {
+            throw new IllegalArgumentException("rawTargets cannot be null");
+        }
         mIsNdef = isNdef;
-        mUid = uid.clone();
-        mNativeHandle = nativeHandle;
+        mId = id;
+        mRawTargets = rawTargets;
+        mPollBytes = pollBytes;
+        mActivationBytes = activationBytes;
+        mServiceHandle = serviceHandle;
+    }
+
+    /**
+     * Construct a mock Tag.
+     * <p>This is an application constructed tag, so NfcAdapter methods on this
+     * Tag such as {@link NfcAdapter#createRawTagConnection} will fail with
+     * {@link IllegalArgumentException} since it does not represent a physical Tag.
+     * <p>This constructor might be useful for mock testing.
+     * @param id The tag identifier, can be null
+     * @param rawTargets must not be null
+     * @param pollBytes can be null
+     * @param activationBytes can be null
+     * @return freshly constructed tag
+     */
+    public static Tag createMockTag(byte[] id, String[] rawTargets, byte[] pollBytes,
+            byte[] activationBytes) {
+        // set serviceHandle to 0 to indicate mock tag
+        return new Tag(id, false, rawTargets, pollBytes, activationBytes, 0);
     }
 
     /**
      * For use by NfcService only.
      * @hide
      */
-    public int getHandle() {
-        return mNativeHandle;
+    public int getServiceHandle() {
+        return mServiceHandle;
     }
 
     /**
      * Return the available targets that this NFC adapter can use to create
      * a RawTagConnection.
      *
-     * @return
+     * @return raw targets, will not be null
      */
     public String[] getRawTargets() {
-        return convertToRaw(mTypeName);
-    }
-
-    /**
-     * Get the Tag type.
-     * <p>
-     * The Tag type is one of the NFC_TAG constants. It is read at discovery
-     * time and this method does not cause any further RF activity and does not
-     * block.
-     *
-     * @return a NFC_TAG constant
-     * @hide
-     */
-    public int getType() {
-        return convertToInt(mTypeName);
+        return mRawTargets;
     }
 
     /**
      * Get the Tag Identifier (if it has one).
-     * <p>
-     * Tag ID is usually a serial number for the tag.
-     * <p>
-     * The Tag ID is read at discovery time and this method does not cause any
-     * further RF activity and does not block.
+     * <p>Tag ID is usually a serial number for the tag.
      *
      * @return ID, or null if it does not exist
      */
     public byte[] getId() {
-        if (mUid.length > 0) {
-            return mUid.clone();
-        } else {
-            return null;
+        return mId;
+    }
+
+    /**
+     * Get the low-level bytes returned by this Tag at poll-time.
+     * <p>These can be used to help with advanced identification of a Tag.
+     * <p>The meaning of these bytes depends on the Tag technology.
+     * <p>ISO14443-3A: ATQA/SENS_RES
+     * <p>ISO14443-3B: Application data (4 bytes) and Protocol Info (3 bytes) from ATQB/SENSB_RES
+     * <p>JIS_X_6319_4: PAD0 (2 byte), PAD1 (2 byte), MRTI(2 byte), PAD2 (1 byte), RC (2 byte)
+     * <p>ISO15693: response flags (1 byte), DSFID (1 byte)
+     * from SENSF_RES
+     *
+     * @return poll bytes, or null if they do not exist for this Tag technology
+     */
+    public byte[] getPollBytes() {
+        return mPollBytes;
+    }
+
+    /**
+     * Get the low-level bytes returned by this Tag at activation-time.
+     * <p>These can be used to help with advanced identification of a Tag.
+     * <p>The meaning of these bytes depends on the Tag technology.
+     * <p>ISO14443-3A: SAK/SEL_RES
+     * <p>ISO14443-3B: null
+     * <p>ISO14443-3A & ISO14443-4: SAK/SEL_RES, historical bytes from ATS  <TODO: confirm>
+     * <p>ISO14443-3B & ISO14443-4: ATTRIB response
+     * <p>JIS_X_6319_4: null
+     * <p>ISO15693: response flags (1 byte), DSFID (1 byte): null
+     * @return activation bytes, or null if they do not exist for this Tag technology
+     */
+    public byte[] getActivationBytes() {
+        return mActivationBytes;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("TAG ")
+            .append("uid = ")
+            .append(mId)
+            .append(" poll ")
+            .append(mPollBytes)
+            .append(" activation ")
+            .append(mActivationBytes)
+            .append(" Raw [");
+        for (String s : mRawTargets) {
+            sb.append(s)
+            .append(", ");
         }
+        return sb.toString();
+    }
+
+    /*package*/ static byte[] readBytesWithNull(Parcel in) {
+        int len = in.readInt();
+        byte[] result = null;
+        if (len > 0) {
+            result = new byte[len];
+            in.readByteArray(result);
+        }
+        return result;
+    }
+
+    /*package*/ static void writeBytesWithNull(Parcel out, byte[] b) {
+        if (b == null) {
+            out.writeInt(-1);
+            return;
+        }
+        out.writeInt(b.length);
+        out.writeByteArray(b);
     }
 
     @Override
@@ -242,29 +218,34 @@
         return 0;
     }
 
+
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        boolean[] booleans = new boolean[] {mIsNdef};
-        dest.writeString(mTypeName);
-        dest.writeBooleanArray(booleans);
-        dest.writeInt(mUid.length);
-        dest.writeByteArray(mUid);
-        dest.writeInt(mNativeHandle);
+        dest.writeInt(mIsNdef ? 1 : 0);
+        writeBytesWithNull(dest, mId);
+        dest.writeInt(mRawTargets.length);
+        dest.writeStringArray(mRawTargets);
+        writeBytesWithNull(dest, mPollBytes);
+        writeBytesWithNull(dest, mActivationBytes);
+        dest.writeInt(mServiceHandle);
     }
 
     public static final Parcelable.Creator<Tag> CREATOR =
             new Parcelable.Creator<Tag>() {
         public Tag createFromParcel(Parcel in) {
-            boolean[] booleans = new boolean[1];
-            String type = in.readString();
-            in.readBooleanArray(booleans);
-            boolean isNdef = booleans[0];
-            int uidLength = in.readInt();
-            byte[] uid = new byte[uidLength];
-            in.readByteArray(uid);
-            int nativeHandle = in.readInt();
+            boolean isNdef = (in.readInt() == 1);
+            if (isNdef) {
+                throw new IllegalArgumentException("Creating Tag from NdefTag parcel");
+            }
+            // Tag fields
+            byte[] id = Tag.readBytesWithNull(in);
+            String[] rawTargets = new String[in.readInt()];
+            in.readStringArray(rawTargets);
+            byte[] pollBytes = Tag.readBytesWithNull(in);
+            byte[] activationBytes = Tag.readBytesWithNull(in);
+            int serviceHandle = in.readInt();
 
-            return new Tag(type, isNdef, uid, nativeHandle);
+            return new Tag(id, isNdef, rawTargets, pollBytes, activationBytes, serviceHandle);
         }
         public Tag[] newArray(int size) {
             return new Tag[size];
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9767786..b4c6a2923 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -188,15 +188,7 @@
         public static final int FROYO = 8;
         
         /**
-         * Next version of Android.
-         * 
-         * <p>Applications targeting this or a later release will get these
-         * new changes in behavior:</p>
-         * <ul>
-         * <li> The status bar is now dark.  Targeting this version allows
-         * the platform to perform performing compatibility on status bar
-         * graphics to ensure they look okay on a dark background.
-         * </ul>
+         * Newest version of Android, version 2.3.
          */
         public static final int GINGERBREAD = 9;
 
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index e912089..9786959 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -15,6 +15,7 @@
  */
 package android.os;
 
+import android.animation.ValueAnimator;
 import android.app.ActivityManagerNative;
 import android.app.ApplicationErrorReport;
 import android.util.Log;
@@ -1110,6 +1111,11 @@
         public int durationMillis = -1;
 
         /**
+         * The number of animations currently running.
+         */
+        public int numAnimationsRunning = 0;
+
+        /**
          * Which violation number this was (1-based) since the last Looper loop,
          * from the perspective of the root caller (if it crossed any processes
          * via Binder calls).  The value is 0 if the root caller wasn't on a Looper
@@ -1138,6 +1144,7 @@
             crashInfo = new ApplicationErrorReport.CrashInfo(tr);
             violationUptimeMillis = SystemClock.uptimeMillis();
             this.policy = policy;
+            this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
         }
 
         /**
@@ -1163,6 +1170,7 @@
             }
             durationMillis = in.readInt();
             violationNumThisLoop = in.readInt();
+            numAnimationsRunning = in.readInt();
             violationUptimeMillis = in.readLong();
         }
 
@@ -1174,6 +1182,7 @@
             dest.writeInt(policy);
             dest.writeInt(durationMillis);
             dest.writeInt(violationNumThisLoop);
+            dest.writeInt(numAnimationsRunning);
             dest.writeLong(violationUptimeMillis);
         }
 
@@ -1190,6 +1199,9 @@
             if (violationNumThisLoop != 0) {
                 pw.println(prefix + "violationNumThisLoop: " + violationNumThisLoop);
             }
+            if (numAnimationsRunning != 0) {
+                pw.println(prefix + "numAnimationsRunning: " + numAnimationsRunning);
+            }
             pw.println(prefix + "violationUptimeMillis: " + violationUptimeMillis);
         }
 
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index baf3505..0448ec0 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -32,7 +32,6 @@
 import android.database.DatabaseUtils;
 import android.graphics.Rect;
 import android.net.Uri;
-import android.net.Uri.Builder;
 import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
@@ -1444,6 +1443,12 @@
          * against the cost of transferring large amounts of denormalized data
          * from the Provider.
          * </p>
+         * <p>
+         * To reduce the amount of data duplication the contacts provider and directory
+         * providers implementing this protocol are allowed to provide common Contacts
+         * and RawContacts fields in the first row returned for each raw contact only and
+         * leave them as null in subsequent rows.
+         * </p>
          */
         public static final class Entity implements BaseColumns, ContactsColumns,
                 ContactNameColumns, RawContactsColumns, BaseSyncColumns, SyncColumns, DataColumns,
@@ -1838,7 +1843,8 @@
      * constituent data rows in a single database transaction
      * and causes at most one aggregation pass.
      * <pre>
-     * ArrayList&lt;ContentProviderOperation&gt; ops = Lists.newArrayList();
+     * ArrayList&lt;ContentProviderOperation&gt; ops =
+     *          new ArrayList&lt;ContentProviderOperation&gt;();
      * ...
      * int rawContactInsertIndex = ops.size();
      * ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
@@ -2725,7 +2731,9 @@
      * <p>
      * The same done using ContentProviderOperations:
      * <pre>
-     * ArrayList&lt;ContentProviderOperation&gt; ops = Lists.newArrayList();
+     * ArrayList&lt;ContentProviderOperation&gt; ops =
+     *          new ArrayList&lt;ContentProviderOperation&gt;();
+     *
      * ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
      *          .withValue(Data.RAW_CONTACT_ID, rawContactId)
      *          .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
@@ -2742,7 +2750,9 @@
      * Just as with insert, update can be done incrementally or as a batch,
      * the batch mode being the preferred method:
      * <pre>
-     * ArrayList&lt;ContentProviderOperation&gt; ops = Lists.newArrayList();
+     * ArrayList&lt;ContentProviderOperation&gt; ops =
+     *          new ArrayList&lt;ContentProviderOperation&gt;();
+     *
      * ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI)
      *          .withSelection(Data._ID + "=?", new String[]{String.valueOf(dataId)})
      *          .withValue(Email.DATA, "somebody@android.com")
@@ -2757,7 +2767,9 @@
      * Just as with insert and update, deletion can be done either using the
      * {@link ContentResolver#delete} method or using a ContentProviderOperation:
      * <pre>
-     * ArrayList&lt;ContentProviderOperation&gt; ops = Lists.newArrayList();
+     * ArrayList&lt;ContentProviderOperation&gt; ops =
+     *          new ArrayList&lt;ContentProviderOperation&gt;();
+     *
      * ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI)
      *          .withSelection(Data._ID + "=?", new String[]{String.valueOf(dataId)})
      *          .build());
@@ -4050,7 +4062,9 @@
          * <p>A data kind representing the contact's nickname. For example, for
          * Bob Parr ("Mr. Incredible"):
          * <pre>
-         * ArrayList&lt;ContentProviderOperation&gt; ops = Lists.newArrayList();
+         * ArrayList&lt;ContentProviderOperation&gt; ops =
+         *          new ArrayList&lt;ContentProviderOperation&gt;();
+         *
          * ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
          *          .withValue(Data.RAW_CONTACT_ID, rawContactId)
          *          .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
@@ -6645,16 +6659,55 @@
 
             /**
              * The extra field that allows the client to supply multiple rows of
-             * arbitrary data for the contact (insert or edit). It is a list of
-             * ContentValues, one per data row. Supplying this extra is
-             * basically equivalent to inserting multiple rows into the
-             * {@link Data} table, except the user gets a chance to see and edit
-             * them before saving. Each ContentValues object must have a value
-             * for {@link Data#MIMETYPE}.
+             * arbitrary data for a single contact created using the {@link Intent#ACTION_INSERT}
+             * or edited using {@link Intent#ACTION_EDIT}. It is an ArrayList of
+             * {@link ContentValues}, one per data row. Supplying this extra is
+             * similar to inserting multiple rows into the {@link Data} table,
+             * except the user gets a chance to see and edit them before saving.
+             * Each ContentValues object must have a value for {@link Data#MIMETYPE}.
+             * If supplied values are not visible in the editor UI, they will be
+             * dropped.  Duplicate data will dropped.  Some fields
+             * like {@link CommonDataKinds.Email#TYPE Email.TYPE} may be automatically
+             * adjusted to comply with the constraints of the specific account type.
+             * For example, an Exchange contact can only have one phone numbers of type Home,
+             * so the contact editor may choose a different type for this phone number to
+             * avoid dropping the valueable part of the row, which is the phone number.
+             * <p>
+             * Example:
+             * <pre>
+             *  ArrayList&lt;ContentValues&gt; data = new ArrayList&lt;ContentValues&gt;();
+             *
+             *  ContentValues row1 = new ContentValues();
+             *  row1.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
+             *  row1.put(Organization.COMPANY, "Android");
+             *  data.add(row1);
+             *
+             *  ContentValues row2 = new ContentValues();
+             *  row2.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+             *  row2.put(Email.TYPE, Email.TYPE_CUSTOM);
+             *  row2.put(Email.LABEL, "Green Bot");
+             *  row2.put(Email.ADDRESS, "android@android.com");
+             *  data.add(row2);
+             *
+             *  Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+             *  intent.putParcelableArrayListExtra(Insert.DATA, data);
+             *
+             *  startActivity(intent);
+             * </pre>
+             */
+            public static final String DATA = "data";
+
+            /**
+             * Used to specify the account in which to create the new contact.
+             * <p>
+             * If this value is not provided, the user is presented with a disambiguation
+             * dialog to chose an account
+             * <p>
+             * Type: {@link Account}
              *
              * @hide
              */
-            public static final String DATA = "data";
+            public static final String ACCOUNT = "com.android.contacts.extra.ACCOUNT";
         }
     }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 17d4aa4..62d2ff2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1642,86 +1642,6 @@
         public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse";
 
         /**
-         * Whether nfc is enabled/disabled
-         * 0=disabled. 1=enabled.
-         * @hide
-         */
-        public static final String NFC_ON = "nfc_on";
-
-        /**
-         * Whether nfc secure element is enabled/disabled
-         * 0=disabled. 1=enabled.
-         * @hide
-         */
-        public static final String NFC_SECURE_ELEMENT_ON = "nfc_secure_element_on";
-
-        /**
-         * Whether nfc secure element is enabled/disabled
-         * 0=disabled. 1=enabled.
-         * @hide
-         */
-        public static final String NFC_SECURE_ELEMENT_ID = "nfc_secure_element_id";
-
-        /**
-         * LLCP LTO value
-         * @hide
-         */
-        public static final String NFC_LLCP_LTO = "nfc_llcp_lto";
-
-        /**
-         * LLCP MIU value
-         * @hide
-         */
-        public static final String NFC_LLCP_MIU = "nfc_llcp_miu";
-
-        /**
-         * LLCP WKS value
-         * @hide
-         */
-        public static final String NFC_LLCP_WKS = "nfc_llcp_wks";
-
-        /**
-         * LLCP OPT value
-         * @hide
-         */
-        public static final String NFC_LLCP_OPT = "nfc_llcp_opt";
-
-        /**
-         * NFC Discovery Reader A
-         * 0=disabled. 1=enabled.
-         * @hide
-         */
-        public static final String NFC_DISCOVERY_A = "nfc_discovery_a";
-
-        /**
-         * NFC Discovery Reader B
-         * 0=disabled. 1=enabled.
-         * @hide
-         */
-        public static final String NFC_DISCOVERY_B = "nfc_discovery_b";
-
-        /**
-         * NFC Discovery Reader Felica
-         * 0=disabled. 1=enabled.
-         * @hide
-         */
-        public static final String NFC_DISCOVERY_F = "nfc_discovery_felica";
-
-        /**
-         * NFC Discovery Reader 15693
-         * 0=disabled. 1=enabled.
-         * @hide
-         */
-        public static final String NFC_DISCOVERY_15693 = "nfc_discovery_15693";
-
-        /**
-         * NFC Discovery NFCIP
-         * 0=disabled. 1=enabled.
-         * @hide
-         */
-        public static final String NFC_DISCOVERY_NFCIP = "nfc_discovery_nfcip";
-
-        /**
          * Show pointer location on screen?
          * 0 = no
          * 1 = yes
@@ -1895,18 +1815,6 @@
             USE_PTP_INTERFACE,
             SIP_CALL_OPTIONS,
             SIP_RECEIVE_CALLS,
-            NFC_ON,
-            NFC_SECURE_ELEMENT_ON,
-            NFC_SECURE_ELEMENT_ID,
-            NFC_LLCP_LTO,
-            NFC_LLCP_MIU,
-            NFC_LLCP_WKS,
-            NFC_LLCP_OPT,
-            NFC_DISCOVERY_A,
-            NFC_DISCOVERY_B,
-            NFC_DISCOVERY_F,
-            NFC_DISCOVERY_15693,
-            NFC_DISCOVERY_NFCIP,
         };
 
         // Settings moved to Settings.Secure
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 7b2022b..61e2305 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -41,6 +41,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 
 public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
@@ -107,10 +108,10 @@
             } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                 if (streamType == AudioManager.STREAM_MUSIC) {
-                    BluetoothDevice sinks[] = getConnectedDevices();
+                    List<BluetoothDevice> sinks = getConnectedDevices();
 
-                    if (sinks.length != 0 && isPhoneDocked(sinks[0])) {
-                        String address = sinks[0].getAddress();
+                    if (sinks.size() != 0 && isPhoneDocked(sinks.get(0))) {
+                        String address = sinks.get(0).getAddress();
                         int newVolLevel =
                           intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
                         int oldVolLevel =
@@ -319,7 +320,7 @@
         if (getDevicesMatchingConnectionStates(new int[] {
                 BluetoothA2dp.STATE_CONNECTING,
                 BluetoothA2dp.STATE_CONNECTED,
-                BluetoothA2dp.STATE_DISCONNECTING}).length != 0) {
+                BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) {
             return false;
         }
 
@@ -430,19 +431,16 @@
         return state;
     }
 
-    public synchronized BluetoothDevice[] getConnectedDevices() {
+    public synchronized List<BluetoothDevice> getConnectedDevices() {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        BluetoothDevice[] sinks = getDevicesMatchingConnectionStates(
+        List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates(
                 new int[] {BluetoothA2dp.STATE_CONNECTED});
         return sinks;
     }
 
-    public synchronized BluetoothDevice[] getDevicesMatchingConnectionStates(int[] states) {
+    public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        ArrayList<BluetoothDevice> sinks = new ArrayList();
-        if (mAudioDevices.isEmpty()) {
-            return sinks.toArray(new BluetoothDevice[sinks.size()]);
-        }
+        ArrayList<BluetoothDevice> sinks = new ArrayList<BluetoothDevice>();
         for (BluetoothDevice device: mAudioDevices.keySet()) {
             int sinkState = getConnectionState(device);
             for (int state : states) {
@@ -452,7 +450,7 @@
                 }
             }
         }
-        return sinks.toArray(new BluetoothDevice[sinks.size()]);
+        return sinks;
     }
 
     public synchronized int getPriority(BluetoothDevice device) {
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 239c3ac..f47c553 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -29,11 +29,12 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
 import android.util.Log;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Set;
-import android.os.PowerManager;
 
 
 /**
@@ -629,7 +630,7 @@
     }
 
     private boolean isOtherInputDeviceConnected(String address) {
-        Set<BluetoothDevice> devices =
+        List<BluetoothDevice> devices =
             mBluetoothService.lookupInputDevicesMatchingStates(new int[] {
                                                 BluetoothInputDevice.STATE_CONNECTING,
                                                 BluetoothInputDevice.STATE_CONNECTED});
@@ -654,13 +655,13 @@
     }
 
     private boolean isOtherSinkInNonDisconnectedState(String address) {
-        Set<BluetoothDevice> devices =
+        List<BluetoothDevice> devices =
             mA2dp.getDevicesMatchingConnectionStates(new int[] {BluetoothA2dp.STATE_CONNECTED,
                                                      BluetoothA2dp.STATE_CONNECTING,
                                                      BluetoothA2dp.STATE_DISCONNECTING});
 
         if (devices.size() == 0) return false;
-        for(BluetoothDevice dev: devices) {
+        for (BluetoothDevice dev: devices) {
             if (!dev.getAddress().equals(address)) return true;
         }
         return false;
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 7abb98e..660f9ab 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -27,12 +27,12 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothDeviceProfileState;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProfileState;
-import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
@@ -80,6 +80,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -1415,7 +1416,7 @@
     }
 
     /*package*/ synchronized boolean allowIncomingTethering() {
-        if (isTetheringOn() && getConnectedPanDevices().length < mMaxPanDevices)
+        if (isTetheringOn() && getConnectedPanDevices().size() < mMaxPanDevices)
             return true;
         return false;
     }
@@ -1503,16 +1504,17 @@
         return true;
     }
 
-    public synchronized BluetoothDevice[] getConnectedPanDevices() {
+    public synchronized List<BluetoothDevice> getConnectedPanDevices() {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>();
+        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+
         for (BluetoothDevice device: mPanDevices.keySet()) {
             if (getPanDeviceState(device) == BluetoothPan.STATE_CONNECTED) {
                 devices.add(device);
             }
         }
-        return devices.toArray(new BluetoothDevice[devices.size()]);
+        return devices;
     }
 
     public synchronized boolean disconnectPanDevice(BluetoothDevice device) {
@@ -1569,7 +1571,7 @@
     }
 
     private String createNewTetheringAddressLocked() {
-        if (getConnectedPanDevices().length == mMaxPanDevices) {
+        if (getConnectedPanDevices().size() == mMaxPanDevices) {
             log("Max PAN device connections reached");
             return null;
         }
@@ -1688,7 +1690,7 @@
                                                 "Need BLUETOOTH_ADMIN permission");
 
         String objectPath = getObjectPathFromAddress(device.getAddress());
-        if (objectPath == null || getConnectedInputDevices().length == 0) {
+        if (objectPath == null || getConnectedInputDevices().size() == 0) {
             return false;
         }
         BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
@@ -1721,11 +1723,11 @@
         return mInputDevices.get(device);
     }
 
-    public synchronized BluetoothDevice[] getConnectedInputDevices() {
+    public synchronized List<BluetoothDevice> getConnectedInputDevices() {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Set<BluetoothDevice> devices = lookupInputDevicesMatchingStates(
+        List<BluetoothDevice> devices = lookupInputDevicesMatchingStates(
             new int[] {BluetoothInputDevice.STATE_CONNECTED});
-        return devices.toArray(new BluetoothDevice[devices.size()]);
+        return devices;
     }
 
     public synchronized int getInputDevicePriority(BluetoothDevice device) {
@@ -1746,11 +1748,9 @@
                 priority);
     }
 
-    /*package*/synchronized Set<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
-        Set<BluetoothDevice> inputDevices = new HashSet<BluetoothDevice>();
-        if (mInputDevices.isEmpty()) {
-            return inputDevices;
-        }
+    /*package*/synchronized List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
+        List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
+
         for (BluetoothDevice device: mInputDevices.keySet()) {
             int inputDeviceState = getInputDeviceState(device);
             for (int state : states) {
@@ -2487,11 +2487,11 @@
         // dump
         pw.println("\n--Headset Service--");
         if (mBluetoothHeadset != null) {
-           Set<BluetoothDevice> deviceSet = mBluetoothHeadset.getConnectedDevices();
-           if (deviceSet.size() == 0) {
+           List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+           if (deviceList.size() == 0) {
               pw.println("\n--No headsets connected--");
            }
-           BluetoothDevice device = (BluetoothDevice) deviceSet.toArray()[0];
+           BluetoothDevice device = deviceList.get(0);
 
             switch (mBluetoothHeadset.getConnectionState(device)) {
                 case BluetoothHeadset.STATE_DISCONNECTED:
@@ -2511,11 +2511,11 @@
                     break;
             }
 
-            deviceSet.clear();
-            deviceSet = mBluetoothHeadset.getDevicesMatchingConnectionStates(new int[] {
+            deviceList.clear();
+            deviceList = mBluetoothHeadset.getDevicesMatchingConnectionStates(new int[] {
                      BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED});
             pw.println("\n--Connected and Disconnected Headsets");
-            for (BluetoothDevice dev: deviceSet) {
+            for (BluetoothDevice dev: deviceList) {
                 pw.println(device);
                 if (mBluetoothHeadset.isAudioConnected(device)) {
                     pw.println("SCO audio connected to device:" + device);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 2b083dc..9645a17 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -452,17 +452,14 @@
         private void dispatchPointer(MotionEvent event) {
             synchronized (mLock) {
                 if (event.getAction() == MotionEvent.ACTION_MOVE) {
-                    if (mPendingMove != null) {
-                        mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove);
-                        mPendingMove.recycle();
-                    }
                     mPendingMove = event;
                 } else {
                     mPendingMove = null;
                 }
-                Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
-                mCaller.sendMessage(msg);
             }
+
+            Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
+            mCaller.sendMessage(msg);
         }
 
         void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
@@ -905,14 +902,22 @@
                     mEngine.doOffsetsChanged();
                 } break;
                 case MSG_TOUCH_EVENT: {
+                    boolean skip = false;
                     MotionEvent ev = (MotionEvent)message.obj;
-                    synchronized (mEngine.mLock) {
-                        if (mEngine.mPendingMove == ev) {
-                            mEngine.mPendingMove = null;
+                    if (ev.getAction() == MotionEvent.ACTION_MOVE) {
+                        synchronized (mEngine.mLock) {
+                            if (mEngine.mPendingMove == ev) {
+                                mEngine.mPendingMove = null;
+                            } else {
+                                // this is not the motion event we are looking for....
+                                skip = true;
+                            }
                         }
                     }
-                    if (DEBUG) Log.v(TAG, "Delivering touch event: " + ev);
-                    mEngine.onTouchEvent(ev);
+                    if (!skip) {
+                        if (DEBUG) Log.v(TAG, "Delivering touch event: " + ev);
+                        mEngine.onTouchEvent(ev);
+                    }
                     ev.recycle();
                 } break;
                 default :
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 76d8106..63baf14 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -45,6 +45,11 @@
     public static final int DENSITY_HIGH = 240;
 
     /**
+     * Standard quantized DPI for extra-high-density screens.
+     */
+    public static final int DENSITY_XHIGH = 320;
+
+    /**
      * The reference density used throughout the system.
      */
     public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index b1160f0..e7c2231 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -39,12 +39,6 @@
     abstract void end();
 
     /**
-     * Frees resources taken by this display list. This method must be called
-     * before releasing all references.
-     */
-    abstract void destroy();
-
-    /**
      * Indicates whether this display list can be replayed or not.
      * 
      * @return True if the display list can be replayed, false otherwise.
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index ebf8505..bbac14c 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -29,6 +29,7 @@
     float mX, mY;
     ClipDescription mClipDescription;
     ClipData mClipData;
+    boolean mDragResult;
 
     private DragEvent mNext;
     private RuntimeException mRecycledLocation;
@@ -53,25 +54,27 @@
     private DragEvent() {
     }
 
-    private void init(int action, float x, float y, ClipDescription description, ClipData data) {
+    private void init(int action, float x, float y, ClipDescription description, ClipData data,
+            boolean result) {
         mAction = action;
         mX = x;
         mY = y;
         mClipDescription = description;
         mClipData = data;
+        mDragResult = result;
     }
 
     static DragEvent obtain() {
-        return DragEvent.obtain(0, 0f, 0f, null, null);
+        return DragEvent.obtain(0, 0f, 0f, null, null, false);
     }
 
     public static DragEvent obtain(int action, float x, float y,
-            ClipDescription description, ClipData data) {
+            ClipDescription description, ClipData data, boolean result) {
         final DragEvent ev;
         synchronized (gRecyclerLock) {
             if (gRecyclerTop == null) {
                 ev = new DragEvent();
-                ev.init(action, x, y, description, data);
+                ev.init(action, x, y, description, data, result);
                 return ev;
             }
             ev = gRecyclerTop;
@@ -82,14 +85,14 @@
         ev.mRecycled = false;
         ev.mNext = null;
 
-        ev.init(action, x, y, description, data);
+        ev.init(action, x, y, description, data, result);
 
         return ev;
     }
 
     public static DragEvent obtain(DragEvent source) {
         return obtain(source.mAction, source.mX, source.mY,
-                source.mClipDescription, source.mClipData);
+                source.mClipDescription, source.mClipData, source.mDragResult);
     }
 
     public int getAction() {
@@ -112,6 +115,10 @@
         return mClipDescription;
     }
 
+    public boolean getResult() {
+        return mDragResult;
+    }
+
     /**
      * Recycle the DragEvent, to be re-used by a later caller.  After calling
      * this function you must never touch the event again.
@@ -146,7 +153,7 @@
     public String toString() {
         return "DragEvent{" + Integer.toHexString(System.identityHashCode(this))
         + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription
-        + " data=" + mClipData
+        + " data=" + mClipData + " result=" + mDragResult
         + "}";
     }
 
@@ -160,6 +167,7 @@
         dest.writeInt(mAction);
         dest.writeFloat(mX);
         dest.writeFloat(mY);
+        dest.writeInt(mDragResult ? 1 : 0);
         if (mClipData == null) {
             dest.writeInt(0);
         } else {
@@ -181,6 +189,7 @@
             event.mAction = in.readInt();
             event.mX = in.readFloat();
             event.mY = in.readFloat();
+            event.mDragResult = (in.readInt() != 0);
             if (in.readInt() != 0) {
                 event.mClipData = ClipData.CREATOR.createFromParcel(in);
             }
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index d78c68a..5d9bd1e 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -34,6 +34,11 @@
 import android.text.SpannableString;
 import android.text.SpannedString;
 import android.text.TextUtils;
+import android.util.Finalizers;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * An implementation of Canvas on top of OpenGL ES 2.0.
@@ -84,22 +89,35 @@
        
         if (mRenderer == 0) {
             throw new IllegalStateException("Could not create GLES20Canvas renderer");
+        } else {
+            new CanvasFinalizer(this);
         }
     }
 
     private native int nCreateRenderer();    
     private native int nCreateDisplayListRenderer();    
 
-    @Override
-    public synchronized void destroy() {
-        if (mRenderer != 0) {
+    private static native void nDestroyRenderer(int renderer);
+
+    private static class CanvasFinalizer extends Finalizers.ReclaimableReference<GLES20Canvas> {
+        private static final Set<CanvasFinalizer> sFinalizers = Collections.synchronizedSet(
+                new HashSet<CanvasFinalizer>());
+
+        private int mRenderer;
+
+        CanvasFinalizer(GLES20Canvas canvas) {
+            super(canvas, Finalizers.getQueue());
+            mRenderer = canvas.mRenderer;
+            sFinalizers.add(this);
+        }
+
+        @Override
+        public void reclaim() {
             nDestroyRenderer(mRenderer);
-            mRenderer = 0;
+            sFinalizers.remove(this);
         }
     }
 
-    private native void nDestroyRenderer(int renderer);
-
     ///////////////////////////////////////////////////////////////////////////
     // Canvas management
     ///////////////////////////////////////////////////////////////////////////
@@ -178,11 +196,11 @@
 
     private native int nCreateDisplayList(int renderer);
     
-    void destroyDisplayList(int displayList) {
+    static void destroyDisplayList(int displayList) {
         nDestroyDisplayList(displayList);
     }
 
-    private native void nDestroyDisplayList(int displayList);
+    private static native void nDestroyDisplayList(int displayList);
 
     @Override
     public void drawDisplayList(DisplayList displayList) {
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index 2886bf3..11e6d30 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -16,6 +16,12 @@
 
 package android.view;
 
+import android.util.Finalizers;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * An implementation of display list for OpenGL ES 2.0.
  */
@@ -33,8 +39,6 @@
             throw new IllegalStateException("Recording has already started");
         }
 
-        destroyCanvas();
-
         mCanvas = new GLES20Canvas(true, true);
         mStarted = true;
         mRecorded = false;
@@ -42,16 +46,6 @@
         return mCanvas;
     }
 
-    private void destroyCanvas() {
-        if (mCanvas != null) {
-            mCanvas.destroyDisplayList(mNativeDisplayList);
-            mCanvas.destroy();
-
-            mCanvas = null;
-            mNativeDisplayList = 0;
-        }
-    }
-
     @Override
     void end() {
         if (mCanvas != null) {
@@ -59,16 +53,31 @@
             mRecorded = true;
 
             mNativeDisplayList = mCanvas.getDisplayList();
+            new DisplayListFinalizer(this);
         }
     }
 
     @Override
-    void destroy() {
-        destroyCanvas();
-    }
-
-    @Override
     boolean isReady() {
         return !mStarted && mRecorded;
     }
+
+    private static class DisplayListFinalizer extends Finalizers.ReclaimableReference<DisplayList> {
+        private static final Set<DisplayListFinalizer> sFinalizers = Collections.synchronizedSet(
+                new HashSet<DisplayListFinalizer>());
+
+        private int mNativeDisplayList;
+
+        DisplayListFinalizer(GLES20DisplayList displayList) {
+            super(displayList, Finalizers.getQueue());
+            mNativeDisplayList = displayList.mNativeDisplayList;
+            sFinalizers.add(this);
+        }
+
+        @Override
+        public void reclaim() {
+            GLES20Canvas.destroyDisplayList(mNativeDisplayList);
+            sFinalizers.remove(this);
+        }
+    }
 }
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 22d2fe6..8b8d15e 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -34,14 +34,6 @@
     }
     
     /**
-     * This method <strong>must</strong> be called before releasing a
-     * reference to a hardware canvas. This method is responsible for
-     * freeing native resources associated with the hardware. Not
-     * invoking this method properly can result in memory leaks.
-     */    
-    public abstract void destroy();
-
-    /**
      * Invoked before any drawing operation is performed in this canvas.
      */
     abstract void onPreDraw();
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 0247f6a..6f4abef 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -414,7 +414,6 @@
         @Override
         void destroy(boolean full) {
             if (full && mCanvas != null) {
-                mCanvas.destroy();
                 mCanvas = null;
             }
             
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 79ea5b6..23fae42 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -130,6 +130,13 @@
     boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,
             float thumbCenterX, float thumbCenterY, in ClipData data);
 
+	/**
+	 * Report the result of a drop action targeted to the given window.
+	 * consumed is 'true' when the drop was accepted by a valid recipient,
+	 * 'false' otherwise.
+	 */
+	void reportDropResult(IWindow window, boolean consumed);
+
     /**
      * Tell the OS that we've just dragged into a View that is willing to accept the drop
      */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index baa749a..0456463 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7417,7 +7417,6 @@
         if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
             return null;
         }
-        
         if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
             return null;
         }
@@ -7425,10 +7424,6 @@
         if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED &&
                 ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDisplayList == null)) {
 
-            if (mDisplayList != null) {
-                mDisplayList.destroy();
-            }
-
             mDisplayList = mAttachInfo.mHardwareRenderer.createDisplayList();
 
             final HardwareCanvas canvas = mDisplayList.start();
@@ -7456,8 +7451,6 @@
                 canvas.onPostDraw();
 
                 mDisplayList.end();
-
-                canvas.destroy();                
             }
         }
 
@@ -7532,7 +7525,6 @@
             mUnscaledDrawingCache = null;
         }
         if (mDisplayList != null) {
-            mDisplayList.destroy();
             mDisplayList = null;
         }
     }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f34fd63..7b2703b 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1700,10 +1700,10 @@
         }
         return false;
     }
-    
+
     /**
      * {@inheritDoc}
-     * 
+     *
      * @hide
      */
     @Override
@@ -1715,10 +1715,10 @@
             children[i].dispatchStartTemporaryDetach();
         }
     }
-    
+
     /**
      * {@inheritDoc}
-     * 
+     *
      * @hide
      */
     @Override
@@ -1915,7 +1915,7 @@
         if (skipChildren) {
             for (int i = 0; i < count; i++) {
                 getChildAt(i).setVisibility(visibilities[i]);
-            }        
+            }
         }
 
         return b;
@@ -2045,7 +2045,7 @@
      *
      * @param i The current iteration.
      * @return The index of the child to draw this iteration.
-     * 
+     *
      * @see #setChildrenDrawingOrderEnabled(boolean)
      * @see #isChildrenDrawingOrderEnabled()
      */
@@ -2186,7 +2186,7 @@
                 (child.mPrivateFlags & DRAW_ANIMATION) == 0) {
             return more;
         }
-        
+
         float alpha = child.getAlpha();
         // Bail out early if the view does not need to be drawn
         if (alpha <= ViewConfiguration.ALPHA_THRESHOLD && (child.mPrivateFlags & ALPHA_SET) == 0 &&
@@ -2360,7 +2360,7 @@
         final View[] children = mChildren;
         final int count = mChildrenCount;
         for (int i = 0; i < count; i++) {
-            
+
             children[i].setSelected(selected);
         }
     }
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index ae671b8..ea688ad 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -2506,7 +2506,7 @@
                 final View prevDragView = mCurrentDragView;
 
                 // Now dispatch the drag/drop event
-                mView.dispatchDragEvent(event);
+                boolean result = mView.dispatchDragEvent(event);
 
                 // If we changed apparent drag target, tell the OS about it
                 if (prevDragView != mCurrentDragView) {
@@ -2521,6 +2521,16 @@
                         Slog.e(TAG, "Unable to note drag target change");
                     }
                 }
+
+                // Report the drop result if necessary
+                if (what == DragEvent.ACTION_DROP) {
+                    try {
+                        Log.i(TAG, "Reporting drop result: " + result);
+                        sWindowSession.reportDropResult(mWindow, result);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Unable to report drop result");
+                    }
+                }
             }
         }
         event.recycle();
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index e7aab9f..3010178 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -98,6 +98,10 @@
 
     private boolean mAcceptCookie = true;
 
+    // TODO: Remove this if/when we permanently switch to the Chromium HTTP stack
+    // http:/b/3118772
+    private static Boolean sUseChromiumHttpStack;
+
     /**
      * This contains a list of 2nd-level domains that aren't allowed to have
      * wildcards when combined with country-codes. For example: [.co.uk].
@@ -257,6 +261,13 @@
         return sRef;
     }
 
+    private static boolean useChromiumHttpStack() {
+        if (sUseChromiumHttpStack == null) {
+            sUseChromiumHttpStack = nativeUseChromiumHttpStack();
+        }
+        return sUseChromiumHttpStack;
+    }
+
     /**
      * Control whether cookie is enabled or disabled
      * @param accept TRUE if accept cookie
@@ -524,6 +535,11 @@
      * Remove all cookies
      */
     public void removeAllCookie() {
+        if (useChromiumHttpStack()) {
+            nativeRemoveAllCookie();
+            return;
+        }
+
         final Runnable clearCache = new Runnable() {
             public void run() {
                 synchronized(CookieManager.this) {
@@ -1016,4 +1032,8 @@
         }
         return ret;
     }
+
+    // Native functions
+    private static native boolean nativeUseChromiumHttpStack();
+    private static native void nativeRemoveAllCookie();
 }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index be1af65..c095199 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2388,7 +2388,7 @@
         if (!rect.equals(mLastVisibleRectSent)) {
             Point pos = new Point(rect.left, rect.top);
             mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
-                    nativeMoveGeneration(), 0, pos);
+                    nativeMoveGeneration(), mUserScroll ? 1 : 0, pos);
             mLastVisibleRectSent = rect;
         }
         Rect globalRect = new Rect();
@@ -3966,7 +3966,9 @@
                 imm.restartInput(mWebTextView);
             }
         }
-        mWebTextView.requestFocus();
+        if (isFocused()) {
+            mWebTextView.requestFocus();
+        }
     }
 
     /**
@@ -6442,6 +6444,7 @@
                 }
                 case NEW_PICTURE_MSG_ID: {
                     // called for new content
+                    mUserScroll = false;
                     final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj;
                     setBaseLayer(draw.mBaseLayer, draw.mInvalRegion.getBounds());
                     final Point viewSize = draw.mViewPoint;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 8cd9f68..1c8e2cd 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1074,7 +1074,7 @@
                             // note: these are in document coordinates
                             // (inv-zoom)
                             Point pt = (Point) msg.obj;
-                            nativeSetScrollOffset(msg.arg1, pt.x, pt.y);
+                            nativeSetScrollOffset(msg.arg1, msg.arg2, pt.x, pt.y);
                             break;
 
                         case SET_GLOBAL_BOUNDS:
@@ -2203,7 +2203,8 @@
             if (mViewportInitialScale > 0) {
                 mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
                         mViewportInitialScale / 100.0f;
-            } else if (mViewportWidth > 0 && mViewportWidth < webViewWidth) {
+            } else if (mViewportWidth > 0 && mViewportWidth < webViewWidth &&
+                !mWebView.getSettings().getUseFixedViewport()) {
                 mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
                         (float) webViewWidth / mViewportWidth;
             } else {
@@ -2347,7 +2348,7 @@
     private native void nativeScrollFocusedTextInput(float xPercent, int y);
 
     // these must be in document space (i.e. not scaled/zoomed).
-    private native void nativeSetScrollOffset(int gen, int dx, int dy);
+    private native void nativeSetScrollOffset(int gen, int userScrolled, int dx, int dy);
 
     private native void nativeSetGlobalBounds(int x, int y, int w, int h);
 
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index b0a96be..91c0fb2 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -554,9 +554,7 @@
         }
 
         if (settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS
-                && willScaleTriggerZoom(mTextWrapScale)
-                // For tablet, not much need to reflow text w/o double tapping.
-                && !settings.getUseFixedViewport()) {
+                && willScaleTriggerZoom(mTextWrapScale)) {
             refreshZoomScale(true);
         } else if (!mInZoomOverview) {
             zoomToOverview();
@@ -844,8 +842,7 @@
             if (mInitialScale > 0) {
                 scale = mInitialScale;
                 reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale);
-            } else if (viewState.mViewScale > 0 &&
-                (viewState.mMobileSite || !settings.getUseFixedViewport())) {
+            } else if (viewState.mViewScale > 0) {
                 mTextWrapScale = viewState.mTextWrapScale;
                 scale = viewState.mViewScale;
                 reflowText = false;
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index f295c41..728c2a7 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -37,7 +37,6 @@
 import android.util.LongSparseArray;
 import android.util.SparseBooleanArray;
 import android.view.ActionMode;
-import android.view.ContextMenu.ContextMenuInfo;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -51,6 +50,7 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -856,6 +856,7 @@
     @Override
     public boolean performItemClick(View view, int position, long id) {
         boolean handled = false;
+        boolean dispatchItemClick = true;
 
         if (mChoiceMode != CHOICE_MODE_NONE) {
             handled = true;
@@ -879,6 +880,7 @@
                 if (mChoiceActionMode != null) {
                     mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
                             position, id, newValue);
+                    dispatchItemClick = false;
                 }
             } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
                 boolean newValue = !mCheckStates.get(position, false);
@@ -900,7 +902,9 @@
             requestLayout();
         }
 
-        handled |= super.performItemClick(view, position, id);
+        if (dispatchItemClick) {
+            handled |= super.performItemClick(view, position, id);
+        }
 
         return handled;
     }
@@ -1984,6 +1988,11 @@
         if (mAdapter != null && mDataSetObserver == null) {
             mDataSetObserver = new AdapterDataSetObserver();
             mAdapter.registerDataSetObserver(mDataSetObserver);
+
+            // Data may have changed while we were detached. Refresh.
+            mDataChanged = true;
+            mOldItemCount = mItemCount;
+            mItemCount = mAdapter.getCount();
         }
     }
 
@@ -2916,7 +2925,8 @@
                     delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
                 }
 
-                final boolean atEnd = trackMotionScroll(delta, delta);
+                // Don't stop just because delta is zero (it could have been rounded)
+                final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0);
 
                 if (more && !atEnd) {
                     invalidate();
@@ -3353,7 +3363,10 @@
         } else {
             mFlingRunnable.endFling();
         }
-        mFlingRunnable.startScroll(distance, duration);
+        // No sense starting to scroll if we're not going anywhere
+        if (distance != 0) {
+            mFlingRunnable.startScroll(distance, duration);
+        }
     }
 
     /**
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 87453ae..bb2e26c3 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -69,12 +69,12 @@
      * The number of views that the {@link AdapterViewAnimator} keeps as children at any
      * given time (not counting views that are pending removal, see {@link #mPreviousViews}).
      */
-    int mNumActiveViews = 1;
+    int mMaxNumActiveViews = 1;
 
     /**
      * Map of the children of the {@link AdapterViewAnimator}.
      */
-    private HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>();
+    HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>();
 
     /**
      * List of views pending removal from the {@link AdapterViewAnimator}
@@ -141,8 +141,6 @@
     ObjectAnimator mInAnimation;
     ObjectAnimator mOutAnimation;
 
-    private  ArrayList<View> mViewsToBringToFront;
-
     private static final int DEFAULT_ANIMATION_DURATION = 200;
 
     public AdapterViewAnimator(Context context) {
@@ -188,10 +186,9 @@
     private void initViewAnimator() {
         mMainQueue = new Handler(Looper.myLooper());
         mPreviousViews = new ArrayList<Integer>();
-        mViewsToBringToFront = new ArrayList<View>();
     }
 
-    private class ViewAndIndex {
+    class ViewAndIndex {
         ViewAndIndex(View v, int i) {
             view = v;
             index = i;
@@ -217,7 +214,7 @@
         if (activeOffset > numVisibleViews - 1) {
             // Throw an exception here.
         }
-        mNumActiveViews = numVisibleViews;
+        mMaxNumActiveViews = numVisibleViews;
         mActiveOffset = activeOffset;
         mPreviousViews.clear();
         mViewsMap.clear();
@@ -266,10 +263,10 @@
     public void setDisplayedChild(int whichChild) {
         if (mAdapter != null) {
             mWhichChild = whichChild;
-            if (whichChild >= mAdapter.getCount()) {
-                mWhichChild = mLoopViews ? 0 : mAdapter.getCount() - 1;
+            if (whichChild >= getWindowSize()) {
+                mWhichChild = mLoopViews ? 0 : getWindowSize() - 1;
             } else if (whichChild < 0) {
-                mWhichChild = mLoopViews ? mAdapter.getCount() - 1 : 0;
+                mWhichChild = mLoopViews ? getWindowSize() - 1 : 0;
             }
 
             boolean hasFocus = getFocusedChild() != null;
@@ -327,7 +324,7 @@
         showOnly(childIndex, animate, false);
     }
 
-    private int modulo(int pos, int size) {
+    int modulo(int pos, int size) {
         if (size > 0) {
             return (size + (pos % size)) % size;
         } else {
@@ -342,9 +339,8 @@
      * @return View at this index, null if the index is outside the bounds
      */
     View getViewAtRelativeIndex(int relativeIndex) {
-        if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1 && mAdapter != null) {
-            int adapterCount =  mAdapter.getCount();
-            int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, adapterCount);
+        if (relativeIndex >= 0 && relativeIndex <= getNumActiveViews() - 1 && mAdapter != null) {
+            int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, getWindowSize());
             if (mViewsMap.get(i) != null) {
                 return mViewsMap.get(i).view;
             }
@@ -352,6 +348,27 @@
         return null;
     }
 
+    int getNumActiveViews() {
+        if (mAdapter != null) {
+            return Math.min(mAdapter.getCount() + 1, mMaxNumActiveViews);
+        } else {
+            return mMaxNumActiveViews;
+        }
+    }
+
+    int getWindowSize() {
+        if (mAdapter != null) {
+            int adapterCount = mAdapter.getCount();
+            if (adapterCount <= getNumActiveViews() && mLoopViews) {
+                return adapterCount*mMaxNumActiveViews;
+            } else {
+                return adapterCount;
+            }
+        } else {
+            return 0;
+        }
+    }
+
     LayoutParams createOrReuseLayoutParams(View v) {
         final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
         if (currentLp instanceof ViewGroup.LayoutParams) {
@@ -363,7 +380,7 @@
 
     void refreshChildren() {
         for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
-            int index = modulo(i, mNumActiveViews);
+            int index = modulo(i, mMaxNumActiveViews);
 
             // get the fresh child from the adapter
             View updatedChild = mAdapter.getView(i, null, this);
@@ -412,7 +429,7 @@
         }
         mPreviousViews.clear();
         int newWindowStartUnbounded = childIndex - mActiveOffset;
-        int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
+        int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1;
         int newWindowStart = Math.max(0, newWindowStartUnbounded);
         int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
 
@@ -420,8 +437,8 @@
             newWindowStart = newWindowStartUnbounded;
             newWindowEnd = newWindowEndUnbounded;
         }
-        int rangeStart = modulo(newWindowStart, adapterCount);
-        int rangeEnd = modulo(newWindowEnd, adapterCount);
+        int rangeStart = modulo(newWindowStart, getWindowSize());
+        int rangeEnd = modulo(newWindowEnd, getWindowSize());
 
         boolean wrap = false;
         if (rangeStart > rangeEnd) {
@@ -450,11 +467,12 @@
         }
 
         // If the window has changed
-        if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
+        if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd &&
+              newWindowStartUnbounded == mCurrentWindowStartUnbounded)) {
             // Run through the indices in the new range
             for (int i = newWindowStart; i <= newWindowEnd; i++) {
 
-                int index = modulo(i, adapterCount);
+                int index = modulo(i, getWindowSize());
                 int oldRelativeIndex;
                 if (mViewsMap.containsKey(index)) {
                     oldRelativeIndex = mViewsMap.get(index).index;
@@ -494,13 +512,6 @@
                 }
                 mViewsMap.get(index).view.bringToFront();
             }
-
-            for (int i = 0; i < mViewsToBringToFront.size(); i++) {
-                View v = mViewsToBringToFront.get(i);
-                v.bringToFront();
-            }
-            mViewsToBringToFront.clear();
-
             mCurrentWindowStart = newWindowStart;
             mCurrentWindowEnd = newWindowEnd;
             mCurrentWindowStartUnbounded = newWindowStartUnbounded;
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 54c4b36..aa68a74 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -154,6 +154,11 @@
         int textColorNormal = textColor.getDefaultColor();
         mPaint.setColor(textColorNormal);
         mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+        // to show mOverlayDrawable properly
+        if (mList.getWidth() > 0 && mList.getHeight() > 0) {
+            onSizeChanged(mList.getWidth(), mList.getHeight(), 0, 0);
+        }
         
         mState = STATE_NONE;
     }
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index d98f316..7ab95a9 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -152,7 +152,7 @@
      * @param context Context used for contained views.
      */
     public ListPopupWindow(Context context) {
-        this(context, null, 0, 0);
+        this(context, null, com.android.internal.R.attr.listPopupWindowStyle, 0);
     }
 
     /**
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 1d36b49..029aebf 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -306,6 +306,10 @@
      */
     public void setQuery(CharSequence query, boolean submit) {
         mQueryTextView.setText(query);
+        if (query != null) {
+            mQueryTextView.setSelection(query.length());
+        }
+
         // If the query is not empty and submit is requested, submit the query
         if (submit && !TextUtils.isEmpty(query)) {
             onSubmitQuery();
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 4c3927b..c05da03 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -129,7 +129,7 @@
     }
 
     private void initStackView() {
-        configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2);
+        configureViewAnimator(NUM_ACTIVE_VIEWS, 1);
         setStaticTransformationsEnabled(true);
         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
         mTouchSlop = configuration.getScaledTouchSlop();
@@ -165,7 +165,7 @@
      * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
      */
     void animateViewForTransition(int fromIndex, int toIndex, View view) {
-        if (fromIndex == -1 && toIndex == 0) {
+        if (fromIndex == -1 && toIndex != 0) {
             // Fade item in
             if (view.getAlpha() == 1) {
                 view.setAlpha(0);
@@ -175,7 +175,7 @@
             ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f);
             fadeIn.setDuration(DEFAULT_ANIMATION_DURATION);
             fadeIn.start();
-        } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) {
+        } else if (fromIndex == 0 && toIndex == 1) {
             // Slide item in
             view.setVisibility(VISIBLE);
 
@@ -189,7 +189,7 @@
             pa.setDuration(duration);
             pa.setInterpolator(new LinearInterpolator());
             pa.start();
-        } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
+        } else if (fromIndex == 1 && toIndex == 0) {
             // Slide item out
             int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
 
@@ -201,7 +201,7 @@
             pa.setDuration(duration);
             pa.setInterpolator(new LinearInterpolator());
             pa.start();
-        } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
+        } else if (fromIndex == -1 && toIndex == 0) {
             // Make sure this view that is "waiting in the wings" is invisible
             view.setAlpha(0.0f);
             view.setVisibility(INVISIBLE);
@@ -223,9 +223,10 @@
     private void transformViewAtIndex(int index, View view) {
         float maxPerpectiveShift = mMeasuredHeight * PERSPECTIVE_SHIFT_FACTOR;
 
-        if (index == mNumActiveViews -1) index--;
+        index = mMaxNumActiveViews - index - 1;
+        if (index == mMaxNumActiveViews - 1) index--;
 
-        float r = (index * 1.0f) / (mNumActiveViews - 2);
+        float r = (index * 1.0f) / (mMaxNumActiveViews - 2);
 
         float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", scale);
@@ -245,8 +246,20 @@
         pa.start();
     }
 
+    @Override
+    void showOnly(int childIndex, boolean animate, boolean onLayout) {
+        super.showOnly(childIndex, animate, onLayout);
+
+        // Here we need to make sure that the z-order of the children is correct
+	for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) {
+            int index = modulo(i, getWindowSize());
+            View v = mViewsMap.get(index).view;
+            if (v != null) v.bringToFront();
+        }
+    }
+
     private void updateChildTransforms() {
-        for (int i = 0; i < mNumActiveViews - 1; i++) {
+        for (int i = 0; i < getNumActiveViews(); i++) {
             View v = getViewAtRelativeIndex(i);
             if (v != null) {
                 transformViewAtIndex(i, v);
@@ -341,19 +354,17 @@
 
             int activeIndex;
             if (mStackMode == ITEMS_SLIDE_UP) {
-                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
-                        mNumActiveViews - 1 : mNumActiveViews - 2;
+                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
             } else {
-                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
-                        mNumActiveViews - 2 : mNumActiveViews - 1;
+                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0;
             }
 
             if (mLoopViews) {
                 mStackSlider.setMode(StackSlider.NORMAL_MODE);
-            } else if (mCurrentWindowStartUnbounded + activeIndex == 0) {
+            } else if (mCurrentWindowStartUnbounded + activeIndex == -1) {
+                activeIndex++;
                 mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE);
-            } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount()) {
-                activeIndex--;
+            } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount() - 1) {
                 mStackSlider.setMode(StackSlider.END_OF_STACK_MODE);
             } else {
                 mStackSlider.setMode(StackSlider.NORMAL_MODE);
@@ -439,8 +450,7 @@
         final int pointerId = ev.getPointerId(activePointerIndex);
         if (pointerId == mActivePointerId) {
 
-            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1
-                    : mNumActiveViews - 2;
+            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
 
             View v = getViewAtRelativeIndex(activeViewIndex);
             if (v == null) return;
@@ -498,18 +508,18 @@
                 && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
             // Swipe threshold exceeded, swipe down
             if (mStackMode == ITEMS_SLIDE_UP) {
-                showNext();
-            } else {
                 showPrevious();
+            } else {
+                showNext();
             }
             mHighlight.bringToFront();
         } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
                 && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
             // Swipe threshold exceeded, swipe up
             if (mStackMode == ITEMS_SLIDE_UP) {
-                showPrevious();
-            } else {
                 showNext();
+            } else {
+                showPrevious();
             }
 
             mHighlight.bringToFront();
@@ -643,13 +653,13 @@
                     mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
                     mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
                     break;
-                case BEGINNING_OF_STACK_MODE:
+                case END_OF_STACK_MODE:
                     r = r * 0.2f;
                     viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
                     highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
                     mHighlight.setAlpha(highlightAlphaInterpolator(r));
                     break;
-                case END_OF_STACK_MODE:
+                case BEGINNING_OF_STACK_MODE:
                     r = (1-r) * 0.2f;
                     viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
                     highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
@@ -735,12 +745,10 @@
     public void onRemoteAdapterConnected() {
         super.onRemoteAdapterConnected();
         // On first run, we want to set the stack to the end.
-        if (mAdapter != null && mWhichChild == -1) {
-            mWhichChild = mAdapter.getCount() - 1;
+        if (mWhichChild == -1) {
+            mWhichChild = 0;
         }
-        if (mWhichChild >= 0) {
-            setDisplayedChild(mWhichChild);
-        }
+        setDisplayedChild(mWhichChild);
     }
 
     LayoutParams createOrReuseLayoutParams(View v) {
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 594b1e6..0469e7b 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -26,8 +26,8 @@
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 
 /**
@@ -53,7 +53,8 @@
 public class TabWidget extends LinearLayout implements OnFocusChangeListener {
     private OnTabSelectionChanged mSelectionChangedListener;
 
-    private int mSelectedTab = 0;
+    // This value will be set to 0 as soon as the first tab is added to TabHost.
+    private int mSelectedTab = -1;
 
     private Drawable mLeftStrip;
     private Drawable mRightStrip;
@@ -98,14 +99,18 @@
 
     @Override
     protected int getChildDrawingOrder(int childCount, int i) {
-        // Always draw the selected tab last, so that drop shadows are drawn
-        // in the correct z-order.
-        if (i == childCount - 1) {
-            return mSelectedTab;
-        } else if (i >= mSelectedTab) {
-            return i + 1;
-        } else {
+        if (mSelectedTab == -1) {
             return i;
+        } else {
+            // Always draw the selected tab last, so that drop shadows are drawn
+            // in the correct z-order.
+            if (i == childCount - 1) {
+                return mSelectedTab;
+            } else if (i >= mSelectedTab) {
+                return i + 1;
+            } else {
+                return i;
+            }
         }
     }
 
@@ -340,7 +345,9 @@
             return;
         }
 
-        getChildTabViewAt(mSelectedTab).setSelected(false);
+        if (mSelectedTab != -1) {
+            getChildTabViewAt(mSelectedTab).setSelected(false);
+        }
         mSelectedTab = index;
         getChildTabViewAt(mSelectedTab).setSelected(true);
         mStripMoved = true;
@@ -354,7 +361,9 @@
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         event.setItemCount(getTabCount());
         event.setCurrentItemIndex(mSelectedTab);
-        getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
+        if (mSelectedTab != -1) {
+            getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
+        }
         return true;
     }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d071dd2..09563fc 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -990,6 +990,22 @@
         setTypeface(tf, styleIndex);
     }
 
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (enabled == isEnabled()) {
+            return;
+        }
+
+        if (!enabled) {
+            // Hide the soft input if the currently active TextView is disabled
+            InputMethodManager imm = InputMethodManager.peekInstance();
+            if (imm != null && imm.isActive(this)) {
+                imm.hideSoftInputFromWindow(getWindowToken(), 0);
+            }
+        }
+        super.setEnabled(enabled);
+    }
+
     /**
      * Sets the typeface and style in which the text should be displayed,
      * and turns on the fake bold and italic bits in the Paint if the
@@ -4640,7 +4656,7 @@
     }
     
     @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-        if (onCheckIsTextEditor()) {
+        if (onCheckIsTextEditor() && isEnabled()) {
             if (mInputMethodState == null) {
                 mInputMethodState = new InputMethodState();
             }
@@ -5002,6 +5018,9 @@
         }
 
         mLayout = mHintLayout = null;
+
+        // Since it depends on the value of mLayout
+        prepareCursorControllers();
     }
 
     /**
@@ -6910,7 +6929,8 @@
             return superResult;
         }
 
-        if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
+        if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
+                && mText instanceof Spannable && mLayout != null) {
             if (mInsertionPointCursorController != null) {
                 mInsertionPointCursorController.onTouchEvent(event);
             }
diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
index 98dcb8b..5ab9217 100644
--- a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
+++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.app;
 
+import com.android.internal.os.storage.ExternalStorageFormatter;
+
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -23,10 +25,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Bundle;
-import android.os.storage.IMountService;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.Environment;
 import android.util.Log;
 
 /**
@@ -95,14 +93,9 @@
     public void onClick(DialogInterface dialog, int which) {
 
         if (which == POSITIVE_BUTTON) {
-            IMountService mountService = IMountService.Stub.asInterface(ServiceManager
-                .getService("mount"));
-            if (mountService != null) {
-                try {
-                    mountService.formatVolume(Environment.getExternalStorageDirectory().toString());
-                } catch (RemoteException e) {
-                }
-            }
+            Intent intent = new Intent(ExternalStorageFormatter.FORMAT_ONLY);
+            intent.setComponent(ExternalStorageFormatter.COMPONENT_NAME);
+            startService(intent);
         }
 
         // No matter what, finish the activity
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5c1dbaf..0f482b7 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -51,8 +51,8 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -65,16 +65,16 @@
     private static final String TAG = "BatteryStatsImpl";
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_HISTORY = false;
-    
+
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
-    private static final int MAGIC = 0xBA757475; // 'BATSTATS' 
+    private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
     private static final int VERSION = 52;
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
-    
+
     // No, really, THIS is the maximum number of items we will record in the history.
     private static final int MAX_MAX_HISTORY_ITEMS = 3000;
 
@@ -82,9 +82,9 @@
     // per uid; once the limit is reached, we batch the remaining wakelocks
     // in to one common name.
     private static final int MAX_WAKELOCKS_PER_UID = 30;
-    
+
     private static final String BATCHED_WAKELOCK_NAME = "*overflow*";
-    
+
     private static int sNumSpeedSteps;
 
     private final JournaledFile mFile;
@@ -147,9 +147,9 @@
     // These are the objects that will want to do something when the device
     // is unplugged from power.
     final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>();
-    
+
     boolean mShuttingDown;
-    
+
     long mHistoryBaseTime;
     boolean mHaveBatteryLevel = false;
     boolean mRecordingHistory = true;
@@ -159,7 +159,7 @@
     HistoryItem mHistoryLastEnd;
     HistoryItem mHistoryCache;
     final HistoryItem mHistoryCur = new HistoryItem();
-    
+
     int mStartCount;
 
     long mBatteryUptime;
@@ -173,41 +173,41 @@
     long mRealtime;
     long mRealtimeStart;
     long mLastRealtime;
-    
+
     boolean mScreenOn;
     StopwatchTimer mScreenOnTimer;
 
     int mScreenBrightnessBin = -1;
     final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
-    
+
     Counter mInputEventCounter;
-    
+
     boolean mPhoneOn;
     StopwatchTimer mPhoneOnTimer;
-    
+
     boolean mAudioOn;
     StopwatchTimer mAudioOnTimer;
-    
+
     boolean mVideoOn;
     StopwatchTimer mVideoOnTimer;
-    
+
     int mPhoneSignalStrengthBin = -1;
-    final StopwatchTimer[] mPhoneSignalStrengthsTimer = 
+    final StopwatchTimer[] mPhoneSignalStrengthsTimer =
             new StopwatchTimer[NUM_SIGNAL_STRENGTH_BINS];
 
     StopwatchTimer mPhoneSignalScanningTimer;
 
     int mPhoneDataConnectionType = -1;
-    final StopwatchTimer[] mPhoneDataConnectionsTimer = 
+    final StopwatchTimer[] mPhoneDataConnectionsTimer =
             new StopwatchTimer[NUM_DATA_CONNECTION_TYPES];
-    
+
     boolean mWifiOn;
     StopwatchTimer mWifiOnTimer;
     int mWifiOnUid = -1;
 
     boolean mGlobalWifiRunning;
     StopwatchTimer mGlobalWifiRunningTimer;
-    
+
     boolean mBluetoothOn;
     StopwatchTimer mBluetoothOnTimer;
 
@@ -256,15 +256,15 @@
     /*
      * Holds a SamplingTimer associated with each kernel wakelock name being tracked.
      */
-    private final HashMap<String, SamplingTimer> mKernelWakelockStats = 
+    private final HashMap<String, SamplingTimer> mKernelWakelockStats =
             new HashMap<String, SamplingTimer>();
-    
+
     public Map<String, ? extends SamplingTimer> getKernelWakelockStats() {
         return mKernelWakelockStats;
     }
-    
+
     private static int sKernelWakelockUpdateVersion = 0;
-    
+
     private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
         Process.PROC_TAB_TERM|Process.PROC_OUT_STRING,                // 0: name
         Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 1: count
@@ -273,19 +273,19 @@
         Process.PROC_TAB_TERM,
         Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 5: totalTime
     };
-    
+
     private final String[] mProcWakelocksName = new String[3];
     private final long[] mProcWakelocksData = new long[3];
-    
+
     /*
      * Used as a buffer for reading in data from /proc/wakelocks before it is processed and added
      * to mKernelWakelockStats.
      */
-    private final Map<String, KernelWakelockStats> mProcWakelockFileStats = 
+    private final Map<String, KernelWakelockStats> mProcWakelockFileStats =
             new HashMap<String, KernelWakelockStats>();
 
     private HashMap<String, Integer> mUidCache = new HashMap<String, Integer>();
-    
+
     // For debugging
     public BatteryStatsImpl() {
         mFile = null;
@@ -296,7 +296,7 @@
         void unplug(long batteryUptime, long batteryRealtime);
         void plug(long batteryUptime, long batteryRealtime);
     }
-    
+
     /**
      * State for keeping track of counting information.
      */
@@ -307,7 +307,7 @@
         int mLastCount;
         int mUnpluggedCount;
         int mPluggedCount;
-        
+
         Counter(ArrayList<Unpluggable> unpluggables, Parcel in) {
             mUnpluggables = unpluggables;
             mPluggedCount = in.readInt();
@@ -322,7 +322,7 @@
             mUnpluggables = unpluggables;
             unpluggables.add(this);
         }
-        
+
         public void writeToParcel(Parcel out) {
             out.writeInt(mCount.get());
             out.writeInt(mLoadedCount);
@@ -337,7 +337,7 @@
         public void plug(long batteryUptime, long batteryRealtime) {
             mPluggedCount = mCount.get();
         }
-        
+
         /**
          * Writes a possibly null Counter to a Parcel.
          *
@@ -377,7 +377,7 @@
                     + " mUnpluggedCount=" + mUnpluggedCount
                     + " mPluggedCount=" + mPluggedCount);
         }
-        
+
         void stepAtomic() {
             mCount.incrementAndGet();
         }
@@ -392,11 +392,11 @@
                 detach();
             }
         }
-        
+
         void detach() {
             mUnpluggables.remove(this);
         }
-        
+
         void writeSummaryFromParcelLocked(Parcel out) {
             int count = mCount.get();
             out.writeInt(count);
@@ -431,41 +431,41 @@
     public static abstract class Timer extends BatteryStats.Timer implements Unpluggable {
         final int mType;
         final ArrayList<Unpluggable> mUnpluggables;
-        
+
         int mCount;
         int mLoadedCount;
         int mLastCount;
         int mUnpluggedCount;
-        
+
         // Times are in microseconds for better accuracy when dividing by the
         // lock count, and are in "battery realtime" units.
-        
+
         /**
          * The total time we have accumulated since the start of the original
          * boot, to the last time something interesting happened in the
          * current run.
          */
         long mTotalTime;
-        
+
         /**
          * The total time we loaded for the previous runs.  Subtract this from
          * mTotalTime to find the time for the current run of the system.
          */
         long mLoadedTime;
-        
+
         /**
          * The run time of the last run of the system, as loaded from the
          * saved data.
          */
         long mLastTime;
-        
+
         /**
          * The value of mTotalTime when unplug() was last called.  Subtract
          * this from mTotalTime to find the time since the last unplug from
          * power.
          */
         long mUnpluggedTime;
-        
+
         /**
          * Constructs from a parcel.
          * @param type
@@ -476,7 +476,7 @@
         Timer(int type, ArrayList<Unpluggable> unpluggables, Parcel in) {
             mType = type;
             mUnpluggables = unpluggables;
-            
+
             mCount = in.readInt();
             mLoadedCount = in.readInt();
             mLastCount = 0;
@@ -495,9 +495,9 @@
         }
 
         protected abstract long computeRunTimeLocked(long curBatteryRealtime);
-        
+
         protected abstract int computeCurrentCountLocked();
-        
+
         /**
          * Clear state of this timer.  Returns true if the timer is inactive
          * so can be completely dropped.
@@ -510,11 +510,11 @@
             }
             return true;
         }
-        
+
         void detach() {
             mUnpluggables.remove(this);
         }
-        
+
         public void writeToParcel(Parcel out, long batteryRealtime) {
             out.writeInt(mCount);
             out.writeInt(mLoadedCount);
@@ -551,7 +551,7 @@
                         + ": new mTotalTime=" + mTotalTime);
             }
         }
-        
+
         /**
          * Writes a possibly null Timer to a Parcel.
          *
@@ -612,8 +612,8 @@
             pw.println(prefix + "mLastTime=" + mLastTime
                     + " mUnpluggedTime=" + mUnpluggedTime);
         }
-        
-        
+
+
         void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) {
             long runTime = computeRunTimeLocked(batteryRealtime);
             // Divide by 1000 for backwards compatibility
@@ -631,9 +631,9 @@
             mUnpluggedCount = mCount;
         }
     }
-    
+
     public static final class SamplingTimer extends Timer {
-        
+
         /**
          * The most recent reported count from /proc/wakelocks.
          */
@@ -647,7 +647,7 @@
 
         /**
          * The most recent reported total_time from /proc/wakelocks.
-         */ 
+         */
         long mCurrentReportedTotalTime;
 
 
@@ -666,12 +666,12 @@
          * Whether we are currently recording reported values.
          */
         boolean mTrackingReportedValues;
-        
+
         /*
          * A sequnce counter, incremented once for each update of the stats.
          */
         int mUpdateVersion;
-        
+
         SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge, Parcel in) {
             super(0, unpluggables, in);
             mCurrentReportedCount = in.readInt();
@@ -681,28 +681,28 @@
             mTrackingReportedValues = in.readInt() == 1;
             mInDischarge = inDischarge;
         }
-        
-        SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge, 
+
+        SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge,
                 boolean trackReportedValues) {
             super(0, unpluggables);
             mTrackingReportedValues = trackReportedValues;
             mInDischarge = inDischarge;
         }
-        
+
         public void setStale() {
             mTrackingReportedValues = false;
             mUnpluggedReportedTotalTime = 0;
             mUnpluggedReportedCount = 0;
         }
-        
+
         public void setUpdateVersion(int version) {
             mUpdateVersion = version;
         }
-        
+
         public int getUpdateVersion() {
             return mUpdateVersion;
         }
-        
+
         public void updateCurrentReportedCount(int count) {
             if (mInDischarge && mUnpluggedReportedCount == 0) {
                 // Updating the reported value for the first time.
@@ -712,7 +712,7 @@
             }
             mCurrentReportedCount = count;
         }
-        
+
         public void updateCurrentReportedTotalTime(long totalTime) {
             if (mInDischarge && mUnpluggedReportedTotalTime == 0) {
                 // Updating the reported value for the first time.
@@ -722,7 +722,7 @@
             }
             mCurrentReportedTotalTime = totalTime;
         }
-        
+
         public void unplug(long batteryUptime, long batteryRealtime) {
             super.unplug(batteryUptime, batteryRealtime);
             if (mTrackingReportedValues) {
@@ -736,25 +736,25 @@
             super.plug(batteryUptime, batteryRealtime);
             mInDischarge = false;
         }
-        
+
         public void logState(Printer pw, String prefix) {
             super.logState(pw, prefix);
-            pw.println(prefix + "mCurrentReportedCount=" + mCurrentReportedCount 
+            pw.println(prefix + "mCurrentReportedCount=" + mCurrentReportedCount
                     + " mUnpluggedReportedCount=" + mUnpluggedReportedCount
                     + " mCurrentReportedTotalTime=" + mCurrentReportedTotalTime
                     + " mUnpluggedReportedTotalTime=" + mUnpluggedReportedTotalTime);
         }
-        
+
         protected long computeRunTimeLocked(long curBatteryRealtime) {
-            return mTotalTime + (mInDischarge && mTrackingReportedValues 
+            return mTotalTime + (mInDischarge && mTrackingReportedValues
                     ? mCurrentReportedTotalTime - mUnpluggedReportedTotalTime : 0);
         }
-        
+
         protected int computeCurrentCountLocked() {
             return mCount + (mInDischarge && mTrackingReportedValues
                     ? mCurrentReportedCount - mUnpluggedReportedCount : 0);
         }
-        
+
         public void writeToParcel(Parcel out, long batteryRealtime) {
             super.writeToParcel(out, batteryRealtime);
             out.writeInt(mCurrentReportedCount);
@@ -763,13 +763,13 @@
             out.writeLong(mUnpluggedReportedTotalTime);
             out.writeInt(mTrackingReportedValues ? 1 : 0);
         }
-        
+
         boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
             super.reset(stats, detachIfReset);
             setStale();
             return true;
         }
-        
+
         void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) {
             super.writeSummaryFromParcelLocked(out, batteryRealtime);
             out.writeLong(mCurrentReportedTotalTime);
@@ -784,7 +784,7 @@
             mTrackingReportedValues = in.readInt() == 1;
         }
     }
-    
+
     /**
      * State for keeping track of timing information.
      */
@@ -800,7 +800,7 @@
          * time we have been running since we last computed an update.
          */
         long mUpdateTime;
-        
+
         /**
          * The total time at which the timer was acquired, to determine if it
          * was actually held for an interesting duration.
@@ -829,7 +829,7 @@
             mUid = uid;
             mTimerPool = timerPool;
         }
-        
+
         void setTimeout(long timeout) {
             mTimeout = timeout;
         }
@@ -857,7 +857,7 @@
             pw.println(prefix + "mNesting=" + mNesting + "mUpdateTime=" + mUpdateTime
                     + " mAcquireTime=" + mAcquireTime);
         }
-        
+
         void startRunningLocked(BatteryStatsImpl stats) {
             if (mNesting++ == 0) {
                 mUpdateTime = stats.getBatteryRealtimeLocked(
@@ -897,19 +897,19 @@
                     // Remove this timer from the active pool
                     mTimerPool.remove(this);
                 } else {
-                    final long realtime = SystemClock.elapsedRealtime() * 1000; 
+                    final long realtime = SystemClock.elapsedRealtime() * 1000;
                     final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
                     mNesting = 1;
                     mTotalTime = computeRunTimeLocked(batteryRealtime);
                     mNesting = 0;
                 }
-                
+
                 if (DEBUG && mType < 0) {
                     Log.v(TAG, "stop #" + mType + ": mUpdateTime=" + mUpdateTime
                             + " mTotalTime=" + mTotalTime + " mCount=" + mCount
                             + " mAcquireTime=" + mAcquireTime);
                 }
-                
+
                 if (mTotalTime == mAcquireTime) {
                     // If there was no change in the time, then discard this
                     // count.  A somewhat cheezy strategy, but hey.
@@ -922,7 +922,7 @@
         // due to a change in timer count
         private static void refreshTimersLocked(final BatteryStatsImpl stats,
                 final ArrayList<StopwatchTimer> pool) {
-            final long realtime = SystemClock.elapsedRealtime() * 1000; 
+            final long realtime = SystemClock.elapsedRealtime() * 1000;
             final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
             final int N = pool.size();
             for (int i=N-1; i>= 0; i--) {
@@ -961,25 +961,25 @@
             mAcquireTime = mTotalTime;
             return canDetach;
         }
-        
+
         void detach() {
             super.detach();
             if (mTimerPool != null) {
                 mTimerPool.remove(this);
             }
         }
-        
+
         void readSummaryFromParcelLocked(Parcel in) {
             super.readSummaryFromParcelLocked(in);
             mNesting = 0;
         }
     }
-    
+
     private final Map<String, KernelWakelockStats> readKernelWakelockStats() {
-        
+
         byte[] buffer = new byte[8192];
         int len;
-        
+
         try {
             FileInputStream is = new FileInputStream("/proc/wakelocks");
             len = is.read(buffer);
@@ -999,10 +999,10 @@
         } catch (java.io.IOException e) {
             return null;
         }
-        
+
         return parseProcWakelocks(buffer, len);
     }
-    
+
     private final Map<String, KernelWakelockStats> parseProcWakelocks(
             byte[] wlBuffer, int len) {
         String name;
@@ -1018,11 +1018,11 @@
 
         synchronized(this) {
             Map<String, KernelWakelockStats> m = mProcWakelockFileStats;
-            
+
             sKernelWakelockUpdateVersion++;
             while (endIndex < len) {
-                for (endIndex=startIndex; 
-                        endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0'; 
+                for (endIndex=startIndex;
+                        endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
                         endIndex++);
                 endIndex++; // endIndex is an exclusive upper bound.
                 // Don't go over the end of the buffer, Process.parseProcLine might
@@ -1049,7 +1049,7 @@
 
                 if (parsed && name.length() > 0) {
                     if (!m.containsKey(name)) {
-                        m.put(name, new KernelWakelockStats(count, totalTime, 
+                        m.put(name, new KernelWakelockStats(count, totalTime,
                                 sKernelWakelockUpdateVersion));
                         numUpdatedWlNames++;
                     } else {
@@ -1080,27 +1080,27 @@
             return m;
         }
     }
-    
+
     private class KernelWakelockStats {
         public int mCount;
         public long mTotalTime;
         public int mVersion;
-        
+
         KernelWakelockStats(int count, long totalTime, int version) {
             mCount = count;
             mTotalTime = totalTime;
             mVersion = version;
         }
     }
-    
+
     /*
-     * Get the KernelWakelockTimer associated with name, and create a new one if one 
+     * Get the KernelWakelockTimer associated with name, and create a new one if one
      * doesn't already exist.
      */
     public SamplingTimer getKernelWakelockTimerLocked(String name) {
         SamplingTimer kwlt = mKernelWakelockStats.get(name);
         if (kwlt == null) {
-            kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal, 
+            kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal,
                     true /* track reported values */);
             mKernelWakelockStats.put(name, kwlt);
         }
@@ -1144,7 +1144,7 @@
     }
 
     /**
-     * Returns the duration that the cell radio was up for data transfers. 
+     * Returns the duration that the cell radio was up for data transfers.
      */
     public long getRadioDataUptime() {
         if (mRadioDataStart == -1) {
@@ -1156,10 +1156,9 @@
 
     private int getCurrentBluetoothPingCount() {
         if (mBtHeadset != null) {
-            Set<BluetoothDevice> deviceSet = mBtHeadset.getConnectedDevices();
-            BluetoothDevice[] devices = deviceSet.toArray(new BluetoothDevice[deviceSet.size()]);
-            if (devices.length > 0) {
-                return mBtHeadset.getBatteryUsageHint(devices[0]);
+            List<BluetoothDevice> deviceList = mBtHeadset.getConnectedDevices();
+            if (deviceList.size() > 0) {
+                return mBtHeadset.getBatteryUsageHint(deviceList.get(0));
             }
         }
         return -1;
@@ -1234,7 +1233,7 @@
 
         addHistoryRecordLocked(curTime, HistoryItem.CMD_UPDATE);
     }
-    
+
     void addHistoryRecordLocked(long curTime, byte cmd) {
         HistoryItem rec = mHistoryCache;
         if (rec != null) {
@@ -1243,10 +1242,10 @@
             rec = new HistoryItem();
         }
         rec.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur);
-        
+
         addHistoryRecordLocked(rec);
     }
-    
+
     void addHistoryRecordLocked(HistoryItem rec) {
         mNumHistoryItems++;
         rec.next = null;
@@ -1258,7 +1257,7 @@
             mHistory = mHistoryEnd = rec;
         }
     }
-    
+
     void clearHistoryLocked() {
         if (mHistory != null) {
             mHistoryEnd.next = mHistoryCache;
@@ -1268,7 +1267,7 @@
         mNumHistoryItems = 0;
         mHistoryBaseTime = 0;
     }
-    
+
     public void doUnplugLocked(long batteryUptime, long batteryRealtime) {
         for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
             Uid u = mUidStats.valueAt(iu);
@@ -1532,7 +1531,7 @@
     }
 
     int mGpsNesting;
-    
+
     public void noteStartGpsLocked(int uid) {
         if (mGpsNesting == 0) {
             mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG;
@@ -1543,7 +1542,7 @@
         mGpsNesting++;
         getUidStatsLocked(uid).noteStartGps();
     }
-    
+
     public void noteStopGpsLocked(int uid) {
         mGpsNesting--;
         if (mGpsNesting == 0) {
@@ -1572,7 +1571,7 @@
             noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
         }
     }
-    
+
     public void noteScreenOffLocked() {
         if (mScreenOn) {
             mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
@@ -1588,7 +1587,7 @@
             noteStopWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
         }
     }
-    
+
     public void noteScreenBrightnessLocked(int brightness) {
         // Bin the brightness.
         int bin = brightness / (256/NUM_SCREEN_BRIGHTNESS_BINS);
@@ -1609,15 +1608,15 @@
             mScreenBrightnessBin = bin;
         }
     }
-    
+
     public void noteInputEventAtomic() {
         mInputEventCounter.stepAtomic();
     }
-    
+
     public void noteUserActivityLocked(int uid, int event) {
         getUidStatsLocked(uid).noteUserActivityLocked(event);
     }
-    
+
     public void notePhoneOnLocked() {
         if (!mPhoneOn) {
             mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG;
@@ -1628,7 +1627,7 @@
             mPhoneOnTimer.startRunningLocked(this);
         }
     }
-    
+
     public void notePhoneOffLocked() {
         if (mPhoneOn) {
             mHistoryCur.states &= ~HistoryItem.STATE_PHONE_IN_CALL_FLAG;
@@ -1688,7 +1687,7 @@
                 mPhoneSignalScanningTimer.startRunningLocked(this);
             }
         }
-        
+
         if (!scanning) {
             // If we are no longer scanning, then stop the scanning timer.
             if (mPhoneSignalScanningTimer.isRunningLocked()) {
@@ -1746,7 +1745,7 @@
             mPhoneSignalStrengthsTimer[bin].startRunningLocked(this);
         }
     }
-    
+
     public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData) {
         int bin = DATA_CONNECTION_NONE;
         if (hasData) {
@@ -1806,7 +1805,7 @@
             mPhoneDataConnectionsTimer[bin].startRunningLocked(this);
         }
     }
-    
+
     public void noteWifiOnLocked() {
         if (!mWifiOn) {
             mHistoryCur.states |= HistoryItem.STATE_WIFI_ON_FLAG;
@@ -1817,7 +1816,7 @@
             mWifiOnTimer.startRunningLocked(this);
         }
     }
-    
+
     public void noteWifiOffLocked() {
         if (mWifiOn) {
             mHistoryCur.states &= ~HistoryItem.STATE_WIFI_ON_FLAG;
@@ -1844,7 +1843,7 @@
         }
         getUidStatsLocked(uid).noteAudioTurnedOnLocked();
     }
-    
+
     public void noteAudioOffLocked(int uid) {
         if (mAudioOn) {
             mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
@@ -1868,7 +1867,7 @@
         }
         getUidStatsLocked(uid).noteVideoTurnedOnLocked();
     }
-    
+
     public void noteVideoOffLocked(int uid) {
         if (mVideoOn) {
             mHistoryCur.states &= ~HistoryItem.STATE_VIDEO_ON_FLAG;
@@ -1940,7 +1939,7 @@
             mBluetoothOnTimer.startRunningLocked(this);
         }
     }
-    
+
     public void noteBluetoothOffLocked() {
         if (mBluetoothOn) {
             mHistoryCur.states &= ~HistoryItem.STATE_BLUETOOTH_ON_FLAG;
@@ -1951,9 +1950,9 @@
             mBluetoothOnTimer.stopRunningLocked(this);
         }
     }
-    
+
     int mWifiFullLockNesting = 0;
-    
+
     public void noteFullWifiLockAcquiredLocked(int uid) {
         if (mWifiFullLockNesting == 0) {
             mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
@@ -1977,7 +1976,7 @@
     }
 
     int mWifiScanLockNesting = 0;
-    
+
     public void noteScanWifiLockAcquiredLocked(int uid) {
         if (mWifiScanLockNesting == 0) {
             mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG;
@@ -2001,7 +2000,7 @@
     }
 
     int mWifiMulticastNesting = 0;
-    
+
     public void noteWifiMulticastEnabledLocked(int uid) {
         if (mWifiMulticastNesting == 0) {
             mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
@@ -2069,7 +2068,7 @@
     @Override public long getScreenOnTime(long batteryRealtime, int which) {
         return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which);
     }
-    
+
     @Override public long getScreenBrightnessTime(int brightnessBin,
             long batteryRealtime, int which) {
         return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked(
@@ -2079,7 +2078,7 @@
     @Override public int getInputEventCount(int which) {
         return mInputEventCounter.getCountLocked(which);
     }
-    
+
     @Override public long getPhoneOnTime(long batteryRealtime, int which) {
         return mPhoneOnTimer.getTotalTimeLocked(batteryRealtime, which);
     }
@@ -2099,21 +2098,21 @@
     @Override public int getPhoneSignalStrengthCount(int dataType, int which) {
         return mPhoneDataConnectionsTimer[dataType].getCountLocked(which);
     }
-    
+
     @Override public long getPhoneDataConnectionTime(int dataType,
             long batteryRealtime, int which) {
         return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked(
                 batteryRealtime, which);
     }
-    
+
     @Override public int getPhoneDataConnectionCount(int dataType, int which) {
         return mPhoneDataConnectionsTimer[dataType].getCountLocked(which);
     }
-    
+
     @Override public long getWifiOnTime(long batteryRealtime, int which) {
         return mWifiOnTimer.getTotalTimeLocked(batteryRealtime, which);
     }
-    
+
     @Override public long getGlobalWifiRunningTime(long batteryRealtime, int which) {
         return mGlobalWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which);
     }
@@ -2121,11 +2120,11 @@
     @Override public long getBluetoothOnTime(long batteryRealtime, int which) {
         return mBluetoothOnTimer.getTotalTimeLocked(batteryRealtime, which);
     }
-    
+
     @Override public boolean getIsOnBattery() {
         return mOnBattery;
     }
-    
+
     @Override public SparseArray<? extends BatteryStats.Uid> getUidStats() {
         return mUidStats;
     }
@@ -2134,7 +2133,7 @@
      * The statistics associated with a particular uid.
      */
     public final class Uid extends BatteryStats.Uid {
-        
+
         final int mUid;
         long mLoadedTcpBytesReceived;
         long mLoadedTcpBytesSent;
@@ -2142,32 +2141,32 @@
         long mCurrentTcpBytesSent;
         long mTcpBytesReceivedAtLastUnplug;
         long mTcpBytesSentAtLastUnplug;
-        
+
         // These are not saved/restored when parcelling, since we want
         // to return from the parcel with a snapshot of the state.
         long mStartedTcpBytesReceived = -1;
         long mStartedTcpBytesSent = -1;
-        
+
         boolean mWifiRunning;
         StopwatchTimer mWifiRunningTimer;
-        
+
         boolean mFullWifiLockOut;
         StopwatchTimer mFullWifiLockTimer;
-        
+
         boolean mScanWifiLockOut;
         StopwatchTimer mScanWifiLockTimer;
-        
+
         boolean mWifiMulticastEnabled;
         StopwatchTimer mWifiMulticastTimer;
-        
+
         boolean mAudioTurnedOn;
         StopwatchTimer mAudioTurnedOnTimer;
-        
+
         boolean mVideoTurnedOn;
         StopwatchTimer mVideoTurnedOnTimer;
 
         Counter[] mUserActivityCounters;
-        
+
         /**
          * The statistics we have collected for this uid's wake locks.
          */
@@ -2187,7 +2186,7 @@
          * The statistics we have collected for this uid's processes.
          */
         final HashMap<String, Pkg> mPackageStats = new HashMap<String, Pkg>();
-        
+
         /**
          * The transient wake stats we have collected for this uid's pids.
          */
@@ -2248,7 +2247,7 @@
                 return current;
             }
         }
-        
+
         public long computeCurrentTcpBytesReceived() {
             return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
                     ? (TrafficStats.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
@@ -2268,7 +2267,7 @@
                 return current;
             }
         }
-        
+
         @Override
         public void noteWifiRunningLocked() {
             if (!mWifiRunning) {
@@ -2280,7 +2279,7 @@
                 mWifiRunningTimer.startRunningLocked(BatteryStatsImpl.this);
             }
         }
-        
+
         @Override
         public void noteWifiStoppedLocked() {
             if (mWifiRunning) {
@@ -2288,7 +2287,7 @@
                 mWifiRunningTimer.stopRunningLocked(BatteryStatsImpl.this);
             }
         }
-        
+
         @Override
         public void noteFullWifiLockAcquiredLocked() {
             if (!mFullWifiLockOut) {
@@ -2300,7 +2299,7 @@
                 mFullWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
             }
         }
-        
+
         @Override
         public void noteFullWifiLockReleasedLocked() {
             if (mFullWifiLockOut) {
@@ -2308,7 +2307,7 @@
                 mFullWifiLockTimer.stopRunningLocked(BatteryStatsImpl.this);
             }
         }
-        
+
         @Override
         public void noteScanWifiLockAcquiredLocked() {
             if (!mScanWifiLockOut) {
@@ -2320,7 +2319,7 @@
                 mScanWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
             }
         }
-        
+
         @Override
         public void noteScanWifiLockReleasedLocked() {
             if (mScanWifiLockOut) {
@@ -2389,7 +2388,7 @@
             }
         }
 
-        @Override 
+        @Override
         public long getWifiRunningTime(long batteryRealtime, int which) {
             if (mWifiRunningTimer == null) {
                 return 0;
@@ -2397,15 +2396,15 @@
             return mWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which);
         }
 
-        @Override 
+        @Override
         public long getFullWifiLockTime(long batteryRealtime, int which) {
             if (mFullWifiLockTimer == null) {
                 return 0;
             }
             return mFullWifiLockTimer.getTotalTimeLocked(batteryRealtime, which);
         }
-        
-        @Override 
+
+        @Override
         public long getScanWifiLockTime(long batteryRealtime, int which) {
             if (mScanWifiLockTimer == null) {
                 return 0;
@@ -2422,7 +2421,7 @@
                                                           which);
         }
 
-        @Override 
+        @Override
         public long getAudioTurnedOnTime(long batteryRealtime, int which) {
             if (mAudioTurnedOnTimer == null) {
                 return 0;
@@ -2430,7 +2429,7 @@
             return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
         }
 
-        @Override 
+        @Override
         public long getVideoTurnedOnTime(long batteryRealtime, int which) {
             if (mVideoTurnedOnTimer == null) {
                 return 0;
@@ -2447,12 +2446,12 @@
             else if (type >= NUM_USER_ACTIVITY_TYPES) type = NUM_USER_ACTIVITY_TYPES-1;
             mUserActivityCounters[type].stepAtomic();
         }
-        
+
         @Override
         public boolean hasUserActivity() {
             return mUserActivityCounters != null;
         }
-        
+
         @Override
         public int getUserActivityCount(int type, int which) {
             if (mUserActivityCounters == null) {
@@ -2460,14 +2459,14 @@
             }
             return mUserActivityCounters[type].getCountLocked(which);
         }
-        
+
         void initUserActivityLocked() {
             mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
             for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
                 mUserActivityCounters[i] = new Counter(mUnpluggables);
             }
         }
-        
+
         public long computeCurrentTcpBytesSent() {
             return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
                     ? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
@@ -2479,7 +2478,7 @@
          */
         boolean reset() {
             boolean active = false;
-            
+
             if (mWifiRunningTimer != null) {
                 active |= !mWifiRunningTimer.reset(BatteryStatsImpl.this, false);
                 active |= mWifiRunning;
@@ -2504,10 +2503,10 @@
                 active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false);
                 active |= mVideoTurnedOn;
             }
-            
+
             mLoadedTcpBytesReceived = mLoadedTcpBytesSent = 0;
             mCurrentTcpBytesReceived = mCurrentTcpBytesSent = 0;
-            
+
             if (mUserActivityCounters != null) {
                 for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
                     mUserActivityCounters[i].reset(false);
@@ -2571,7 +2570,7 @@
                 }
                 mPackageStats.clear();
             }
-            
+
             mPids.clear();
 
             if (!active) {
@@ -2599,10 +2598,10 @@
                     }
                 }
             }
-            
+
             return !active;
         }
-        
+
         void writeToParcelLocked(Parcel out, long batteryRealtime) {
             out.writeInt(mWakelockStats.size());
             for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) {
@@ -2631,7 +2630,7 @@
                 Uid.Pkg pkg = pkgEntry.getValue();
                 pkg.writeToParcelLocked(out);
             }
-            
+
             out.writeLong(mLoadedTcpBytesReceived);
             out.writeLong(mLoadedTcpBytesSent);
             out.writeLong(computeCurrentTcpBytesReceived());
@@ -2725,7 +2724,7 @@
                 pkg.readFromParcelLocked(in);
                 mPackageStats.put(packageName, pkg);
             }
-            
+
             mLoadedTcpBytesReceived = in.readLong();
             mLoadedTcpBytesSent = in.readLong();
             mCurrentTcpBytesReceived = in.readLong();
@@ -2846,7 +2845,7 @@
                 }
                 return !wlactive;
             }
-            
+
             void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
                 mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL,
                         mPartialTimers, unpluggables, in);
@@ -2876,7 +2875,7 @@
         public final class Sensor extends BatteryStats.Uid.Sensor {
             final int mHandle;
             StopwatchTimer mTimer;
-            
+
             public Sensor(int handle) {
                 mHandle = handle;
             }
@@ -2902,7 +2901,7 @@
                 }
                 return false;
             }
-            
+
             void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
                 mTimer = readTimerFromParcel(unpluggables, in);
             }
@@ -3024,7 +3023,7 @@
 
             public void plug(long batteryUptime, long batteryRealtime) {
             }
-            
+
             void detach() {
                 mUnpluggables.remove(this);
                 for (int i = 0; i < mSpeedBins.length; i++) {
@@ -3035,7 +3034,7 @@
                     }
                 }
             }
-            
+
             public int countExcessivePowers() {
                 return mExcessivePower != null ? mExcessivePower.size() : 0;
             }
@@ -3302,18 +3301,18 @@
             Pkg() {
                 mUnpluggables.add(this);
             }
-            
+
             public void unplug(long batteryUptime, long batteryRealtime) {
                 mUnpluggedWakeups = mWakeups;
             }
 
             public void plug(long batteryUptime, long batteryRealtime) {
             }
-            
+
             void detach() {
                 mUnpluggables.remove(this);
             }
-            
+
             void readFromParcelLocked(Parcel in) {
                 mWakeups = in.readInt();
                 mLoadedWakeups = in.readInt();
@@ -3464,7 +3463,7 @@
                 Serv() {
                     mUnpluggables.add(this);
                 }
-                
+
                 public void unplug(long batteryUptime, long batteryRealtime) {
                     mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime);
                     mUnpluggedStarts = mStarts;
@@ -3473,11 +3472,11 @@
 
                 public void plug(long batteryUptime, long batteryRealtime) {
                 }
-                
+
                 void detach() {
                     mUnpluggables.remove(this);
                 }
-                
+
                 void readFromParcelLocked(Parcel in) {
                     mStartTime = in.readLong();
                     mRunningSince = in.readLong();
@@ -3652,7 +3651,7 @@
         public SparseArray<? extends Pid> getPidStats() {
             return mPids;
         }
-        
+
         public Pid getPidStatsLocked(int pid) {
             Pid p = mPids.get(pid);
             if (p == null) {
@@ -3790,7 +3789,7 @@
                 p.addExcessiveWake(overTime, usedTime);
             }
         }
-        
+
         public void reportExcessiveCpuLocked(String proc, long overTime, long usedTime) {
             Proc p = getProcessStatsLocked(proc);
             if (p != null) {
@@ -3802,7 +3801,7 @@
             StopwatchTimer t = getSensorTimerLocked(sensor, true);
             if (t != null) {
                 t.startRunningLocked(BatteryStatsImpl.this);
-            }            
+            }
         }
 
         public void noteStopSensor(int sensor) {
@@ -3810,16 +3809,16 @@
             StopwatchTimer t = getSensorTimerLocked(sensor, false);
             if (t != null) {
                 t.stopRunningLocked(BatteryStatsImpl.this);
-            }            
+            }
         }
-        
+
         public void noteStartGps() {
             StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, true);
             if (t != null) {
                 t.startRunningLocked(BatteryStatsImpl.this);
-            }  
+            }
         }
-        
+
         public void noteStopGps() {
             StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false);
             if (t != null) {
@@ -3909,12 +3908,12 @@
     public HistoryItem getHistory() {
         return mHistory;
     }
-    
+
     @Override
     public long getHistoryBaseTime() {
         return mHistoryBaseTime;
     }
-    
+
     @Override
     public int getStartCount() {
         return mStartCount;
@@ -3936,7 +3935,7 @@
         mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
         mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
     }
-    
+
     public void resetAllStatsLocked() {
         mStartCount = 0;
         initTimes();
@@ -3958,24 +3957,24 @@
         mWifiOnTimer.reset(this, false);
         mGlobalWifiRunningTimer.reset(this, false);
         mBluetoothOnTimer.reset(this, false);
-        
+
         for (int i=0; i<mUidStats.size(); i++) {
             if (mUidStats.valueAt(i).reset()) {
                 mUidStats.remove(mUidStats.keyAt(i));
                 i--;
             }
         }
-        
+
         if (mKernelWakelockStats.size() > 0) {
             for (SamplingTimer timer : mKernelWakelockStats.values()) {
                 mUnpluggables.remove(timer);
             }
             mKernelWakelockStats.clear();
         }
-        
+
         clearHistoryLocked();
     }
-    
+
     void setOnBattery(boolean onBattery, int oldStatus, int level) {
         synchronized(this) {
             boolean doWrite = false;
@@ -3983,7 +3982,7 @@
             m.arg1 = onBattery ? 1 : 0;
             mHandler.sendMessage(m);
             mOnBattery = mOnBatteryInternal = onBattery;
-            
+
             long uptime = SystemClock.uptimeMillis() * 1000;
             long mSecRealtime = SystemClock.elapsedRealtime();
             long realtime = mSecRealtime * 1000;
@@ -3993,8 +3992,8 @@
                 // we have gone through a significant charge (from a very low
                 // level to a now very high level).
                 if (oldStatus == BatteryManager.BATTERY_STATUS_FULL
-                        || level >= 100
-                        || (mDischargeCurrentLevel < 20 && level > 90)) {
+                        || level >= 95
+                        || (mDischargeCurrentLevel < 30 && level >= 90)) {
                     doWrite = true;
                     resetAllStatsLocked();
                     mDischargeStartLevel = level;
@@ -4036,10 +4035,10 @@
             }
         }
     }
-    
+
     // This should probably be exposed in the API, though it's not critical
     private static final int BATTERY_PLUGGED_NONE = 0;
-    
+
     public void setBatteryState(int status, int health, int plugType, int level,
             int temp, int volt) {
         boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
@@ -4107,10 +4106,10 @@
             mRecordingHistory = false;
         }
     }
-    
+
     public void updateKernelWakelocksLocked() {
         Map<String, KernelWakelockStats> m = readKernelWakelockStats();
-        
+
         if (m == null) {
             // Not crashing might make board bringup easier.
             Slog.w(TAG, "Couldn't get kernel wake lock stats");
@@ -4120,10 +4119,10 @@
         for (Map.Entry<String, KernelWakelockStats> ent : m.entrySet()) {
             String name = ent.getKey();
             KernelWakelockStats kws = ent.getValue();
-        
+
             SamplingTimer kwlt = mKernelWakelockStats.get(name);
             if (kwlt == null) {
-                kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal, 
+                kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal,
                         true /* track reported values */);
                 mKernelWakelockStats.put(name, kwlt);
             }
@@ -4131,7 +4130,7 @@
             kwlt.updateCurrentReportedTotalTime(kws.mTotalTime);
             kwlt.setUpdateVersion(sKernelWakelockUpdateVersion);
         }
-        
+
         if (m.size() != mKernelWakelockStats.size()) {
             // Set timers to stale if they didn't appear in /proc/wakelocks this time.
             for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
@@ -4276,18 +4275,18 @@
             return getDischargeStartLevelLocked();
         }
     }
-    
+
     public int getDischargeStartLevelLocked() {
             return mDischargeUnplugLevel;
     }
-    
+
     @Override
     public int getDischargeCurrentLevel() {
         synchronized(this) {
             return getDischargeCurrentLevelLocked();
         }
     }
-    
+
     public int getDischargeCurrentLevelLocked() {
             return mDischargeCurrentLevel;
     }
@@ -4430,7 +4429,7 @@
         writeSyncLocked();
         mShuttingDown = true;
     }
-    
+
     Parcel mPendingWrite = null;
     final ReentrantLock mWriteLock = new ReentrantLock();
 
@@ -4451,7 +4450,7 @@
         if (mShuttingDown) {
             return;
         }
-        
+
         Parcel out = Parcel.obtain();
         writeSummaryToParcel(out);
         mLastWriteTime = SystemClock.elapsedRealtime();
@@ -4551,7 +4550,7 @@
         } catch(java.io.IOException e) {
             Slog.e("BatteryStats", "Error reading battery statistics", e);
         }
-        
+
         addHistoryRecordLocked(SystemClock.elapsedRealtime(), HistoryItem.CMD_START);
     }
 
@@ -4570,7 +4569,7 @@
                 mHistoryBaseTime = rec.time;
             }
         }
-        
+
         long oldnow = SystemClock.elapsedRealtime() - (5*60*100);
         if (oldnow > 0) {
             // If the system process has restarted, but not the entire
@@ -4582,7 +4581,7 @@
             mHistoryBaseTime -= oldnow;
         }
     }
-    
+
     void writeHistory(Parcel out) {
         HistoryItem rec = mHistory;
         while (rec != null) {
@@ -4591,7 +4590,7 @@
         }
         out.writeLong(-1);
     }
-    
+
     private void readSummaryFromParcel(Parcel in) {
         final int version = in.readInt();
         if (version != VERSION) {
@@ -4601,7 +4600,7 @@
         }
 
         readHistory(in);
-        
+
         mStartCount = in.readInt();
         mBatteryUptime = in.readLong();
         mBatteryRealtime = in.readLong();
@@ -4611,9 +4610,9 @@
         mDischargeCurrentLevel = in.readInt();
         mLowDischargeAmountSinceCharge = in.readInt();
         mHighDischargeAmountSinceCharge = in.readInt();
-        
+
         mStartCount++;
-        
+
         mScreenOn = false;
         mScreenOnTimer.readSummaryFromParcelLocked(in);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
@@ -4693,7 +4692,7 @@
                     u.mUserActivityCounters[i].readSummaryFromParcelLocked(in);
                 }
             }
-            
+
             int NW = in.readInt();
             if (NW > 10000) {
                 Slog.w(TAG, "File corrupt: too many wake locks " + NW);
@@ -4786,7 +4785,7 @@
         out.writeInt(VERSION);
 
         writeHistory(out);
-        
+
         out.writeInt(mStartCount);
         out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED));
         out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED));
@@ -4796,7 +4795,7 @@
         out.writeInt(mDischargeCurrentLevel);
         out.writeInt(mLowDischargeAmountSinceCharge);
         out.writeInt(mHighDischargeAmountSinceCharge);
-        
+
         mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
@@ -4825,14 +4824,14 @@
                 out.writeInt(0);
             }
         }
-        
+
         out.writeInt(sNumSpeedSteps);
         final int NU = mUidStats.size();
         out.writeInt(NU);
         for (int iu = 0; iu < NU; iu++) {
             out.writeInt(mUidStats.keyAt(iu));
             Uid u = mUidStats.valueAt(iu);
-            
+
             if (u.mWifiRunningTimer != null) {
                 out.writeInt(1);
                 u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL);
@@ -4878,7 +4877,7 @@
                     u.mUserActivityCounters[i].writeSummaryFromParcelLocked(out);
                 }
             }
-            
+
             int NW = u.mWakelockStats.size();
             out.writeInt(NW);
             if (NW > 0) {
@@ -4970,7 +4969,7 @@
                     }
                 }
             }
-            
+
             out.writeLong(u.getTcpBytesReceived(STATS_SINCE_CHARGED));
             out.writeLong(u.getTcpBytesSent(STATS_SINCE_CHARGED));
         }
@@ -4979,7 +4978,7 @@
     public void readFromParcel(Parcel in) {
         readFromParcelLocked(in);
     }
-    
+
     void readFromParcelLocked(Parcel in) {
         int magic = in.readInt();
         if (magic != MAGIC) {
@@ -4987,7 +4986,7 @@
         }
 
         readHistory(in);
-        
+
         mStartCount = in.readInt();
         mBatteryUptime = in.readLong();
         mBatteryLastUptime = 0;
@@ -5062,7 +5061,7 @@
                 mKernelWakelockStats.put(wakelockName, kwlt);
             }
         }
-        
+
         mPartialTimers.clear();
         mFullTimers.clear();
         mWindowTimers.clear();
@@ -5090,18 +5089,18 @@
     public void writeToParcelWithoutUids(Parcel out, int flags) {
         writeToParcelLocked(out, false, flags);
     }
-    
-    @SuppressWarnings("unused") 
+
+    @SuppressWarnings("unused")
     void writeToParcelLocked(Parcel out, boolean inclUids, int flags) {
         final long uSecUptime = SystemClock.uptimeMillis() * 1000;
         final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
         final long batteryUptime = getBatteryUptimeLocked(uSecUptime);
         final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime);
-        
+
         out.writeInt(MAGIC);
-        
+
         writeHistory(out);
-        
+
         out.writeInt(mStartCount);
         out.writeLong(mBatteryUptime);
         out.writeLong(mBatteryRealtime);
@@ -5190,7 +5189,7 @@
             return new BatteryStatsImpl[size];
         }
     };
-    
+
     public void dumpLocked(PrintWriter pw) {
         if (DEBUG) {
             Printer pr = new PrintWriterPrinter(pw);
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java
index aaaa194..7920b72 100644
--- a/core/java/com/android/internal/util/HierarchicalStateMachine.java
+++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java
@@ -1094,7 +1094,9 @@
      * @param msg that couldn't be handled.
      */
     protected void unhandledMessage(Message msg) {
-        Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
+        if (false) {
+            Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java
index d381901..b54daba 100644
--- a/core/java/com/android/internal/view/StandaloneActionMode.java
+++ b/core/java/com/android/internal/view/StandaloneActionMode.java
@@ -87,7 +87,6 @@
         mFinished = true;
 
         mCallback.onDestroyActionMode(this);
-        mContextView.setVisibility(View.GONE);
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 5d66372..38f76d3 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -213,7 +213,7 @@
         }
 
         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-        if (heightMode != MeasureSpec.AT_MOST) {
+        if (heightMode == MeasureSpec.UNSPECIFIED) {
             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
                     "with android:layout_height=\"wrap_content\"");
         }
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 880fb6e..8956e39 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -255,13 +255,20 @@
 static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {

 #ifdef USE_OPENGL_RENDERER

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

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

+        android::uirenderer::Caches::getInstance().resourceCache.destructor(bitmap);

+        return;

     }

-#endif

+#endif // USE_OPENGL_RENDERER

     delete bitmap;

 }

 

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

+#ifdef USE_OPENGL_RENDERER

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

+        android::uirenderer::Caches::getInstance().resourceCache.recycle(bitmap);

+        return;

+    }

+#endif // USE_OPENGL_RENDERER

     bitmap->setPixels(NULL, NULL);

 }

 

diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index f700791..bf18d55 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -65,6 +65,7 @@
         }
     }
     data = (char*)sk_realloc_throw(data, streamLen);
+
     SkMemoryStream* streamMem = new SkMemoryStream();
     streamMem->setMemoryOwned(data, streamLen);
     return streamMem;
@@ -133,6 +134,12 @@
         }
         stream = fdStream;
     } else {
+        /* Restore our offset when we leave, so we can be called more than once
+           with the same descriptor. This is only required if we didn't dup the
+           file descriptor, but it is OK to do it all the time.
+        */
+        AutoFDSeek as(descriptor);
+
         SkFDStream* fdStream = new SkFDStream(descriptor, false);
         if (!fdStream->isValid()) {
             fdStream->unref();
@@ -142,12 +149,6 @@
         fdStream->unref();
     }
 
-    /* Restore our offset when we leave, so we can be called more than once
-       with the same descriptor. This is only required if we didn't dup the
-       file descriptor, but it is OK to do it all the time.
-    */
-    AutoFDSeek as(descriptor);
-
     return doBuildTileIndex(env, stream);
 }
 
diff --git a/core/jni/android/graphics/Matrix.cpp b/core/jni/android/graphics/Matrix.cpp
index cafceab..b305506 100644
--- a/core/jni/android/graphics/Matrix.cpp
+++ b/core/jni/android/graphics/Matrix.cpp
@@ -15,11 +15,6 @@
 ** limitations under the License.
 */
 
-// This file was generated from the C++ include file: SkMatrix.h
-// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
-// or one of the auxilary file specifications in device/tools/gluemaker.
-
 #include "jni.h"
 #include "GraphicsJNI.h"
 #include <android_runtime/AndroidRuntime.h>
@@ -29,12 +24,20 @@
 
 #include "Matrix.h"
 
+#include <Caches.h>
+
 namespace android {
 
 class SkMatrixGlue {
 public:
 
     static void finalizer(JNIEnv* env, jobject clazz, SkMatrix* obj) {
+#ifdef USE_OPENGL_RENDERER
+        if (android::uirenderer::Caches::hasInstance()) {
+            android::uirenderer::Caches::getInstance().resourceCache.destructor(obj);
+            return;
+        }
+#endif // USE_OPENGL_RENDERER
         delete obj;
     }
 
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index ca9f371..79a02f1 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -15,11 +15,6 @@
 ** limitations under the License.
 */
 
-// This file was generated from the C++ include file: SkPaint.h
-// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
-// or one of the auxilary file specifications in device/tools/gluemaker.
-
 #include "jni.h"
 #include "GraphicsJNI.h"
 #include <android_runtime/AndroidRuntime.h>
@@ -35,6 +30,7 @@
 #include "TextLayout.h"
 
 // temporary for debugging
+#include <Caches.h>
 #include <utils/Log.h>
 
 namespace android {
@@ -67,6 +63,12 @@
     };
 
     static void finalizer(JNIEnv* env, jobject clazz, SkPaint* obj) {
+#ifdef USE_OPENGL_RENDERER
+        if (android::uirenderer::Caches::hasInstance()) {
+            android::uirenderer::Caches::getInstance().resourceCache.destructor(obj);
+            return;
+        }
+#endif // USE_OPENGL_RENDERER
         delete obj;
     }
 
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index ee44747..79051c2 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -53,13 +53,13 @@
 
 static void Shader_destructor(JNIEnv* env, jobject o, SkShader* shader, SkiaShader* skiaShader)
 {
+    shader->safeUnref();
+    // skiaShader == NULL when not !USE_OPENGL_RENDERER, so no need to delete it outside the ifdef
 #ifdef USE_OPENGL_RENDERER
     if (android::uirenderer::Caches::hasInstance()) {
-        android::uirenderer::Caches::getInstance().gradientCache.remove(shader);
+        android::uirenderer::Caches::getInstance().resourceCache.destructor(skiaShader);
     }
 #endif
-    delete skiaShader;
-    shader->safeUnref();
 }
 
 static bool Shader_getLocalMatrix(JNIEnv* env, jobject, const SkShader* shader, SkMatrix* matrix)
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 6aa77f6..45fd5a0 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -35,8 +35,8 @@
 #include "android_view_InputChannel.h"
 #include "android_view_KeyEvent.h"
 
-//#define LOG_TRACE(...)
-#define LOG_TRACE(...) LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define LOG_TRACE(...)
+//#define LOG_TRACE(...) LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
 
 namespace android
 {
@@ -46,6 +46,7 @@
 
     jmethodID dispatchUnhandledKeyEvent;
     jmethodID preDispatchKeyEvent;
+    jmethodID finish;
     jmethodID setWindowFlags;
     jmethodID setWindowFormat;
     jmethodID showIme;
@@ -62,6 +63,7 @@
 
 enum {
     CMD_DEF_KEY = 1,
+    CMD_FINISH,
     CMD_SET_WINDOW_FORMAT,
     CMD_SET_WINDOW_FLAGS,
     CMD_SHOW_SOFT_INPUT,
@@ -512,6 +514,11 @@
     sp<Looper> looper;
 };
 
+void android_NativeActivity_finish(ANativeActivity* activity) {
+    NativeCode* code = static_cast<NativeCode*>(activity);
+    write_work(code->mainWorkWrite, CMD_FINISH, 0);
+}
+
 void android_NativeActivity_setWindowFormat(
         ANativeActivity* activity, int32_t format) {
     NativeCode* code = static_cast<NativeCode*>(activity);
@@ -538,6 +545,16 @@
 
 // ------------------------------------------------------------------------
 
+static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+   if (env->ExceptionCheck()) {
+       LOGE("An exception was thrown by callback '%s'.", methodName);
+       LOGE_EX(env);
+       env->ExceptionClear();
+       return true;
+   }
+   return false;
+}
+
 /*
  * Callback for handling native events on the application's main thread.
  */
@@ -562,6 +579,7 @@
                         code->env, keyEvent);
                 code->env->CallVoidMethod(code->clazz,
                         gNativeActivityClassInfo.dispatchUnhandledKeyEvent, inputEventObj);
+                checkAndClearExceptionFromCallback(code->env, "dispatchUnhandledKeyEvent");
                 code->nativeInputQueue->finishEvent(keyEvent, true);
             }
             int seq;
@@ -570,23 +588,32 @@
                         code->env, keyEvent);
                 code->env->CallVoidMethod(code->clazz,
                         gNativeActivityClassInfo.preDispatchKeyEvent, inputEventObj, seq);
+                checkAndClearExceptionFromCallback(code->env, "preDispatchKeyEvent");
             }
         } break;
+        case CMD_FINISH: {
+            code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.finish);
+            checkAndClearExceptionFromCallback(code->env, "finish");
+        } break;
         case CMD_SET_WINDOW_FORMAT: {
             code->env->CallVoidMethod(code->clazz,
                     gNativeActivityClassInfo.setWindowFormat, work.arg1);
+            checkAndClearExceptionFromCallback(code->env, "setWindowFormat");
         } break;
         case CMD_SET_WINDOW_FLAGS: {
             code->env->CallVoidMethod(code->clazz,
                     gNativeActivityClassInfo.setWindowFlags, work.arg1, work.arg2);
+            checkAndClearExceptionFromCallback(code->env, "setWindowFlags");
         } break;
         case CMD_SHOW_SOFT_INPUT: {
             code->env->CallVoidMethod(code->clazz,
                     gNativeActivityClassInfo.showIme, work.arg1);
+            checkAndClearExceptionFromCallback(code->env, "showIme");
         } break;
         case CMD_HIDE_SOFT_INPUT: {
             code->env->CallVoidMethod(code->clazz,
                     gNativeActivityClassInfo.hideIme, work.arg1);
+            checkAndClearExceptionFromCallback(code->env, "hideIme");
         } break;
         default:
             LOGW("Unknown work command: %d", work.cmd);
@@ -599,7 +626,8 @@
 // ------------------------------------------------------------------------
 
 static jint
-loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jobject messageQueue,
+loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName,
+        jobject messageQueue,
         jstring internalDataDir, jstring externalDataDir, int sdkVersion,
         jobject jAssetMgr, jbyteArray savedState)
 {
@@ -613,8 +641,11 @@
     env->ReleaseStringUTFChars(path, pathStr);
     
     if (handle != NULL) {
+        const char* funcStr = env->GetStringUTFChars(funcName, NULL);
         code = new NativeCode(handle, (ANativeActivity_createFunc*)
-                dlsym(handle, "ANativeActivity_onCreate"));
+                dlsym(handle, funcStr));
+        env->ReleaseStringUTFChars(funcName, funcStr);
+        
         if (code->createActivityFunc == NULL) {
             LOGW("ANativeActivity_onCreate not found");
             delete code;
@@ -972,7 +1003,7 @@
 }
 
 static const JNINativeMethod g_methods[] = {
-    { "loadNativeCode", "(Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;[B)I",
+    { "loadNativeCode", "(Ljava/lang/String;Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;[B)I",
             (void*)loadNativeCode_native },
     { "unloadNativeCode", "(I)V", (void*)unloadNativeCode_native },
     { "onStartNative", "(I)V", (void*)onStart_native },
@@ -1018,6 +1049,9 @@
             gNativeActivityClassInfo.clazz,
             "preDispatchKeyEvent", "(Landroid/view/KeyEvent;I)V");
 
+    GET_METHOD_ID(gNativeActivityClassInfo.finish,
+            gNativeActivityClassInfo.clazz,
+            "finish", "()V");
     GET_METHOD_ID(gNativeActivityClassInfo.setWindowFlags,
             gNativeActivityClassInfo.clazz,
             "setWindowFlags", "(II)V");
diff --git a/core/jni/android_nfc_NdefMessage.cpp b/core/jni/android_nfc_NdefMessage.cpp
index 99295f4..eaf989d 100644
--- a/core/jni/android_nfc_NdefMessage.cpp
+++ b/core/jni/android_nfc_NdefMessage.cpp
@@ -16,6 +16,8 @@
 
 #define LOG_TAG "NdefMessage"
 
+#include <stdlib.h>
+
 #include "jni.h"
 #include "JNIHelp.h"
 
diff --git a/core/jni/android_nfc_NdefRecord.cpp b/core/jni/android_nfc_NdefRecord.cpp
index 9d20d6d..0a3a519 100644
--- a/core/jni/android_nfc_NdefRecord.cpp
+++ b/core/jni/android_nfc_NdefRecord.cpp
@@ -16,6 +16,8 @@
 
 #define LOG_TAG "NdefRecord"
 
+#include <stdlib.h>
+
 #include "jni.h"
 #include "JNIHelp.h"
 
diff --git a/core/jni/android_text_AndroidBidi.cpp b/core/jni/android_text_AndroidBidi.cpp
index 7696bb3..53028c3 100644
--- a/core/jni/android_text_AndroidBidi.cpp
+++ b/core/jni/android_text_AndroidBidi.cpp
@@ -17,7 +17,7 @@
 
 #define LOG_TAG "AndroidUnicode"
 
-#include <jni.h>
+#include "JNIHelp.h"
 #include <android_runtime/AndroidRuntime.h>
 #include "utils/misc.h"
 #include "utils/Log.h"
@@ -25,14 +25,6 @@
 
 namespace android {
     
-static void jniThrowException(JNIEnv* env, const char* exc, const char* msg = NULL)
-{
-    jclass excClazz = env->FindClass(exc);
-    LOG_ASSERT(excClazz, "Unable to find class %s", exc);
-
-    env->ThrowNew(excClazz, msg);
-}
-
 static jint runBidi(JNIEnv* env, jobject obj, jint dir, jcharArray chsArray, 
                     jbyteArray infoArray, int n, jboolean haveInfo)
 {
diff --git a/core/jni/android_text_AndroidCharacter.cpp b/core/jni/android_text_AndroidCharacter.cpp
index 5d8d419..6b90541 100644
--- a/core/jni/android_text_AndroidCharacter.cpp
+++ b/core/jni/android_text_AndroidCharacter.cpp
@@ -17,7 +17,7 @@
 
 #define LOG_TAG "AndroidUnicode"
 
-#include <jni.h>
+#include "JNIHelp.h"
 #include <android_runtime/AndroidRuntime.h>
 #include "utils/misc.h"
 #include "utils/Log.h"
@@ -50,14 +50,6 @@
 
 namespace android {
     
-static void jniThrowException(JNIEnv* env, const char* exc, const char* msg = NULL)
-{
-    jclass excClazz = env->FindClass(exc);
-    LOG_ASSERT(excClazz, "Unable to find class %s", exc);
-
-    env->ThrowNew(excClazz, msg);
-}
-
 static void getDirectionalities(JNIEnv* env, jobject obj, jcharArray srcArray, jbyteArray destArray, int count)
 {
     jchar* src = env->GetCharArrayElements(srcArray, NULL);
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index cfa9a27..6d848a4 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -87,7 +87,7 @@
 #endif
 }
 
-static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject canvas,
+static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer) {
     RENDERER_LOGD("Destroy OpenGLRenderer");
     delete renderer;
@@ -415,7 +415,7 @@
 }
 
 static void android_view_GLES20Canvas_destroyDisplayList(JNIEnv* env,
-        jobject canvas, DisplayList* displayList) {
+        jobject clazz, DisplayList* displayList) {
     delete displayList;
 }
 
diff --git a/core/res/res/color/primary_text_holo_dark.xml b/core/res/res/color/primary_text_holo_dark.xml
index 69ee309..6418664 100644
--- a/core/res/res/color/primary_text_holo_dark.xml
+++ b/core/res/res/color/primary_text_holo_dark.xml
@@ -15,10 +15,10 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:color="@android:color/bright_foreground_dark_disabled"/>
-    <item android:state_window_focused="false" android:color="@android:color/bright_foreground_dark"/>
-    <item android:state_pressed="true" android:color="@android:color/bright_foreground_dark"/>
-    <item android:state_selected="true" android:color="@android:color/bright_foreground_dark"/>
-    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark"/>
-    <item android:color="@android:color/bright_foreground_dark"/> <!-- not selected -->
+    <item android:state_enabled="false" android:color="@android:color/bright_foreground_disabled_holo_dark"/>
+    <item android:state_window_focused="false" android:color="@android:color/bright_foreground_holo_dark"/>
+    <item android:state_pressed="true" android:color="@android:color/bright_foreground_holo_dark"/>
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_holo_dark"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_holo_dark"/>
+    <item android:color="@android:color/bright_foreground_holo_dark"/> <!-- not selected -->
 </selector>
diff --git a/core/res/res/color/primary_text_holo_light.xml b/core/res/res/color/primary_text_holo_light.xml
index a8d31ce..86f8f86 100644
--- a/core/res/res/color/primary_text_holo_light.xml
+++ b/core/res/res/color/primary_text_holo_light.xml
@@ -15,12 +15,12 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:color="@android:color/bright_foreground_light_disabled"/>
-    <item android:state_window_focused="false" android:color="@android:color/bright_foreground_light"/>
-    <item android:state_pressed="true" android:color="@android:color/bright_foreground_light"/>
-    <item android:state_selected="true" android:color="@android:color/bright_foreground_light"/>
-    <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/>
-    <item android:color="@android:color/bright_foreground_light"/> <!-- not selected -->
+    <item android:state_enabled="false" android:color="@android:color/bright_foreground_disabled_holo_light"/>
+    <item android:state_window_focused="false" android:color="@android:color/bright_foreground_holo_light"/>
+    <item android:state_pressed="true" android:color="@android:color/bright_foreground_holo_light"/>
+    <item android:state_selected="true" android:color="@android:color/bright_foreground_holo_light"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_holo_light"/>
+    <item android:color="@android:color/bright_foreground_holo_light"/> <!-- not selected -->
     
 </selector>
 
diff --git a/core/res/res/color/secondary_text_holo_dark.xml b/core/res/res/color/secondary_text_holo_dark.xml
index 376156e..881a1de3 100644
--- a/core/res/res/color/secondary_text_holo_dark.xml
+++ b/core/res/res/color/secondary_text_holo_dark.xml
@@ -15,13 +15,13 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/>
-    <item android:state_window_focused="false" android:color="@android:color/dim_foreground_dark"/>
-    <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
-    <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
-    <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/>
-    <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/>
-    <item android:state_pressed="true" android:color="@android:color/dim_foreground_dark_inverse"/>
-    <item android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/>
-    <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected -->
+    <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_disabled_holo_dark"/>
+    <item android:state_window_focused="false" android:color="@android:color/dim_foreground_holo_dark"/>
+    <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_inverse_disabled_holo_dark"/>
+    <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_inverse_disabled_holo_dark"/>
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_inverse_holo_dark"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_inverse_holo_dark"/>
+    <item android:state_pressed="true" android:color="@android:color/dim_foreground_inverse_holo_dark"/>
+    <item android:state_enabled="false" android:color="@android:color/dim_foreground_disabled_holo_dark"/>
+    <item android:color="@android:color/dim_foreground_holo_dark"/> <!-- not selected -->
 </selector>
diff --git a/core/res/res/color/secondary_text_holo_light.xml b/core/res/res/color/secondary_text_holo_light.xml
index b791aeb..05721b2 100644
--- a/core/res/res/color/secondary_text_holo_light.xml
+++ b/core/res/res/color/secondary_text_holo_light.xml
@@ -15,14 +15,14 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
-    <item android:state_window_focused="false" android:color="@android:color/dim_foreground_light"/>
+    <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_disabled_holo_light"/>
+    <item android:state_window_focused="false" android:color="@android:color/dim_foreground_holo_light"/>
     <!-- Since there is only one selector (for both light and dark), the light's selected state shouldn't be inversed like the dark's. -->
-    <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
-    <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
-    <item android:state_pressed="true" android:color="@android:color/dim_foreground_light"/>
-    <item android:state_selected="true" android:color="@android:color/dim_foreground_light"/>
-    <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/>
-    <item android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
-    <item android:color="@android:color/dim_foreground_light"/> <!-- not selected -->
+    <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_disabled_holo_light"/>
+    <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_disabled_holo_light"/>
+    <item android:state_pressed="true" android:color="@android:color/dim_foreground_holo_light"/>
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_holo_light"/>
+    <item android:state_activated="true" android:color="@android:color/bright_foreground_holo_light"/>
+    <item android:state_enabled="false" android:color="@android:color/dim_foreground_disabled_holo_light"/>
+    <item android:color="@android:color/dim_foreground_holo_light"/> <!-- not selected -->
 </selector>
diff --git a/core/res/res/color/tertiary_text_holo_dark.xml b/core/res/res/color/tertiary_text_holo_dark.xml
index 269ff71..0718d7d 100644
--- a/core/res/res/color/tertiary_text_holo_dark.xml
+++ b/core/res/res/color/tertiary_text_holo_dark.xml
@@ -18,7 +18,7 @@
     <item android:state_enabled="false" android:color="#808080"/>
     <item android:state_window_focused="false" android:color="#808080"/>
     <item android:state_pressed="true" android:color="#808080"/>
-    <item android:state_selected="true" android:color="@android:color/dim_foreground_light"/>
+    <item android:state_selected="true" android:color="@android:color/dim_foreground_holo_light"/>
     <item android:color="#808080"/> <!-- not selected -->
 </selector>
 
diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png
index 05d1668..19ad5c9 100644
--- a/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png
index 6f7eb42..180cdf5 100644
--- a/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png
index 2c814f0..c66bb07 100644
--- a/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png
index a88bdf6..c0d3dd7 100644
--- a/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png
index 9a61d82..b440e15 100644
--- a/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png
index 56ca528..8769d0a 100644
--- a/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png
index 6600035..7b8e9e8 100644
--- a/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png
index 11d31bb..37c8028 100644
--- a/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png
index 8c58c37..aa08f45 100644
--- a/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png
index d543c66..3e99cb6 100644
--- a/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_activated_holo.9.png b/core/res/res/drawable-hdpi/list_activated_holo.9.png
new file mode 100644
index 0000000..cd9a08e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_activated_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_divider_holo_dark.9.png b/core/res/res/drawable-hdpi/list_divider_holo_dark.9.png
new file mode 100644
index 0000000..7b18202
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_divider_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_divider_holo_light.9.png b/core/res/res/drawable-hdpi/list_divider_holo_light.9.png
new file mode 100644
index 0000000..17bcbb4
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_divider_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_focused_holo.9.png b/core/res/res/drawable-hdpi/list_focused_holo.9.png
new file mode 100644
index 0000000..b91e9f2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_focused_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_longpressed_holo.9.png b/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
new file mode 100644
index 0000000..db4831d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/list_pressed_holo_dark.9.png
new file mode 100644
index 0000000..1ce1601
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/list_pressed_holo_light.9.png
new file mode 100644
index 0000000..b36aa2c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_section_divider_holo_dark.9.png b/core/res/res/drawable-hdpi/list_section_divider_holo_dark.9.png
new file mode 100644
index 0000000..2dabb5f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_section_divider_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_section_divider_holo_light.9.png b/core/res/res/drawable-hdpi/list_section_divider_holo_light.9.png
new file mode 100644
index 0000000..763f4f6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_section_divider_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
new file mode 100644
index 0000000..c054e14
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selected_holo_light.9.png b/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
new file mode 100644
index 0000000..77c4032
--- /dev/null
+++ b/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png
index 15003be..ca2271a 100644
--- a/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png
index 573f197..0d498e3 100644
--- a/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png
index 953a553..ae922ce 100644
--- a/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png
index d79a61e..89aef31 100644
--- a/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png
index 897676e..1f4be44 100644
--- a/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png
index b485925..e4d0ba0 100644
--- a/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png
index 1cdbf66..050fb19 100644
--- a/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png
index ab7eb54..75651fc 100644
--- a/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png
index d95ef6d..c7d02cf 100644
--- a/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png
index 60ed4fc..40c9f64 100644
--- a/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_activated_holo.9.png b/core/res/res/drawable-mdpi/list_activated_holo.9.png
new file mode 100644
index 0000000..01ec674
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_activated_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_divider_holo_dark.9.png b/core/res/res/drawable-mdpi/list_divider_holo_dark.9.png
new file mode 100644
index 0000000..7b18202
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_divider_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_divider_holo_light.9.png b/core/res/res/drawable-mdpi/list_divider_holo_light.9.png
new file mode 100644
index 0000000..17bcbb4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_divider_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_focused_holo.9.png b/core/res/res/drawable-mdpi/list_focused_holo.9.png
new file mode 100644
index 0000000..23385df
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_focused_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_longpressed_holo.9.png b/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
new file mode 100644
index 0000000..3312a51
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/list_pressed_holo_dark.9.png
new file mode 100644
index 0000000..bbb14d8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/list_pressed_holo_light.9.png
new file mode 100644
index 0000000..72380d4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_section_divider_holo_dark.9.png b/core/res/res/drawable-mdpi/list_section_divider_holo_dark.9.png
new file mode 100644
index 0000000..953d7bf
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_section_divider_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_section_divider_holo_light.9.png b/core/res/res/drawable-mdpi/list_section_divider_holo_light.9.png
new file mode 100644
index 0000000..e5fe664
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_section_divider_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
new file mode 100644
index 0000000..c453506
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selected_holo_light.9.png b/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
new file mode 100644
index 0000000..58b6b3c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable/activated_background_holo_dark.xml b/core/res/res/drawable/activated_background_holo_dark.xml
index f71235e..febf2c4 100644
--- a/core/res/res/drawable/activated_background_holo_dark.xml
+++ b/core/res/res/drawable/activated_background_holo_dark.xml
@@ -15,6 +15,6 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_activated="true" android:drawable="@android:drawable/list_selector_activated_holo_dark" />
+    <item android:state_activated="true" android:drawable="@android:drawable/list_activated_holo" />
     <item android:drawable="@color/transparent" />
 </selector>
diff --git a/core/res/res/drawable/activated_background_holo_light.xml b/core/res/res/drawable/activated_background_holo_light.xml
index 615033d..febf2c4 100644
--- a/core/res/res/drawable/activated_background_holo_light.xml
+++ b/core/res/res/drawable/activated_background_holo_light.xml
@@ -15,6 +15,6 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_activated="true" android:drawable="@android:drawable/list_selector_activated_holo_light" />
+    <item android:state_activated="true" android:drawable="@android:drawable/list_activated_holo" />
     <item android:drawable="@color/transparent" />
 </selector>
diff --git a/core/res/res/drawable/btn_default_holo_light.xml b/core/res/res/drawable/btn_default_holo_light.xml
new file mode 100644
index 0000000..61bb1bd
--- /dev/null
+++ b/core/res/res/drawable/btn_default_holo_light.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_default_normal_holo_light" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_default_disabled_holo_light" />
+    <item android:state_pressed="true"
+        android:drawable="@drawable/btn_default_pressed_holo_light" />
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_default_focused_holo_light" />
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_default_normal_holo_light" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_default_disabled_focused_holo_light" />
+    <item
+         android:drawable="@drawable/btn_default_disabled_holo_light" />
+</selector>
diff --git a/core/res/res/drawable/list_selector_background_transition_holo_dark.xml b/core/res/res/drawable/list_selector_background_transition_holo_dark.xml
new file mode 100644
index 0000000..7c68426
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background_transition_holo_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:drawable/list_pressed_holo_dark"  />
+    <item android:drawable="@android:drawable/list_longpressed_holo"  />
+</transition>
diff --git a/core/res/res/drawable/list_selector_background_transition_holo_light.xml b/core/res/res/drawable/list_selector_background_transition_holo_light.xml
new file mode 100644
index 0000000..fc08a84
--- /dev/null
+++ b/core/res/res/drawable/list_selector_background_transition_holo_light.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:drawable/list_pressed_holo_light"  />
+    <item android:drawable="@android:drawable/list_longpressed_holo"  />
+</transition>
diff --git a/core/res/res/drawable/list_selector_holo_dark.xml b/core/res/res/drawable/list_selector_holo_dark.xml
index 9cc993a..e4c5c52 100644
--- a/core/res/res/drawable/list_selector_holo_dark.xml
+++ b/core/res/res/drawable/list_selector_holo_dark.xml
@@ -21,7 +21,7 @@
     <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
     <item android:state_focused="true"  android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/list_selector_disabled_holo_dark" />
     <item android:state_focused="true"  android:state_enabled="false"                              android:drawable="@drawable/list_selector_disabled_holo_dark" />
-    <item android:state_focused="true"                                android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" />
-    <item android:state_focused="false"                               android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" />
-    <item android:state_focused="true"                                                             android:drawable="@drawable/list_selector_focused_holo_dark" />
+    <item android:state_focused="true"                                android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_dark" />
+    <item android:state_focused="false"                               android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_dark" />
+    <item android:state_focused="true"                                                             android:drawable="@drawable/list_focused_holo" />
 </selector>
diff --git a/core/res/res/drawable/list_selector_holo_light.xml b/core/res/res/drawable/list_selector_holo_light.xml
index f2def40..17631bd 100644
--- a/core/res/res/drawable/list_selector_holo_light.xml
+++ b/core/res/res/drawable/list_selector_holo_light.xml
@@ -21,8 +21,8 @@
     <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
     <item android:state_focused="true"  android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/list_selector_disabled_holo_light" />
     <item android:state_focused="true"  android:state_enabled="false"                              android:drawable="@drawable/list_selector_disabled_holo_light" />
-    <item android:state_focused="true"                                android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" />
-    <item android:state_focused="false"                               android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" />
-    <item android:state_focused="true"                                                             android:drawable="@drawable/list_selector_focused_holo_light" />
+    <item android:state_focused="true"                                android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_light" />
+    <item android:state_focused="false"                               android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_light" />
+    <item android:state_focused="true"                                                             android:drawable="@drawable/list_focused_holo" />
 
 </selector>
diff --git a/core/res/res/raw-ar/loaderror.html b/core/res/res/raw-ar/loaderror.html
index edcd63a..aa9805c 100644
--- a/core/res/res/raw-ar/loaderror.html
+++ b/core/res/res/raw-ar/loaderror.html
@@ -2,7 +2,7 @@
     <head>
         <title>صفحة الويب غير متوفرة</title>
         <style type="text/css">
-            body { margin-top: 0px; padding-top: 0px; }
+            body { margin-top: 0px; padding-top: 0px; direction: rtl; }
             h2   { margin-top: 5px; padding-top: 0px; }
         </style>
 
diff --git a/core/res/res/raw-ar/nodomain.html b/core/res/res/raw-ar/nodomain.html
index bc070f6..2e5849f 100644
--- a/core/res/res/raw-ar/nodomain.html
+++ b/core/res/res/raw-ar/nodomain.html
@@ -2,7 +2,7 @@
     <head>
         <title>صفحة الويب غير متوفرة</title>
         <style type="text/css">
-            body { margin-top: 0px; padding-top: 0px; }
+            body { margin-top: 0px; padding-top: 0px; direction: rtl; }
             h2   { margin-top: 5px; padding-top: 0px; }
         </style>
 
diff --git a/core/res/res/raw-iw/loaderror.html b/core/res/res/raw-iw/loaderror.html
index 8155432..8d5a53f 100644
--- a/core/res/res/raw-iw/loaderror.html
+++ b/core/res/res/raw-iw/loaderror.html
@@ -2,7 +2,7 @@
     <head>
         <title>דף אינטרנט לא זמין</title>
         <style type="text/css">
-            body { margin-top: 0px; padding-top: 0px; }
+            body { margin-top: 0px; padding-top: 0px; direction: rtl; }
             h2   { margin-top: 5px; padding-top: 0px; }
         </style>
 
diff --git a/core/res/res/raw-iw/nodomain.html b/core/res/res/raw-iw/nodomain.html
index f7b9f42..0dcd7f8 100644
--- a/core/res/res/raw-iw/nodomain.html
+++ b/core/res/res/raw-iw/nodomain.html
@@ -2,7 +2,7 @@
     <head>
         <title>דף אינטרנט לא זמין</title>
         <style type="text/css">
-            body { margin-top: 0px; padding-top: 0px; }
+            body { margin-top: 0px; padding-top: 0px; direction: rtl; }
             h2   { margin-top: 5px; padding-top: 0px; }
         </style>
 
diff --git a/core/res/res/values-xlarge/config.xml b/core/res/res/values-xlarge/config.xml
index 813651e..9504d04 100644
--- a/core/res/res/values-xlarge/config.xml
+++ b/core/res/res/values-xlarge/config.xml
@@ -20,9 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Component to be used as the status bar service.  Must implement the IStatusBar
-         interface.  This name is in the ComponentName flattened format (package/class)  -->
-    <string name="config_statusBarComponent">com.android.systemui/com.android.systemui.statusbar.tablet.TabletStatusBarService</string>
     <bool name="config_statusBarCanHide">false</bool>
 
     <!-- see comment in values/config.xml -->
diff --git a/core/res/res/values-zh-rCN/donottranslate-cldr.xml b/core/res/res/values-zh-rCN/donottranslate-cldr.xml
index bbf7df5..ec9d8c0 100644
--- a/core/res/res/values-zh-rCN/donottranslate-cldr.xml
+++ b/core/res/res/values-zh-rCN/donottranslate-cldr.xml
@@ -144,6 +144,6 @@
     <string name="same_month_mdy1_mdy2">%9$s 年 %2$s %3$s - %8$s 日</string>
     <string name="same_year_wday1_mdy1_wday2_mdy2">%9$s 年 %2$s %3$s 日%1$s - %7$s %8$s 日%6$s</string>
     <string name="short_format_month">%b</string>
-    <string name="full_wday_month_day_no_year">M 月 d 日E</string>
-    <string name="abbrev_wday_month_day_year">yyyy 年 M 月 d 日EEE</string>
+    <string name="full_wday_month_day_no_year">M 月 d 日 E</string>
+    <string name="abbrev_wday_month_day_year">yyyy 年 M 月 d 日 EEE</string>
 </resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 949d960..065c10d 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1441,6 +1441,38 @@
         <attr name="functionalTest" />
     </declare-styleable>
     
+    <!-- Attributes that can be supplied in an AndroidManifest.xml
+         <code>screen</code> tag, a child of <code>compatible-screens</code>,
+         which is itseld a child of the root
+         {@link #AndroidManifest manifest} tag. -->
+    <declare-styleable name="AndroidManifestCompatibleScreensScreen">
+        <!-- Specifies a compatible screen size, as per the device
+             configuration screen size bins. -->
+        <attr name="screenSize">
+            <!-- A small screen configuration, at least 240x320db. -->
+            <enum name="small" value="200" />
+            <!-- A normal screen configuration, at least 320x480db. -->
+            <enum name="normal" value="300" />
+            <!-- A large screen configuration, at least 400x530db. -->
+            <enum name="large" value="400" />
+            <!-- An extra large screen configuration, at least 600x800db. -->
+            <enum name="xlarge" value="500" />
+        </attr>
+        <!-- Specifies a compatible screen density, as per the device
+             configuration screen density bins. -->
+        <attr name="screenDensity" format="integer">
+            <!-- A low density screen, approximately 120dpi. -->
+            <enum name="ldpi" value="120" />
+            <!-- A medium density screen, approximately 160dpi. -->
+            <enum name="mdpi" value="160" />
+            <!-- A high density screen, approximately 240dpi. -->
+            <enum name="hdpi" value="240" />
+            <!-- An extra high density screen, approximately 320dpi. -->
+            <enum name="xhdpi" value="320" />
+        </attr>
+    </declare-styleable>
+    
+    
     <!-- Declaration of an {@link android.content.Intent} object in XML.  May
          also include zero or more {@link #IntentCategory <category> and
          {@link #Extra <extra>} tags. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index de2b930..a5c9a40 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -20,9 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Component to be used as the status bar service.  Must implement the IStatusBar
-         interface.  This name is in the ComponentName flattened format (package/class)  -->
-    <string name="config_statusBarComponent">com.android.systemui/com.android.systemui.statusbar.PhoneStatusBarService</string>
     <bool name="config_statusBarCanHide">true</bool>
 
     <!-- Do not translate. Defines the slots for the right-hand side icons.  That is to say, the
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5add8d6a..47bb5f8 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1257,6 +1257,8 @@
   <public type="attr" name="textSelectHandle" id="0x010102c7" />
   <public type="attr" name="textSelectHandleWindowStyle" id="0x010102c8" />
   <public type="attr" name="popupAnimationStyle" id="0x010102c9" />
+  <public type="attr" name="screenSize" id="0x010102ca" />
+  <public type="attr" name="screenDensity" id="0x010102cb" />
 
   <!-- presence drawables for videochat or audiochat capable contacts -->
   <public type="drawable" name="presence_video_away" id="0x010800ac" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 5e77ee1..6f6f1db 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1289,7 +1289,7 @@
     </style>
 
     <style name="Widget.Holo.TextView.ListSeparator" parent="Widget.TextView.ListSeparator">
-        <item name="android:background">@android:drawable/list_section_header_holo_dark</item>
+        <item name="android:background">@android:drawable/list_section_divider_holo_dark</item>
     </style>
 
     <style name="Widget.Holo.TextSelectHandle" parent="Widget.TextSelectHandle">
@@ -1332,6 +1332,8 @@
     </style>
 
     <style name="Widget.Holo.ListView" parent="Widget.ListView">
+        <item name="android:divider">?android:attr/listDivider</item>
+        <item name="android:listSelector">?android:attr/listChoiceBackgroundIndicator</item>
     </style>
 
     <style name="Widget.Holo.ListView.White">
@@ -1521,6 +1523,12 @@
     </style>
 
     <style name="Widget.Holo.Light.Button" parent="Widget.Button">
+        <item name="android:background">@android:drawable/btn_default_holo_light</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
+        <item name="android:textColor">@android:color/primary_text_holo_light</item>
+        <item name="android:minHeight">48dip</item>
+        <item name="android:paddingLeft">32dip</item>
+        <item name="android:paddingRight">32dip</item>
     </style>
 
     <style name="Widget.Holo.Light.Button.Small">
@@ -1556,7 +1564,7 @@
     </style>
 
     <style name="Widget.Holo.Light.TextView.ListSeparator" parent="Widget.TextView.ListSeparator">
-        <item name="android:background">@android:drawable/list_section_header_holo_light</item>
+        <item name="android:background">@android:drawable/list_section_divider_holo_light</item>
     </style>
 
     <style name="Widget.Holo.Light.TextSelectHandle" parent="Widget.TextSelectHandle">
@@ -1598,7 +1606,7 @@
     <style name="Widget.Holo.Light.ImageWell" parent="Widget.ImageWell">
     </style>
 
-    <style name="Widget.Holo.Light.ListView" parent="Widget.ListView">
+    <style name="Widget.Holo.Light.ListView" parent="Widget.Holo.ListView">
     </style>
 
     <style name="Widget.Holo.Light.ListView.White">
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index b226aa0..7f6da2a 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -703,7 +703,7 @@
         <item name="listPreferredItemHeight">64dip</item>
         <!-- @hide -->
         <item name="searchResultListItemHeight">58dip</item>
-        <item name="listDivider">@drawable/divider_horizontal_holo_dark</item>
+        <item name="listDivider">@drawable/list_divider_holo_dark</item>
         <item name="listSeparatorTextViewStyle">@android:style/Widget.Holo.TextView.ListSeparator</item>   
 
         <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio_holo_dark</item>
@@ -713,7 +713,7 @@
 
         <item name="activatedBackgroundIndicator">@android:drawable/activated_background_holo_dark</item>
 
-        <item name="listDividerAlertDialog">@android:drawable/divider_horizontal_holo_dark</item>
+        <item name="listDividerAlertDialog">@android:drawable/list_divider_holo_dark</item>
 
         <item name="expandableListPreferredItemPaddingLeft">40dip</item>
         <item name="expandableListPreferredChildPaddingLeft">
@@ -936,7 +936,7 @@
         <item name="listPreferredItemHeight">64dip</item>
         <!-- @hide -->
         <item name="searchResultListItemHeight">58dip</item>
-        <item name="listDivider">@drawable/divider_horizontal_holo_light</item>
+        <item name="listDivider">@drawable/list_divider_holo_light</item>
         <item name="listSeparatorTextViewStyle">@android:style/Widget.Holo.Light.TextView.ListSeparator</item>   
 
         <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio_holo_light</item>
@@ -957,7 +957,7 @@
         <item name="expandableListPreferredChildIndicatorRight">
                 ?android:attr/expandableListPreferredItemIndicatorRight</item>
 
-        <item name="listDividerAlertDialog">@android:drawable/divider_horizontal_holo_light</item>
+        <item name="listDividerAlertDialog">@android:drawable/list_divider_holo_light</item>
 
         <!-- Gallery attributes -->
         <item name="galleryItemBackground">@android:drawable/gallery_item_background</item>
@@ -1143,6 +1143,7 @@
         <item name="android:windowAnimationStyle">@android:style/Animation.Holo.Dialog</item>
         <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
         <item name="android:windowActionBar">false</item>
+        <item name="android:windowActionModeOverlay">true</item>
 
         <item name="android:colorBackgroundCacheHint">@null</item>
         
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
index 82de509..cddf63d 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
@@ -35,7 +35,7 @@
     /**
      * Timeout for {@link BluetoothAdapter#disable()} in ms.
      */
-    private static final int DISABLE_TIMEOUT = 5000;
+    private static final int DISABLE_TIMEOUT = 20000;
 
     /**
      * Timeout for {@link BluetoothAdapter#enable()} in ms.
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
new file mode 100644
index 0000000..4e7a403
--- /dev/null
+++ b/data/sounds/AllAudio.mk
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+$(call inherit-product, frameworks/base/data/sounds/OriginalAudio.mk)
+$(call inherit-product, frameworks/base/data/sounds/AudioPackage2.mk)
+$(call inherit-product, frameworks/base/data/sounds/AudioPackage3.mk)
+$(call inherit-product, frameworks/base/data/sounds/AudioPackage4.mk)
diff --git a/docs/html/sdk/android-2.2.jd b/docs/html/sdk/android-2.2.jd
index 495fd80..063a10f 100644
--- a/docs/html/sdk/android-2.2.jd
+++ b/docs/html/sdk/android-2.2.jd
@@ -42,7 +42,7 @@
 
 <p>Android {@sdkPlatformVersion} is a {@sdkPlatformMajorMinor} platform release including user
 features, developer features, API changes, and bug
-fixes. For  information on developer features and API changes, see the 
+fixes. For  information on developer features and API changes, see the
 <a href="#api">Framework API</a> section.</p>
 
 <p>For developers, the Android {@sdkPlatformVersion} platform is available as a
@@ -62,7 +62,7 @@
 
 <h2 id="features">Platform Highlights</h2>
 
-<p>For a list of new user features and platform highlights, see the <a 
+<p>For a list of new user features and platform highlights, see the <a
 href="http://developer.android.com/sdk/android-2.2-highlights.html">Android
 2.2 Platform Highlights</a> document.</p>
 
@@ -316,8 +316,8 @@
 
 <ul>
   <li> New <code>android:backupAgent</code> attribute of the
-<code>&lt;application&gt;</code> element. Specifies the component name of the 
-BackupAgent subclass provided by the application to handle backup/restore 
+<code>&lt;application&gt;</code> element. Specifies the component name of the
+BackupAgent subclass provided by the application to handle backup/restore
 operations, if any.</li>
   <li> New <code>android:restoreAnyVersion</code> attribute of the
 <code>&lt;application&gt;</code> element. Boolean value that indicates whether
@@ -436,6 +436,11 @@
 <p>Localized UI strings match the locales that are accessible
 through Settings.</p>
 
+<p class="note"><strong>Note:</strong> Android supports more locales than are listed above. However,
+the entire collection of locale strings cannot fit on a single system image, so the above list is
+only what's included in the system image for the SDK. All of Android's supported locales are
+available in the <a href="http://source.android.com/">Android Open Source Project</a>.</p>
+
 <h2 id="skins">Emulator Skins</h2>
 
 <p>The downloadable platform includes a set of emulator skins that you can use
diff --git a/include/android_runtime/android_app_NativeActivity.h b/include/android_runtime/android_app_NativeActivity.h
index b49e02a..5dbec59 100644
--- a/include/android_runtime/android_app_NativeActivity.h
+++ b/include/android_runtime/android_app_NativeActivity.h
@@ -26,6 +26,9 @@
 
 namespace android {
 
+extern void android_NativeActivity_finish(
+        ANativeActivity* activity);
+
 extern void android_NativeActivity_setWindowFormat(
         ANativeActivity* activity, int32_t format);
 
diff --git a/include/camera/Camera.h b/include/camera/Camera.h
index 171a3b6..8d8edd6 100644
--- a/include/camera/Camera.h
+++ b/include/camera/Camera.h
@@ -81,6 +81,18 @@
 enum {
     CAMERA_CMD_START_SMOOTH_ZOOM     = 1,
     CAMERA_CMD_STOP_SMOOTH_ZOOM      = 2,
+    // Set the clockwise rotation of preview display (setPreviewDisplay) in
+    // degrees. This affects the preview frames and the picture displayed after
+    // snapshot. This method is useful for portrait mode applications. Note that
+    // preview display of front-facing cameras is flipped horizontally before
+    // the rotation, that is, the image is reflected along the central vertical
+    // axis of the camera sensor. So the users can see themselves as looking
+    // into a mirror.
+    //
+    // This does not affect the order of byte array of CAMERA_MSG_PREVIEW_FRAME,
+    // CAMERA_MSG_VIDEO_FRAME, CAMERA_MSG_POSTVIEW_FRAME, CAMERA_MSG_RAW_IMAGE,
+    // or CAMERA_MSG_COMPRESSED_IMAGE. This is not allowed to be set during
+    // preview.
     CAMERA_CMD_SET_DISPLAY_ORIENTATION = 3,
 
     // cmdType to disable/enable shutter sound.
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index 1f8ce71..fa775e7 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -79,6 +79,9 @@
             node_id node, OMX_INDEXTYPE index,
             const void *params, size_t size) = 0;
 
+    virtual status_t storeMetaDataInBuffers(
+            node_id node, OMX_U32 port_index, OMX_BOOL enable) = 0;
+
     virtual status_t enableGraphicBuffers(
             node_id node, OMX_U32 port_index, OMX_BOOL enable) = 0;
 
diff --git a/include/media/stagefright/HardwareAPI.h b/include/media/stagefright/HardwareAPI.h
index 4ded5e8..b009e1b 100644
--- a/include/media/stagefright/HardwareAPI.h
+++ b/include/media/stagefright/HardwareAPI.h
@@ -49,6 +49,32 @@
     OMX_BOOL enable;
 };
 
+// A pointer to this struct is passed to OMX_SetParameter() when the extension
+// index "OMX.google.android.index.storeMetaDataInBuffers"
+// is given.
+//
+// When meta data is stored in the video buffers passed between OMX clients
+// and OMX components, interpretation of the buffer data is up to the
+// buffer receiver, and the data may or may not be the actual video data, but
+// some information helpful for the receiver to locate the actual data.
+// The buffer receiver thus needs to know how to interpret what is stored
+// in these buffers, with mechanisms pre-determined externally. How to
+// interpret the meta data is outside of the scope of this method.
+//
+// Currently, this is specifically used to pass meta data from video source
+// (camera component, for instance) to video encoder to avoid memcpying of
+// input video frame data. To do this, bStoreMetaDta is set to OMX_TRUE.
+// If bStoreMetaData is set to false, real YUV frame data will be stored
+// in the buffers. In addition, if no OMX_SetParameter() call is made
+// with the corresponding extension index, real YUV data is stored
+// in the buffers.
+struct StoreMetaDataInBuffersParams {
+    OMX_U32 nSize;
+    OMX_VERSIONTYPE nVersion;
+    OMX_U32 nPortIndex;
+    OMX_BOOL bStoreMetaData;
+};
+
 // Color formats in the range [OMX_COLOR_FormatAndroidPrivateStart,
 // OMX_COLOR_FormatAndroidPrivateEnd) will be converted to a gralloc pixel
 // format when used to allocate Android native buffers via gralloc.  The
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index cff38b2..bb469e5 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -152,6 +152,7 @@
     bool exceedsFileSizeLimit();
     bool use32BitFileOffset() const;
     bool exceedsFileDurationLimit();
+    bool isFileStreamable() const;
     void trackProgressStatus(const Track* track, int64_t timeUs, status_t err = OK);
 
     MPEG4Writer(const MPEG4Writer &);
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
index 2d4bf8b..f3a2dd2 100644
--- a/include/ui/InputReader.h
+++ b/include/ui/InputReader.h
@@ -170,11 +170,10 @@
  * and parameters maintained by the input reader.
  */
 class InputReaderContext {
-protected:
+public:
     InputReaderContext() { }
     virtual ~InputReaderContext() { }
 
-public:
     virtual void updateGlobalMetaState() = 0;
     virtual int32_t getGlobalMetaState() = 0;
 
@@ -193,7 +192,7 @@
  *     the input reader, the input reader never calls into other components while holding
  *     an exclusive internal lock whenever re-entrance can happen.
  */
-class InputReader : public InputReaderInterface, private InputReaderContext {
+class InputReader : public InputReaderInterface, protected InputReaderContext {
 public:
     InputReader(const sp<EventHubInterface>& eventHub,
             const sp<InputReaderPolicyInterface>& policy,
@@ -219,6 +218,11 @@
     virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
             size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
 
+protected:
+    // These methods are protected virtual so they can be overridden and instrumented
+    // by test cases.
+    virtual InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes);
+
 private:
     sp<EventHubInterface> mEventHub;
     sp<InputReaderPolicyInterface> mPolicy;
@@ -244,12 +248,11 @@
 
     void addDevice(int32_t deviceId);
     void removeDevice(int32_t deviceId);
-    InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes);
     void configureExcludedDevices();
 
     void consumeEvent(const RawEvent* rawEvent);
 
-    void handleConfigurationChanged();
+    void handleConfigurationChanged(nsecs_t when);
 
     // state management for all devices
     Mutex mStateLock;
@@ -546,6 +549,21 @@
         int32_t toolMajor;
         int32_t toolMinor;
         int32_t orientation;
+
+        inline bool operator== (const PointerData& other) const {
+            return id == other.id
+                    && x == other.x
+                    && y == other.y
+                    && pressure == other.pressure
+                    && touchMajor == other.touchMajor
+                    && touchMinor == other.touchMinor
+                    && toolMajor == other.toolMajor
+                    && toolMinor == other.toolMinor
+                    && orientation == other.orientation;
+        }
+        inline bool operator!= (const PointerData& other) const {
+            return !(*this == other);
+        }
     };
 
     // Raw data for a collection of pointers including a pointer id mapping table.
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 5bf0ccc..d661f7b 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -8,6 +8,7 @@
 		utils/SortedListImpl.cpp \
 		FontRenderer.cpp \
 		GammaFontRenderer.cpp \
+		Caches.cpp \
 		DisplayListRenderer.cpp \
 		FboCache.cpp \
 		GradientCache.cpp \
@@ -20,6 +21,7 @@
 		PathCache.cpp \
 		Program.cpp \
 		ProgramCache.cpp \
+		ResourceCache.cpp \
 		SkiaColorFilter.cpp \
 		SkiaShader.cpp \
 		TextureCache.cpp \
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
new file mode 100644
index 0000000..a4def0b
--- /dev/null
+++ b/libs/hwui/Caches.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "Caches.h"
+
+namespace android {
+
+#ifdef USE_OPENGL_RENDERER
+using namespace uirenderer;
+ANDROID_SINGLETON_STATIC_INSTANCE(Caches);
+#endif
+
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+Caches::Caches(): Singleton<Caches>(), blend(false), lastSrcMode(GL_ZERO),
+        lastDstMode(GL_ZERO), currentProgram(NULL) {
+    GLint maxTextureUnits;
+    glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
+    if (maxTextureUnits < REQUIRED_TEXTURE_UNITS_COUNT) {
+        LOGW("At least %d texture units are required!", REQUIRED_TEXTURE_UNITS_COUNT);
+    }
+
+    glGenBuffers(1, &meshBuffer);
+    glBindBuffer(GL_ARRAY_BUFFER, meshBuffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(gMeshVertices), gMeshVertices, GL_STATIC_DRAW);
+
+    currentBuffer = meshBuffer;
+}
+
+/**
+ * Binds the VBO used to render simple textured quads.
+ */
+void Caches::bindMeshBuffer() {
+    bindMeshBuffer(meshBuffer);
+}
+
+/**
+ * Binds the specified VBO.
+ */
+void Caches::bindMeshBuffer(const GLuint buffer) {
+    if (currentBuffer != buffer) {
+        glBindBuffer(GL_ARRAY_BUFFER, buffer);
+        currentBuffer = buffer;
+    }
+}
+
+/**
+ * Unbinds the VBO used to render simple textured quads.
+ */
+void Caches::unbindMeshBuffer() {
+    if (currentBuffer) {
+        glBindBuffer(GL_ARRAY_BUFFER, 0);
+        currentBuffer = 0;
+    }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index e6e494d..79644a5 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -34,6 +34,7 @@
 #include "TextDropShadowCache.h"
 #include "FboCache.h"
 #include "Line.h"
+#include "ResourceCache.h"
 
 namespace android {
 namespace uirenderer {
@@ -74,52 +75,16 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 class Caches: public Singleton<Caches> {
-    Caches(): Singleton<Caches>(), blend(false), lastSrcMode(GL_ZERO),
-            lastDstMode(GL_ZERO), currentProgram(NULL) {
-        GLint maxTextureUnits;
-        glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
-        if (maxTextureUnits < REQUIRED_TEXTURE_UNITS_COUNT) {
-            LOGW("At least %d texture units are required!", REQUIRED_TEXTURE_UNITS_COUNT);
-        }
-
-        glGenBuffers(1, &meshBuffer);
-        glBindBuffer(GL_ARRAY_BUFFER, meshBuffer);
-        glBufferData(GL_ARRAY_BUFFER, sizeof(gMeshVertices), gMeshVertices, GL_STATIC_DRAW);
-
-        currentBuffer = meshBuffer;
-    }
+    Caches();
 
     friend class Singleton<Caches>;
 
     CacheLogger logger;
 
 public:
-    /**
-     * Binds the VBO used to render simple textured quads.
-     */
-    inline void bindMeshBuffer() {
-        bindMeshBuffer(meshBuffer);
-    }
-
-    /**
-     * Binds the specified VBO.
-     */
-    inline void bindMeshBuffer(const GLuint buffer) {
-        if (currentBuffer != buffer) {
-            glBindBuffer(GL_ARRAY_BUFFER, buffer);
-            currentBuffer = buffer;
-        }
-    }
-
-    /**
-     * Unbinds the VBO used to render simple textured quads.
-     */
-    inline void unbindMeshBuffer() {
-        if (currentBuffer) {
-            glBindBuffer(GL_ARRAY_BUFFER, 0);
-            currentBuffer = 0;
-        }
-    }
+    void bindMeshBuffer();
+    void bindMeshBuffer(const GLuint buffer);
+    void unbindMeshBuffer();
 
     bool blend;
     GLenum lastSrcMode;
@@ -138,17 +103,13 @@
     TextDropShadowCache dropShadowCache;
     FboCache fboCache;
     GammaFontRenderer fontRenderer;
+    ResourceCache resourceCache;
 
     Line line;
 }; // class Caches
 
 }; // namespace uirenderer
 
-#ifdef USE_OPENGL_RENDERER
-using namespace uirenderer;
-ANDROID_SINGLETON_STATIC_INSTANCE(Caches);
-#endif
-
 }; // namespace android
 
 #endif // ANDROID_UI_CACHES_H
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index b3517c4..a43f164 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -100,39 +100,31 @@
     mTFPlayback.reset(&recorder.mTFRecorder);
     mTFPlayback.setupBuffer(mReader);
 
-    const SkTDArray<const SkFlatBitmap*>& bitmaps = recorder.getBitmaps();
-    mBitmapCount = bitmaps.count();
-    if (mBitmapCount > 0) {
-        mBitmaps = new SkBitmap[mBitmapCount];
-        for (const SkFlatBitmap** flatBitmapPtr = bitmaps.begin();
-                flatBitmapPtr != bitmaps.end(); flatBitmapPtr++) {
-            const SkFlatBitmap* flatBitmap = *flatBitmapPtr;
-            int index = flatBitmap->index() - 1;
-            flatBitmap->unflatten(&mBitmaps[index], &mRCPlayback);
-        }
-    }
+    Caches& caches = Caches::getInstance();
 
-    const SkTDArray<const SkFlatMatrix*>& matrices = recorder.getMatrices();
-    mMatrixCount = matrices.count();
-    if (mMatrixCount > 0) {
-        mMatrices = new SkMatrix[mMatrixCount];
-        for (const SkFlatMatrix** matrixPtr = matrices.begin();
-                matrixPtr != matrices.end(); matrixPtr++) {
-            const SkFlatMatrix* flatMatrix = *matrixPtr;
-            flatMatrix->unflatten(&mMatrices[flatMatrix->index() - 1]);
-        }
+    const Vector<SkBitmap*> &bitmapResources = recorder.getBitmapResources();
+    for (size_t i = 0; i < bitmapResources.size(); i++) {
+        SkBitmap* resource = bitmapResources.itemAt(i);
+        mBitmapResources.add(resource);
+        caches.resourceCache.incrementRefcount(resource);
     }
-
-    const SkTDArray<const SkFlatPaint*>& paints = recorder.getPaints();
-    mPaintCount = paints.count();
-    if (mPaintCount > 0) {
-        mPaints = new SkPaint[mPaintCount];
-        for (const SkFlatPaint** flatPaintPtr = paints.begin();
-                flatPaintPtr != paints.end(); flatPaintPtr++) {
-            const SkFlatPaint* flatPaint = *flatPaintPtr;
-            int index = flatPaint->index() - 1;
-            flatPaint->unflatten(&mPaints[index], &mRCPlayback, &mTFPlayback);
-        }
+    const Vector<SkMatrix*> &matrixResources = recorder.getMatrixResources();
+    for (size_t i = 0; i < matrixResources.size(); i++) {
+        SkMatrix* resource = matrixResources.itemAt(i);
+        mMatrixResources.add(resource);
+        caches.resourceCache.incrementRefcount(resource);
+    }
+    const Vector<SkPaint*> &paintResources = recorder.getPaintResources();
+    for (size_t i = 0; i < paintResources.size(); i++) {
+        SkPaint* resource = paintResources.itemAt(i);
+        mPaintResources.add(resource);
+        caches.resourceCache.incrementRefcount(resource);
+    }
+    const Vector<SkiaShader*> &shaderResources = recorder.getShaderResources();
+    for (size_t i = 0; i < shaderResources.size(); i++) {
+        SkiaShader* resource = shaderResources.itemAt(i);
+        mShaderResources.add(resource);
+        caches.resourceCache.incrementRefcount(resource);
     }
 
     mPathHeap = recorder.mPathHeap;
@@ -143,23 +135,32 @@
     sk_free((void*) mReader.base());
 
     Caches& caches = Caches::getInstance();
-    for (int i = 0; i < mBitmapCount; i++) {
-        caches.textureCache.remove(&mBitmaps[i]);
+
+    for (size_t i = 0; i < mBitmapResources.size(); i++) {
+        SkBitmap* resource = mBitmapResources.itemAt(i);
+        caches.resourceCache.decrementRefcount(resource);
     }
-
-    delete[] mBitmaps;
-    delete[] mMatrices;
-    delete[] mPaints;
-
+    mBitmapResources.clear();
+    for (size_t i = 0; i < mMatrixResources.size(); i++) {
+        SkMatrix* resource = mMatrixResources.itemAt(i);
+        caches.resourceCache.decrementRefcount(resource);
+    }
+    mMatrixResources.clear();
+    for (size_t i = 0; i < mPaintResources.size(); i++) {
+        SkPaint* resource = mPaintResources.itemAt(i);
+        caches.resourceCache.decrementRefcount(resource);
+    }
+    mPaintResources.clear();
+    for (size_t i = 0; i < mShaderResources.size(); i++) {
+        SkiaShader* resource = mShaderResources.itemAt(i);
+        caches.resourceCache.decrementRefcount(resource);
+    }
+    mShaderResources.clear();
     mPathHeap->safeUnref();
 }
 
 void DisplayList::init() {
-    mBitmaps = NULL;
-    mMatrices = NULL;
-    mPaints = NULL;
     mPathHeap = NULL;
-    mBitmapCount = mMatrixCount = mPaintCount = 0;
 }
 
 void DisplayList::replay(OpenGLRenderer& renderer) {
@@ -280,7 +281,7 @@
             }
             break;
             case SetupShader: {
-                // TODO: Implement
+                renderer.setupShader(getShader());
             }
             break;
             case ResetColorFilter: {
@@ -309,7 +310,6 @@
 
 DisplayListRenderer::DisplayListRenderer():
         mHeap(HEAP_BLOCK_SIZE), mWriter(MIN_WRITER_SIZE) {
-    mBitmapIndex = mMatrixIndex = mPaintIndex = 1;
     mPathHeap = NULL;
 }
 
@@ -323,15 +323,33 @@
         mPathHeap = NULL;
     }
 
-    mBitmaps.reset();
-    mMatrices.reset();
-    mPaints.reset();
-
     mWriter.reset();
     mHeap.reset();
 
     mRCRecorder.reset();
     mTFRecorder.reset();
+
+    Caches& caches = Caches::getInstance();
+    for (size_t i = 0; i < mBitmapResources.size(); i++) {
+        SkBitmap* resource = mBitmapResources.itemAt(i);
+        caches.resourceCache.decrementRefcount(resource);
+    }
+    mBitmapResources.clear();
+    for (size_t i = 0; i < mMatrixResources.size(); i++) {
+        SkMatrix* resource = mMatrixResources.itemAt(i);
+        caches.resourceCache.decrementRefcount(resource);
+    }
+    mMatrixResources.clear();
+    for (size_t i = 0; i < mPaintResources.size(); i++) {
+        SkPaint* resource = mPaintResources.itemAt(i);
+        caches.resourceCache.decrementRefcount(resource);
+    }
+    mPaintResources.clear();
+    for (size_t i = 0; i < mShaderResources.size(); i++) {
+        SkiaShader* resource = mShaderResources.itemAt(i);
+        caches.resourceCache.decrementRefcount(resource);
+    }
+    mShaderResources.clear();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -380,7 +398,7 @@
 }
 
 int DisplayListRenderer::saveLayer(float left, float top, float right, float bottom,
-        const SkPaint* p, int flags) {
+        SkPaint* p, int flags) {
     addOp(DisplayList::SaveLayer);
     addBounds(left, top, right, bottom);
     addPaint(p);
@@ -427,15 +445,15 @@
 }
 
 void DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top,
-        const SkPaint* paint) {
+        SkPaint* paint) {
     addOp(DisplayList::DrawBitmap);
     addBitmap(bitmap);
     addPoint(left, top);
     addPaint(paint);
 }
 
-void DisplayListRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix,
-        const SkPaint* paint) {
+void DisplayListRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix,
+        SkPaint* paint) {
     addOp(DisplayList::DrawBitmapMatrix);
     addBitmap(bitmap);
     addMatrix(matrix);
@@ -444,7 +462,7 @@
 
 void DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
         float srcRight, float srcBottom, float dstLeft, float dstTop,
-        float dstRight, float dstBottom, const SkPaint* paint) {
+        float dstRight, float dstBottom, SkPaint* paint) {
     addOp(DisplayList::DrawBitmapRect);
     addBitmap(bitmap);
     addBounds(srcLeft, srcTop, srcRight, srcBottom);
@@ -454,7 +472,7 @@
 
 void DisplayListRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
         const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
-        float left, float top, float right, float bottom, const SkPaint* paint) {
+        float left, float top, float right, float bottom, SkPaint* paint) {
     addOp(DisplayList::DrawPatch);
     addBitmap(bitmap);
     addInts(xDivs, width);
@@ -471,7 +489,7 @@
 }
 
 void DisplayListRenderer::drawRect(float left, float top, float right, float bottom,
-        const SkPaint* paint) {
+        SkPaint* paint) {
     addOp(DisplayList::DrawRect);
     addBounds(left, top, right, bottom);
     addPaint(paint);
@@ -483,7 +501,7 @@
     addPaint(paint);
 }
 
-void DisplayListRenderer::drawLines(float* points, int count, const SkPaint* paint) {
+void DisplayListRenderer::drawLines(float* points, int count, SkPaint* paint) {
     addOp(DisplayList::DrawLines);
     addFloats(points, count);
     addPaint(paint);
@@ -504,8 +522,8 @@
 }
 
 void DisplayListRenderer::setupShader(SkiaShader* shader) {
-    // TODO: Implement
-    OpenGLRenderer::setupShader(shader);
+    addOp(DisplayList::SetupShader);
+    addShader(shader);
 }
 
 void DisplayListRenderer::resetColorFilter() {
@@ -531,58 +549,5 @@
     OpenGLRenderer::setupShadow(radius, dx, dy, color);
 }
 
-///////////////////////////////////////////////////////////////////////////////
-// Recording management
-///////////////////////////////////////////////////////////////////////////////
-
-int DisplayListRenderer::find(SkTDArray<const SkFlatPaint*>& paints, const SkPaint* paint) {
-    if (paint == NULL) {
-        return 0;
-    }
-
-    SkFlatPaint* flat = SkFlatPaint::Flatten(&mHeap, *paint, mPaintIndex,
-            &mRCRecorder, &mTFRecorder);
-    int index = SkTSearch<SkFlatData>((const SkFlatData**) paints.begin(),
-            paints.count(), (SkFlatData*) flat, sizeof(flat), &SkFlatData::Compare);
-    if (index >= 0) {
-        (void) mHeap.unalloc(flat);
-        return paints[index]->index();
-    }
-
-    index = ~index;
-    *paints.insert(index) = flat;
-    return mPaintIndex++;
-}
-
-int DisplayListRenderer::find(SkTDArray<const SkFlatMatrix*>& matrices, const SkMatrix* matrix) {
-    if (matrix == NULL) {
-        return 0;
-    }
-
-    SkFlatMatrix* flat = SkFlatMatrix::Flatten(&mHeap, *matrix, mMatrixIndex);
-    int index = SkTSearch<SkFlatData>((const SkFlatData**) matrices.begin(),
-            matrices.count(), (SkFlatData*) flat, sizeof(flat), &SkFlatData::Compare);
-    if (index >= 0) {
-        (void) mHeap.unalloc(flat);
-        return matrices[index]->index();
-    }
-    index = ~index;
-    *matrices.insert(index) = flat;
-    return mMatrixIndex++;
-}
-
-int DisplayListRenderer::find(SkTDArray<const SkFlatBitmap*>& bitmaps, const SkBitmap& bitmap) {
-    SkFlatBitmap* flat = SkFlatBitmap::Flatten(&mHeap, bitmap, mBitmapIndex, &mRCRecorder);
-    int index = SkTSearch<SkFlatData>((const SkFlatData**) bitmaps.begin(),
-            bitmaps.count(), (SkFlatData*) flat, sizeof(flat), &SkFlatData::Compare);
-    if (index >= 0) {
-        (void) mHeap.unalloc(flat);
-        return bitmaps[index]->index();
-    }
-    index = ~index;
-    *bitmaps.insert(index) = flat;
-    return mBitmapIndex++;
-}
-
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 9e6d5b1..c8cd801 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -128,8 +128,11 @@
     };
 
     SkBitmap* getBitmap() {
-        int index = getInt();
-        return &mBitmaps[index - 1];
+        return (SkBitmap*) getInt();
+    }
+
+    SkiaShader* getShader() {
+        return (SkiaShader*) getInt();
     }
 
     inline int getIndex() {
@@ -141,11 +144,7 @@
     }
 
     SkMatrix* getMatrix() {
-        int index = getInt();
-        if (index == 0) {
-            return NULL;
-        }
-        return &mMatrices[index - 1];
+        return (SkMatrix*) getInt();
     }
 
     SkPath* getPath() {
@@ -153,11 +152,7 @@
     }
 
     SkPaint* getPaint() {
-        int index = getInt();
-        if (index == 0) {
-            return NULL;
-        }
-        return &mPaints[index - 1];
+        return (SkPaint*) getInt();
     }
 
     inline float getFloat() {
@@ -186,14 +181,10 @@
 
     PathHeap* mPathHeap;
 
-    SkBitmap* mBitmaps;
-    int mBitmapCount;
-
-    SkMatrix* mMatrices;
-    int mMatrixCount;
-
-    SkPaint* mPaints;
-    int mPaintCount;
+    Vector<SkBitmap*> mBitmapResources;
+    Vector<SkMatrix*> mMatrixResources;
+    Vector<SkPaint*> mPaintResources;
+    Vector<SkiaShader*> mShaderResources;
 
     mutable SkFlattenableReadBuffer mReader;
 
@@ -224,7 +215,7 @@
     void restoreToCount(int saveCount);
 
     int saveLayer(float left, float top, float right, float bottom,
-            const SkPaint* p, int flags);
+            SkPaint* p, int flags);
 
     void translate(float dx, float dy);
     void rotate(float degrees);
@@ -235,18 +226,18 @@
 
     bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
 
-    void drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint);
-    void drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint);
+    void drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
+    void drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
     void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
             float srcRight, float srcBottom, float dstLeft, float dstTop,
-            float dstRight, float dstBottom, const SkPaint* paint);
+            float dstRight, float dstBottom, SkPaint* paint);
     void drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
             const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
-            float left, float top, float right, float bottom, const SkPaint* paint);
+            float left, float top, float right, float bottom, SkPaint* paint);
     void drawColor(int color, SkXfermode::Mode mode);
-    void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
+    void drawRect(float left, float top, float right, float bottom, SkPaint* paint);
     void drawPath(SkPath* path, SkPaint* paint);
-    void drawLines(float* points, int count, const SkPaint* paint);
+    void drawLines(float* points, int count, SkPaint* paint);
     void drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint);
 
     void resetShader();
@@ -268,16 +259,20 @@
         return mWriter;
     }
 
-    const SkTDArray<const SkFlatBitmap*>& getBitmaps() const {
-        return mBitmaps;
+    const Vector<SkBitmap*>& getBitmapResources() const {
+        return mBitmapResources;
     }
 
-    const SkTDArray<const SkFlatMatrix*>& getMatrices() const {
-        return mMatrices;
+    const Vector<SkMatrix*>& getMatrixResources() const {
+        return mMatrixResources;
     }
 
-    const SkTDArray<const SkFlatPaint*>& getPaints() const {
-        return mPaints;
+    const Vector<SkPaint*>& getPaintResources() const {
+        return mPaintResources;
+    }
+
+    const Vector<SkiaShader*>& getShaderResources() const {
+        return mShaderResources;
     }
 
 private:
@@ -338,34 +333,40 @@
         addInt(mPathHeap->append(*path));
     }
 
-    int find(SkTDArray<const SkFlatPaint*>& paints, const SkPaint* paint);
-
-    inline void addPaint(const SkPaint* paint) {
-        addInt(find(mPaints, paint));
+    inline void addPaint(SkPaint* paint) {
+        addInt((int)paint);
+        mPaintResources.add(paint);
+        Caches& caches = Caches::getInstance();
+        caches.resourceCache.incrementRefcount(paint);
     }
 
-    int find(SkTDArray<const SkFlatMatrix*>& matrices, const SkMatrix* matrix);
-
-    inline void addMatrix(const SkMatrix* matrix) {
-        addInt(find(mMatrices, matrix));
+    inline void addMatrix(SkMatrix* matrix) {
+        addInt((int)matrix);
+        mMatrixResources.add(matrix);
+        Caches& caches = Caches::getInstance();
+        caches.resourceCache.incrementRefcount(matrix);
     }
 
-    int find(SkTDArray<const SkFlatBitmap*>& bitmaps, const SkBitmap& bitmap);
+    inline void addBitmap(SkBitmap* bitmap) {
+        addInt((int)bitmap);
+        mBitmapResources.add(bitmap);
+        Caches& caches = Caches::getInstance();
+        caches.resourceCache.incrementRefcount(bitmap);
+    }
 
-    inline void addBitmap(const SkBitmap* bitmap) {
-        addInt(find(mBitmaps, *bitmap));
+    inline void addShader(SkiaShader* shader) {
+        addInt((int)shader);
+        mShaderResources.add(shader);
+        Caches& caches = Caches::getInstance();
+        caches.resourceCache.incrementRefcount(shader);
     }
 
     SkChunkAlloc mHeap;
 
-    int mBitmapIndex;
-    SkTDArray<const SkFlatBitmap*> mBitmaps;
-
-    int mMatrixIndex;
-    SkTDArray<const SkFlatMatrix*> mMatrices;
-
-    int mPaintIndex;
-    SkTDArray<const SkFlatPaint*> mPaints;
+    Vector<SkBitmap*> mBitmapResources;
+    Vector<SkMatrix*> mMatrixResources;
+    Vector<SkPaint*> mPaintResources;
+    Vector<SkiaShader*> mShaderResources;
 
     PathHeap* mPathHeap;
     SkWriter32 mWriter;
diff --git a/libs/hwui/OpenGLDebugRenderer.cpp b/libs/hwui/OpenGLDebugRenderer.cpp
index d492e23..fe75ca2 100644
--- a/libs/hwui/OpenGLDebugRenderer.cpp
+++ b/libs/hwui/OpenGLDebugRenderer.cpp
@@ -42,21 +42,21 @@
 }
 
 int OpenGLDebugRenderer::saveLayer(float left, float top, float right, float bottom,
-        const SkPaint* p, int flags) {
+        SkPaint* p, int flags) {
     mPrimitivesCount++;
     StopWatch w("saveLayer");
     return OpenGLRenderer::saveLayer(left, top, right, bottom, p, flags);
 }
 
 void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, float left, float top,
-        const SkPaint* paint) {
+        SkPaint* paint) {
     mPrimitivesCount++;
     StopWatch w("drawBitmap");
     OpenGLRenderer::drawBitmap(bitmap, left, top, paint);
 }
 
-void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix,
-        const SkPaint* paint) {
+void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix,
+        SkPaint* paint) {
     mPrimitivesCount++;
     StopWatch w("drawBitmapMatrix");
     OpenGLRenderer::drawBitmap(bitmap, matrix, paint);
@@ -64,7 +64,7 @@
 
 void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
         float srcRight, float srcBottom, float dstLeft, float dstTop,
-        float dstRight, float dstBottom, const SkPaint* paint) {
+        float dstRight, float dstBottom, SkPaint* paint) {
     mPrimitivesCount++;
     StopWatch w("drawBitmapRect");
     OpenGLRenderer::drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom,
@@ -73,7 +73,7 @@
 
 void OpenGLDebugRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
         const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
-        float left, float top, float right, float bottom, const SkPaint* paint) {
+        float left, float top, float right, float bottom, SkPaint* paint) {
     mPrimitivesCount++;
     StopWatch w("drawPatch");
     OpenGLRenderer::drawPatch(bitmap, xDivs, yDivs, colors, width, height, numColors,
@@ -87,7 +87,7 @@
 }
 
 void OpenGLDebugRenderer::drawRect(float left, float top, float right, float bottom,
-        const SkPaint* paint) {
+        SkPaint* paint) {
     mPrimitivesCount++;
     StopWatch w("drawRect");
     OpenGLRenderer::drawRect(left, top, right, bottom, paint);
@@ -99,7 +99,7 @@
     OpenGLRenderer::drawPath(path, paint);
 }
 
-void OpenGLDebugRenderer::drawLines(float* points, int count, const SkPaint* paint) {
+void OpenGLDebugRenderer::drawLines(float* points, int count, SkPaint* paint) {
     mPrimitivesCount++;
     StopWatch w("drawLines");
     OpenGLRenderer::drawLines(points, count, paint);
diff --git a/libs/hwui/OpenGLDebugRenderer.h b/libs/hwui/OpenGLDebugRenderer.h
index 4997ef3..ce6a4aa 100644
--- a/libs/hwui/OpenGLDebugRenderer.h
+++ b/libs/hwui/OpenGLDebugRenderer.h
@@ -38,20 +38,20 @@
     void finish();
 
     int saveLayer(float left, float top, float right, float bottom,
-            const SkPaint* p, int flags);
+            SkPaint* p, int flags);
 
-    void drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint);
-    void drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint);
+    void drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
+    void drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
     void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
             float srcRight, float srcBottom, float dstLeft, float dstTop,
-            float dstRight, float dstBottom, const SkPaint* paint);
+            float dstRight, float dstBottom, SkPaint* paint);
     void drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
             const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
-            float left, float top, float right, float bottom, const SkPaint* paint);
+            float left, float top, float right, float bottom, SkPaint* paint);
     void drawColor(int color, SkXfermode::Mode mode);
-    void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
+    void drawRect(float left, float top, float right, float bottom, SkPaint* paint);
     void drawPath(SkPath* path, SkPaint* paint);
-    void drawLines(float* points, int count, const SkPaint* paint);
+    void drawLines(float* points, int count, SkPaint* paint);
     void drawText(const char* text, int bytesCount, int count, float x, float y,
             SkPaint* paint);
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 633d778..7495a06 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -252,7 +252,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
-        const SkPaint* p, int flags) {
+        SkPaint* p, int flags) {
     const GLuint previousFbo = mSnapshot->fbo;
     const int count = saveSnapshot(flags);
 
@@ -353,11 +353,22 @@
 
     // Window coordinates of the layer
     Rect bounds(left, top, right, bottom);
-    if (!fboLayer) {
+    if (fboLayer) {
+        // Clear the previous layer regions before we change the viewport
+        clearLayerRegions();
+    } else {
         mSnapshot->transform->mapRect(bounds);
+
         // Layers only make sense if they are in the framebuffer's bounds
-        bounds.intersect(*mSnapshot->clipRect);
+        bounds.intersect(*snapshot->clipRect);
+
+        // We cannot work with sub-pixels in this case
         bounds.snapToPixelBoundaries();
+
+        // When the layer is not an FBO, we may use glCopyTexImage so we
+        // need to make sure the layer does not extend outside the bounds
+        // of the framebuffer
+        bounds.intersect(snapshot->previous->viewport);
     }
 
     if (bounds.isEmpty() || bounds.getWidth() > mMaxTextureSize ||
@@ -446,14 +457,14 @@
         // Copy the framebuffer into the layer
         glBindTexture(GL_TEXTURE_2D, layer->texture);
 
-         if (layer->empty) {
-             glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
-                     layer->width, layer->height, 0);
-             layer->empty = false;
-         } else {
-             glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
-                     bounds.getWidth(), bounds.getHeight());
-          }
+        if (layer->empty) {
+            glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left,
+                    snapshot->height - bounds.bottom, layer->width, layer->height, 0);
+            layer->empty = false;
+        } else {
+            glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left,
+                    snapshot->height - bounds.bottom, bounds.getWidth(), bounds.getHeight());
+        }
 
         // Enqueue the buffer coordinates to clear the corresponding region later
         mLayers.push(new Rect(bounds));
@@ -479,7 +490,8 @@
     }
 
     // Restore the clip from the previous snapshot
-    const Rect& clip = *previous->clipRect;
+    Rect& clip(*previous->clipRect);
+    clip.snapToPixelBoundaries();
     glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
 
     Layer* layer = current->layer;
@@ -623,7 +635,7 @@
 // Drawing
 ///////////////////////////////////////////////////////////////////////////////
 
-void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint) {
+void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) {
     const float right = left + bitmap->width();
     const float bottom = top + bitmap->height();
 
@@ -639,7 +651,7 @@
     drawTextureRect(left, top, right, bottom, texture, paint);
 }
 
-void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint) {
+void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint) {
     Rect r(0.0f, 0.0f, bitmap->width(), bitmap->height());
     const mat4 transform(*matrix);
     transform.mapRect(r);
@@ -659,7 +671,7 @@
 void OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
          float srcLeft, float srcTop, float srcRight, float srcBottom,
          float dstLeft, float dstTop, float dstRight, float dstBottom,
-         const SkPaint* paint) {
+         SkPaint* paint) {
     if (quickReject(dstLeft, dstTop, dstRight, dstBottom)) {
         return;
     }
@@ -693,7 +705,7 @@
 
 void OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
         const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
-        float left, float top, float right, float bottom, const SkPaint* paint) {
+        float left, float top, float right, float bottom, SkPaint* paint) {
     if (quickReject(left, top, right, bottom)) {
         return;
     }
@@ -719,7 +731,7 @@
     }
 }
 
-void OpenGLRenderer::drawLines(float* points, int count, const SkPaint* paint) {
+void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
     if (mSnapshot->invisible) return;
 
     int alpha;
@@ -787,11 +799,12 @@
 }
 
 void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
-    const Rect& clip = *mSnapshot->clipRect;
+    Rect& clip(*mSnapshot->clipRect);
+    clip.snapToPixelBoundaries();
     drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode, true);
 }
 
-void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, const SkPaint* p) {
+void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, SkPaint* p) {
     if (quickReject(left, top, right, bottom)) {
         return;
     }
@@ -855,6 +868,7 @@
             paint->getTextSize());
 
     Rect clipRect(*mSnapshot->clipRect);
+    clipRect.snapToPixelBoundaries();
     glScissor(clipRect.left, mSnapshot->height - clipRect.bottom,
             clipRect.getWidth(), clipRect.getHeight());
 
@@ -1206,7 +1220,7 @@
 }
 
 void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
-        const Texture* texture, const SkPaint* paint) {
+        const Texture* texture, SkPaint* paint) {
     int alpha;
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
@@ -1334,7 +1348,7 @@
     TextureVertex::setUV(v++, u2, v2);
 }
 
-void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
+void OpenGLRenderer::getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
     if (paint) {
         if (!mExtensions.hasFramebufferFetch()) {
             const bool isMode = SkXfermode::IsMode(paint->getXfermode(), mode);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 1d8a3d9..b7615fe 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -79,7 +79,7 @@
     virtual void restoreToCount(int saveCount);
 
     virtual int saveLayer(float left, float top, float right, float bottom,
-            const SkPaint* p, int flags);
+            SkPaint* p, int flags);
     virtual int saveLayerAlpha(float left, float top, float right, float bottom,
             int alpha, int flags);
 
@@ -96,18 +96,18 @@
     bool quickReject(float left, float top, float right, float bottom);
     virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
 
-    virtual void drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint);
-    virtual void drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint);
+    virtual void drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
+    virtual void drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
     virtual void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
             float srcRight, float srcBottom, float dstLeft, float dstTop,
-            float dstRight, float dstBottom, const SkPaint* paint);
+            float dstRight, float dstBottom, SkPaint* paint);
     virtual void drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs,
             const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors,
-            float left, float top, float right, float bottom, const SkPaint* paint);
+            float left, float top, float right, float bottom, SkPaint* paint);
     virtual void drawColor(int color, SkXfermode::Mode mode);
-    virtual void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
+    virtual void drawRect(float left, float top, float right, float bottom, SkPaint* paint);
     virtual void drawPath(SkPath* path, SkPaint* paint);
-    virtual void drawLines(float* points, int count, const SkPaint* paint);
+    virtual void drawLines(float* points, int count, SkPaint* paint);
     virtual void drawText(const char* text, int bytesCount, int count, float x, float y,
             SkPaint* paint);
 
@@ -231,7 +231,7 @@
      * @param paint The paint containing the alpha, blending mode, etc.
      */
     void drawTextureRect(float left, float top, float right, float bottom,
-            const Texture* texture, const SkPaint* paint);
+            const Texture* texture, SkPaint* paint);
 
     /**
      * Draws a textured mesh with the specified texture. If the indices are omitted,
@@ -357,7 +357,7 @@
      * @param alpha Where to store the resulting alpha
      * @param mode Where to store the resulting xfermode
      */
-    inline void getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode);
+    inline void getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode);
 
     /**
      * Binds the specified texture with the specified wrap modes.
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index c571ea1..8f3655c 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -149,10 +149,10 @@
     }
 
     void snapToPixelBoundaries() {
-        left = floorf(left);
-        top = floorf(top);
-        right = ceilf(right);
-        bottom = ceilf(bottom);
+        left = floorf(left + 0.5f);
+        top = floorf(top + 0.5f);
+        right = floorf(right + 0.5f);
+        bottom = floorf(bottom + 0.5f);
     }
 
     void dump() const {
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
new file mode 100644
index 0000000..20b8d6c
--- /dev/null
+++ b/libs/hwui/ResourceCache.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <SkPixelRef.h>
+#include "ResourceCache.h"
+#include "Caches.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Resource cache
+///////////////////////////////////////////////////////////////////////////////
+
+void ResourceCache::logCache() {
+    LOGD("ResourceCache: cacheReport:");
+    for (size_t i = 0; i < mCache->size(); ++i) {
+        ResourceReference* ref = mCache->valueAt(i);
+        LOGD("  ResourceCache: mCache(%d): resource, ref = 0x%p, 0x%p",
+                i, mCache->keyAt(i), mCache->valueAt(i));
+        LOGD("  ResourceCache: mCache(%d): refCount, recycled, destroyed, type = %d, %d, %d, %d",
+                i, ref->refCount, ref->recycled, ref->destroyed, ref->resourceType);
+    }
+}
+
+ResourceCache::ResourceCache() {
+    mCache = new KeyedVector<void *, ResourceReference *>();
+}
+
+ResourceCache::~ResourceCache() {
+    delete mCache;
+}
+
+void ResourceCache::incrementRefcount(void* resource, ResourceType resourceType) {
+    for (size_t i = 0; i < mCache->size(); ++i) {
+        void* ref = mCache->valueAt(i);
+    }
+    ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL;
+    if (ref == NULL || mCache->size() == 0) {
+        ref = new ResourceReference(resourceType);
+        mCache->add(resource, ref);
+    }
+    ref->refCount++;
+}
+
+void ResourceCache::incrementRefcount(SkBitmap* bitmapResource) {
+    bitmapResource->pixelRef()->safeRef();
+    bitmapResource->getColorTable()->safeRef();
+    incrementRefcount((void*)bitmapResource, kBitmap);
+}
+
+void ResourceCache::incrementRefcount(SkMatrix* matrixResource) {
+    incrementRefcount((void*)matrixResource, kMatrix);
+}
+
+void ResourceCache::incrementRefcount(SkPaint* paintResource) {
+    incrementRefcount((void*)paintResource, kPaint);
+}
+
+void ResourceCache::incrementRefcount(SkiaShader* shaderResource) {
+    shaderResource->getSkShader()->safeRef();
+    incrementRefcount((void*)shaderResource, kShader);
+}
+
+void ResourceCache::decrementRefcount(void* resource) {
+    ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL;
+    if (ref == NULL) {
+        // Should not get here - shouldn't get a call to decrement if we're not yet tracking it
+        return;
+    }
+    ref->refCount--;
+    if (ref->refCount == 0) {
+        deleteResourceReference(resource, ref);
+    }
+}
+
+void ResourceCache::decrementRefcount(SkBitmap* bitmapResource) {
+    bitmapResource->pixelRef()->safeUnref();
+    bitmapResource->getColorTable()->safeUnref();
+    decrementRefcount((void*)bitmapResource);
+}
+
+void ResourceCache::decrementRefcount(SkiaShader* shaderResource) {
+    shaderResource->getSkShader()->safeUnref();
+    decrementRefcount((void*)shaderResource);
+}
+
+void ResourceCache::recycle(SkBitmap* resource) {
+    if (mCache->indexOfKey(resource) < 0) {
+        // not tracking this resource; just recycle the pixel data
+        resource->setPixels(NULL, NULL);
+        return;
+    }
+    recycle((void*) resource);
+}
+
+void ResourceCache::recycle(void* resource) {
+    ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL;
+    if (ref == NULL) {
+        // Should not get here - shouldn't get a call to recycle if we're not yet tracking it
+        return;
+    }
+    ref->recycled = true;
+    if (ref->refCount == 0) {
+        deleteResourceReference(resource, ref);
+    }
+}
+
+void ResourceCache::destructor(SkBitmap* resource) {
+    ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL;
+    if (ref == NULL) {
+        // If we're not tracking this resource, just delete it
+        if (Caches::hasInstance()) {
+            Caches::getInstance().textureCache.remove(resource);
+        }
+        delete resource;
+        return;
+    }
+    ref->destroyed = true;
+    if (ref->refCount == 0) {
+        deleteResourceReference(resource, ref);
+        return;
+    }
+}
+
+void ResourceCache::destructor(SkMatrix* resource) {
+    ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL;
+    if (ref == NULL) {
+        // If we're not tracking this resource, just delete it
+        delete resource;
+        return;
+    }
+    ref->destroyed = true;
+    if (ref->refCount == 0) {
+        deleteResourceReference(resource, ref);
+        return;
+    }
+}
+
+void ResourceCache::destructor(SkPaint* resource) {
+    ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL;
+    if (ref == NULL) {
+        // If we're not tracking this resource, just delete it
+        delete resource;
+        return;
+    }
+    ref->destroyed = true;
+    if (ref->refCount == 0) {
+        deleteResourceReference(resource, ref);
+        return;
+    }
+}
+
+void ResourceCache::destructor(SkiaShader* resource) {
+    ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL;
+    if (ref == NULL) {
+        // If we're not tracking this resource, just delete it
+        if (Caches::hasInstance()) {
+            Caches::getInstance().gradientCache.remove(resource->getSkShader());
+        }
+        delete resource;
+        return;
+    }
+    ref->destroyed = true;
+    if (ref->refCount == 0) {
+        deleteResourceReference(resource, ref);
+        return;
+    }
+}
+
+void ResourceCache::deleteResourceReference(void* resource, ResourceReference* ref) {
+    if (ref->recycled && ref->resourceType == kBitmap) {
+        ((SkBitmap*) resource)->setPixels(NULL, NULL);
+    }
+    if (ref->destroyed) {
+        switch (ref->resourceType) {
+            case kBitmap:
+            {
+                SkBitmap* bitmap = (SkBitmap*)resource;
+                if (Caches::hasInstance()) {
+                    Caches::getInstance().textureCache.remove(bitmap);
+                }
+                delete bitmap;
+            }
+            break;
+            case kMatrix:
+                delete (SkMatrix*) resource;
+                break;
+            case kPaint:
+                delete (SkPaint*) resource;
+                break;
+            case kShader:
+                SkiaShader* shader = (SkiaShader*)resource;
+                if (Caches::hasInstance()) {
+                    Caches::getInstance().gradientCache.remove(shader->getSkShader());
+                }
+                delete shader;
+                break;
+        }
+    }
+    mCache->removeItem(resource);
+    delete ref;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h
new file mode 100644
index 0000000..cda2718
--- /dev/null
+++ b/libs/hwui/ResourceCache.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_RESOURCE_CACHE_H
+#define ANDROID_UI_RESOURCE_CACHE_H
+
+#include <SkBitmap.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
+#include <SkiaShader.h>
+#include <utils/KeyedVector.h>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Type of Resource being cached
+ */
+enum ResourceType {
+    kBitmap,
+    kMatrix,
+    kPaint,
+    kShader,
+};
+
+class ResourceReference {
+public:
+
+    ResourceReference() { refCount = 0; recycled = false; destroyed = false;}
+    ResourceReference(ResourceType type) {
+        refCount = 0; recycled = false; destroyed = false; resourceType = type;
+    }
+
+    int refCount;
+    bool recycled;
+    bool destroyed;
+    ResourceType resourceType;
+};
+
+class ResourceCache {
+    KeyedVector<void *, ResourceReference *>* mCache;
+public:
+    ResourceCache();
+    ~ResourceCache();
+    void incrementRefcount(SkBitmap* resource);
+    void incrementRefcount(SkMatrix* resource);
+    void incrementRefcount(SkPaint* resource);
+    void incrementRefcount(SkiaShader* resource);
+    void incrementRefcount(const void* resource, ResourceType resourceType);
+    void decrementRefcount(void* resource);
+    void decrementRefcount(SkBitmap* resource);
+    void decrementRefcount(SkiaShader* resource);
+    void recycle(void* resource);
+    void recycle(SkBitmap* resource);
+    void destructor(SkBitmap* resource);
+    void destructor(SkMatrix* resource);
+    void destructor(SkPaint* resource);
+    void destructor(SkiaShader* resource);
+private:
+    void deleteResourceReference(void* resource, ResourceReference* ref);
+    void incrementRefcount(void* resource, ResourceType resourceType);
+    void logCache();
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_RESOURCE_CACHE_H
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index 2565e65..011991a 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -60,6 +60,10 @@
     virtual void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
             GLuint* textureUnit);
 
+    inline SkShader *getSkShader() {
+        return mKey;
+    }
+
     inline bool blend() const {
         return mBlend;
     }
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 05c1a48..2ec003f 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -23,7 +23,13 @@
 # TODO: This should go into build/core/config.mk
 RSG_GENERATOR:=$(LOCAL_BUILT_MODULE)
 
-
+include $(CLEAR_VARS)
+input_data_file := $(LOCAL_PATH)/rslib.bc
+slangdata_output_var_name := rs_runtime_lib_bc
+LOCAL_MODULE := librslib_rt
+LOCAL_MODULE_TAGS := optional
+include frameworks/compile/slang/SlangData.mk
+include $(BUILD_STATIC_LIBRARY)
 
 # Build render script lib ====================
 
@@ -109,7 +115,7 @@
 
 LOCAL_SHARED_LIBRARIES += libcutils libutils libEGL libGLESv1_CM libGLESv2 libui libbcc
 
-LOCAL_STATIC_LIBRARIES := libft2
+LOCAL_STATIC_LIBRARIES := libft2 librslib_rt
 
 LOCAL_C_INCLUDES += external/freetype/include
 
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index e63cc9b..d078d46 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -288,7 +288,7 @@
 
 
 // Async commands for returning new IDS
-void * rsaTypeCreate(RsContext, RsElement, uint32_t dimCount,
+RsType rsaTypeCreate(RsContext, RsElement, uint32_t dimCount,
                      const RsDimension *dims, const uint32_t *vals);
 
 
diff --git a/libs/rs/rsAdapter.cpp b/libs/rs/rsAdapter.cpp
index ef69b75..1d1425c 100644
--- a/libs/rs/rsAdapter.cpp
+++ b/libs/rs/rsAdapter.cpp
@@ -27,15 +27,11 @@
 
 Adapter1D::Adapter1D(Context *rsc) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     reset();
 }
 
 Adapter1D::Adapter1D(Context *rsc, Allocation *a) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     reset();
     setAllocation(a);
 }
@@ -76,7 +72,7 @@
 
 void Adapter1D::serialize(OStream *stream) const
 {
-    
+
 }
 
 Adapter1D *Adapter1D::createFromStream(Context *rsc, IStream *stream)
@@ -145,15 +141,11 @@
 
 Adapter2D::Adapter2D(Context *rsc) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     reset();
 }
 
 Adapter2D::Adapter2D(Context *rsc, Allocation *a) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     reset();
     setAllocation(a);
 }
@@ -200,7 +192,7 @@
 
 void Adapter2D::serialize(OStream *stream) const
 {
-    
+
 }
 
 Adapter2D *Adapter2D::createFromStream(Context *rsc, IStream *stream)
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 6748bb4..fc41a72 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -57,8 +57,6 @@
 
 void Allocation::init(Context *rsc, const Type *type)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mPtr = NULL;
 
     mCpuWrite = false;
@@ -478,7 +476,7 @@
     uint32_t dataSize = stream->loadU32();
     if(dataSize != type->getSizeBytes()) {
         LOGE("failed to read allocation because numbytes written is not the same loaded type wants\n");
-        delete type;
+        ObjectBase::checkDelete(type);
         return NULL;
     }
 
diff --git a/libs/rs/rsElement.cpp b/libs/rs/rsElement.cpp
index 9e6fbd5..dc021fc 100644
--- a/libs/rs/rsElement.cpp
+++ b/libs/rs/rsElement.cpp
@@ -30,8 +30,6 @@
 Element::Element(Context *rsc) : ObjectBase(rsc)
 {
     mBits = 0;
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mFields = NULL;
     mFieldCount = 0;
     mHasReference = false;
@@ -140,7 +138,7 @@
     for (uint32_t ct=0; ct < rsc->mStateElement.mElements.size(); ct++) {
         Element *ee = rsc->mStateElement.mElements[ct];
         if(ee->isEqual(elem)) {
-            delete elem;
+            ObjectBase::checkDelete(elem);
             ee->incUserRef();
             return ee;
         }
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index 633129a..d171a48 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -36,8 +36,6 @@
 
 Font::Font(Context *rsc) : ObjectBase(rsc), mCachedGlyphs(NULL)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mInitialized = false;
     mHasKerning = false;
     mFace = NULL;
@@ -308,7 +306,7 @@
         return newFont;
     }
 
-    delete newFont;
+    ObjectBase::checkDelete(newFont);
     return NULL;
 
 }
diff --git a/libs/rs/rsMesh.cpp b/libs/rs/rsMesh.cpp
index 8e43f24..761be93 100644
--- a/libs/rs/rsMesh.cpp
+++ b/libs/rs/rsMesh.cpp
@@ -33,8 +33,6 @@
 
 Mesh::Mesh(Context *rsc) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mPrimitives = NULL;
     mPrimitivesCount = 0;
     mVertexBuffers = NULL;
diff --git a/libs/rs/rsObjectBase.cpp b/libs/rs/rsObjectBase.cpp
index 46b1750..724172e 100644
--- a/libs/rs/rsObjectBase.cpp
+++ b/libs/rs/rsObjectBase.cpp
@@ -22,6 +22,7 @@
 #include "rsContextHostStub.h"
 #endif
 
+
 using namespace android;
 using namespace android::renderscript;
 
@@ -34,101 +35,119 @@
     mRSC = rsc;
     mNext = NULL;
     mPrev = NULL;
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
+
+#if RS_OBJECT_DEBUG
+    mStack.update(2);
+#endif
 
     rsAssert(rsc);
     add();
+    //LOGV("ObjectBase %p con", this);
 }
 
 ObjectBase::~ObjectBase()
 {
     //LOGV("~ObjectBase %p  ref %i,%i", this, mUserRefCount, mSysRefCount);
+#if RS_OBJECT_DEBUG
+    mStack.dump();
+#endif
+
+    if(mPrev || mNext) {
+        // While the normal practice is to call remove before we call
+        // delete.  Its possible for objects without a re-use list
+        // for avoiding duplication to be created on the stack.  In those
+        // cases we need to remove ourself here.
+        asyncLock();
+        remove();
+        asyncUnlock();
+    }
+
     rsAssert(!mUserRefCount);
     rsAssert(!mSysRefCount);
-    remove();
 }
 
 void ObjectBase::dumpLOGV(const char *op) const
 {
     if (mName.size()) {
-        LOGV("%s RSobj %p, name %s, refs %i,%i  from %s,%i links %p,%p,%p",
-             op, this, mName.string(), mUserRefCount, mSysRefCount, mAllocFile, mAllocLine, mNext, mPrev, mRSC);
+        LOGV("%s RSobj %p, name %s, refs %i,%i  links %p,%p,%p",
+             op, this, mName.string(), mUserRefCount, mSysRefCount, mNext, mPrev, mRSC);
     } else {
-        LOGV("%s RSobj %p, no-name, refs %i,%i  from %s,%i links %p,%p,%p",
-             op, this, mUserRefCount, mSysRefCount, mAllocFile, mAllocLine, mNext, mPrev, mRSC);
+        LOGV("%s RSobj %p, no-name, refs %i,%i  links %p,%p,%p",
+             op, this, mUserRefCount, mSysRefCount, mNext, mPrev, mRSC);
     }
 }
 
 void ObjectBase::incUserRef() const
 {
-    lockUserRef();
-    mUserRefCount++;
-    unlockUserRef();
-    //LOGV("ObjectBase %p inc ref %i", this, mUserRefCount);
-}
-
-void ObjectBase::prelockedIncUserRef() const
-{
-    mUserRefCount++;
+    android_atomic_inc(&mUserRefCount);
+    //LOGV("ObjectBase %p incU ref %i, %i", this, mUserRefCount, mSysRefCount);
 }
 
 void ObjectBase::incSysRef() const
 {
-    mSysRefCount ++;
-    //LOGV("ObjectBase %p inc ref %i", this, mSysRefCount);
+    android_atomic_inc(&mSysRefCount);
+    //LOGV("ObjectBase %p incS ref %i, %i", this, mUserRefCount, mSysRefCount);
 }
 
-bool ObjectBase::checkDelete() const
+void ObjectBase::preDestroy() const
 {
-    if (!(mSysRefCount | mUserRefCount)) {
-        lockUserRef();
+}
 
-        // Recheck the user ref count since it can be incremented from other threads.
-        if (mUserRefCount) {
-            unlockUserRef();
-            return false;
-        }
+bool ObjectBase::checkDelete(const ObjectBase *ref)
+{
+    if (!ref) {
+        return false;
+    }
 
-        if (mRSC && mRSC->props.mLogObjects) {
-            dumpLOGV("checkDelete");
-        }
-        unlockUserRef();
-        delete this;
-        return true;
+    asyncLock();
+    // This lock protects us against the non-RS threads changing
+    // the ref counts.  At this point we should be the only thread
+    // working on them.
+    if (ref->mUserRefCount || ref->mSysRefCount) {
+        asyncUnlock();
+        return false;
+    }
+
+    ref->remove();
+    // At this point we can unlock because there should be no possible way
+    // for another thread to reference this object.
+    ref->preDestroy();
+    asyncUnlock();
+    delete ref;
+    return true;
+}
+
+
+bool ObjectBase::decUserRef() const
+{
+    //LOGV("ObjectBase %p decU ref %i, %i", this, mUserRefCount, mSysRefCount);
+    rsAssert(mUserRefCount > 0);
+    if ((android_atomic_dec(&mUserRefCount) <= 1) &&
+        (android_atomic_acquire_load(&mSysRefCount) <= 0)) {
+        return checkDelete(this);
     }
     return false;
 }
 
-bool ObjectBase::decUserRef() const
-{
-    lockUserRef();
-    rsAssert(mUserRefCount > 0);
-    //dumpLOGV("decUserRef");
-    mUserRefCount--;
-    unlockUserRef();
-    bool ret = checkDelete();
-    return ret;
-}
-
 bool ObjectBase::zeroUserRef() const
 {
-    lockUserRef();
-    // This can only happen during cleanup and is therefore
-    // thread safe.
-    mUserRefCount = 0;
-    //dumpLOGV("zeroUserRef");
-    unlockUserRef();
-    bool ret = checkDelete();
-    return ret;
+    //LOGV("ObjectBase %p zeroU ref %i, %i", this, mUserRefCount, mSysRefCount);
+    android_atomic_acquire_store(0, &mUserRefCount);
+    if (android_atomic_acquire_load(&mSysRefCount) <= 0) {
+        return checkDelete(this);
+    }
+    return false;
 }
 
 bool ObjectBase::decSysRef() const
 {
+    //LOGV("ObjectBase %p decS ref %i, %i", this, mUserRefCount, mSysRefCount);
     rsAssert(mSysRefCount > 0);
-    mSysRefCount --;
-    //dumpLOGV("decSysRef");
-    return checkDelete();
+    if ((android_atomic_dec(&mSysRefCount) <= 1) &&
+        (android_atomic_acquire_load(&mUserRefCount) <= 0)) {
+        return checkDelete(this);
+    }
+    return false;
 }
 
 void ObjectBase::setName(const char *name)
@@ -141,19 +160,19 @@
     mName.setTo(name, len);
 }
 
-void ObjectBase::lockUserRef()
+void ObjectBase::asyncLock()
 {
     pthread_mutex_lock(&gObjectInitMutex);
 }
 
-void ObjectBase::unlockUserRef()
+void ObjectBase::asyncUnlock()
 {
     pthread_mutex_unlock(&gObjectInitMutex);
 }
 
 void ObjectBase::add() const
 {
-    pthread_mutex_lock(&gObjectInitMutex);
+    asyncLock();
 
     rsAssert(!mNext);
     rsAssert(!mPrev);
@@ -164,12 +183,11 @@
     }
     mRSC->mObjHead = this;
 
-    pthread_mutex_unlock(&gObjectInitMutex);
+    asyncUnlock();
 }
 
 void ObjectBase::remove() const
 {
-    lockUserRef();
     //LOGV("calling remove  rsc %p", mRSC);
     if (!mRSC) {
         rsAssert(!mPrev);
@@ -188,7 +206,6 @@
     }
     mPrev = NULL;
     mNext = NULL;
-    unlockUserRef();
 }
 
 void ObjectBase::zeroAllUserRef(Context *rsc)
@@ -219,7 +236,7 @@
 
 void ObjectBase::dumpAll(Context *rsc)
 {
-    lockUserRef();
+    asyncLock();
 
     LOGV("Dumping all objects");
     const ObjectBase * o = rsc->mObjHead;
@@ -229,22 +246,22 @@
         o = o->mNext;
     }
 
-    unlockUserRef();
+    asyncUnlock();
 }
 
 bool ObjectBase::isValid(const Context *rsc, const ObjectBase *obj)
 {
-    lockUserRef();
+    asyncLock();
 
     const ObjectBase * o = rsc->mObjHead;
     while (o) {
         if (o == obj) {
-            unlockUserRef();
+            asyncUnlock();
             return true;
         }
         o = o->mNext;
     }
-    unlockUserRef();
+    asyncUnlock();
     return false;
 }
 
diff --git a/libs/rs/rsObjectBase.h b/libs/rs/rsObjectBase.h
index 8d1ace1..5f03db5 100644
--- a/libs/rs/rsObjectBase.h
+++ b/libs/rs/rsObjectBase.h
@@ -19,6 +19,11 @@
 
 #include "rsUtils.h"
 
+#define RS_OBJECT_DEBUG 0
+
+#if RS_OBJECT_DEBUG
+    #include <utils/CallStack.h>
+#endif
 
 namespace android {
 namespace renderscript {
@@ -31,7 +36,6 @@
 {
 public:
     ObjectBase(Context *rsc);
-    virtual ~ObjectBase();
 
     void incSysRef() const;
     bool decSysRef() const;
@@ -39,7 +43,8 @@
     void incUserRef() const;
     bool decUserRef() const;
     bool zeroUserRef() const;
-    void prelockedIncUserRef() const;
+
+    static bool checkDelete(const ObjectBase *);
 
     const char * getName() const {
         return mName.string();
@@ -58,13 +63,18 @@
 
     static bool isValid(const Context *rsc, const ObjectBase *obj);
 
-    static void lockUserRef();
-    static void unlockUserRef();
+    // The async lock is taken during object creation in non-rs threads
+    // and object deletion in the rs thread.
+    static void asyncLock();
+    static void asyncUnlock();
 
 protected:
-    const char *mAllocFile;
-    uint32_t mAllocLine;
+    // Called inside the async lock for any object list management that is
+    // necessary in derived classes.
+    virtual void preDestroy() const;
+
     Context *mRSC;
+    virtual ~ObjectBase();
 
 private:
     static pthread_mutex_t gObjectInitMutex;
@@ -72,14 +82,17 @@
     void add() const;
     void remove() const;
 
-    bool checkDelete() const;
-
     String8 mName;
     mutable int32_t mSysRefCount;
     mutable int32_t mUserRefCount;
 
     mutable const ObjectBase * mPrev;
     mutable const ObjectBase * mNext;
+
+#if RS_OBJECT_DEBUG
+    CallStack mStack;
+#endif
+
 };
 
 template<class T>
@@ -104,6 +117,10 @@
         }
     }
 
+    ObjectBaseRef & operator= (const ObjectBaseRef &ref) {
+        return ObjectBaseRef(ref);
+    }
+
     ~ObjectBaseRef() {
         clear();
     }
diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp
index 10e00e6..72d1b02 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -31,8 +31,6 @@
 
 Program::Program(Context *rsc) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mDirty = true;
     mShaderID = 0;
     mAttribCount = 0;
@@ -55,8 +53,6 @@
                  const uint32_t * params, uint32_t paramLength) :
     ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mDirty = true;
     mShaderID = 0;
     mAttribCount = 0;
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 81b4fa4..33399d5 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -36,9 +36,6 @@
                                  uint32_t paramLength) :
     Program(rsc, shaderText, shaderLength, params, paramLength)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
-
     mConstantColor[0] = 1.f;
     mConstantColor[1] = 1.f;
     mConstantColor[2] = 1.f;
@@ -182,8 +179,8 @@
 
 ProgramFragmentState::~ProgramFragmentState()
 {
-    delete mPF;
-
+    ObjectBase::checkDelete(mPF);
+    mPF = NULL;
 }
 
 void ProgramFragmentState::init(Context *rsc)
diff --git a/libs/rs/rsProgramRaster.cpp b/libs/rs/rsProgramRaster.cpp
index 62d060d..5f8c4ba 100644
--- a/libs/rs/rsProgramRaster.cpp
+++ b/libs/rs/rsProgramRaster.cpp
@@ -36,8 +36,6 @@
                              bool pointSprite) :
     Program(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mPointSmooth = pointSmooth;
     mLineSmooth = lineSmooth;
     mPointSprite = pointSprite;
diff --git a/libs/rs/rsProgramStore.cpp b/libs/rs/rsProgramStore.cpp
index 586a89f..876299f 100644
--- a/libs/rs/rsProgramStore.cpp
+++ b/libs/rs/rsProgramStore.cpp
@@ -33,8 +33,6 @@
 ProgramStore::ProgramStore(Context *rsc) :
     Program(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mDitherEnable = true;
     mBlendEnable = false;
     mColorRWriteEnable = true;
@@ -236,8 +234,8 @@
 
 ProgramStoreState::~ProgramStoreState()
 {
-    delete mPFS;
-
+    ObjectBase::checkDelete(mPFS);
+    mPFS = NULL;
 }
 
 void ProgramStoreState::init(Context *rsc)
@@ -258,9 +256,8 @@
 
 void rsi_ProgramStoreBegin(Context * rsc, RsElement in, RsElement out)
 {
-    delete rsc->mStateFragmentStore.mPFS;
+    ObjectBase::checkDelete(rsc->mStateFragmentStore.mPFS);
     rsc->mStateFragmentStore.mPFS = new ProgramStore(rsc);
-
 }
 
 void rsi_ProgramStoreDepthFunc(Context *rsc, RsDepthFunc func)
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index a785262..d12439f 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -37,9 +37,6 @@
                              uint32_t paramLength) :
     Program(rsc, shaderText, shaderLength, params, paramLength)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
-
     init(rsc);
 }
 
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index 180d78e..cfae7b2 100644
--- a/libs/rs/rsSampler.cpp
+++ b/libs/rs/rsSampler.cpp
@@ -33,8 +33,6 @@
 
 Sampler::Sampler(Context *rsc) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     // Should not get called.
     rsAssert(0);
 }
@@ -47,8 +45,6 @@
                  RsSamplerValue wrapR,
                  float aniso) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mMagFilter = magFilter;
     mMinFilter = minFilter;
     mWrapS = wrapS;
diff --git a/libs/rs/rsScript.cpp b/libs/rs/rsScript.cpp
index c5632b5..ef380d2 100644
--- a/libs/rs/rsScript.cpp
+++ b/libs/rs/rsScript.cpp
@@ -21,8 +21,6 @@
 
 Script::Script(Context *rsc) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     memset(&mEnviroment, 0, sizeof(mEnviroment));
 
     mSlots = NULL;
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index a2910d7..9dce158 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -35,8 +35,6 @@
 
 ScriptC::ScriptC(Context *rsc) : Script(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mBccScript = NULL;
     memset(&mProgram, 0, sizeof(mProgram));
 }
@@ -392,6 +390,9 @@
     return NULL;
 }
 
+extern const char rs_runtime_lib_bc[];
+extern unsigned rs_runtime_lib_bc_size;
+
 void ScriptCState::runCompiler(Context *rsc, ScriptC *s)
 {
     LOGV("%p ScriptCState::runCompiler ", rsc);
@@ -400,6 +401,7 @@
         s->mBccScript = bccCreateScript();
         s->mEnviroment.mIsThreadable = true;
         bccScriptBitcode(s->mBccScript, s->mEnviroment.mScriptText, s->mEnviroment.mScriptTextLength);
+        //bccLinkBitcode(s->mBccScript, rs_runtime_lib_bc, rs_runtime_lib_bc_size);
         bccRegisterSymbolCallback(s->mBccScript, symbolLookup, s);
         bccCompileScript(s->mBccScript);
         bccGetScriptLabel(s->mBccScript, "root", (BCCvoid**) &s->mProgram.mRoot);
@@ -524,16 +526,14 @@
 {
     ScriptCState *ss = &rsc->mScriptC;
 
-    ObjectBaseRef<ScriptC> s = ss->mScript.get();
+    ObjectBaseRef<ScriptC> s(ss->mScript);
     ss->mScript.clear();
+    s->incUserRef();
 
     ss->runCompiler(rsc, s.get());
-    s->incUserRef();
     ss->clear(rsc);
     return s.get();
 }
 
 }
 }
-
-
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index 27b1b4f..82ad33e 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -27,8 +27,6 @@
 
 Type::Type(Context *rsc) : ObjectBase(rsc)
 {
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
     mLODs = 0;
     mLODCount = 0;
     mAttribs = NULL;
@@ -36,7 +34,7 @@
     clear();
 }
 
-Type::~Type()
+void Type::preDestroy()
 {
     for (uint32_t ct = 0; ct < mRSC->mStateType.mTypes.size(); ct++) {
         if (mRSC->mStateType.mTypes[ct] == this) {
@@ -44,6 +42,10 @@
             break;
         }
     }
+}
+
+Type::~Type()
+{
     if (mLODs) {
         delete [] mLODs;
         mLODs = NULL;
@@ -401,7 +403,7 @@
 }
 }
 
-void * rsaTypeCreate(RsContext con, RsElement _e, uint32_t dimCount,
+RsType rsaTypeCreate(RsContext con, RsElement _e, uint32_t dimCount,
                      const RsDimension *dims, const uint32_t *vals)
 {
     Context *rsc = static_cast<Context *>(con);
@@ -428,7 +430,7 @@
         }
     }
 
-    ObjectBase::lockUserRef();
+    ObjectBase::asyncLock();
     for (uint32_t ct=0; ct < stc->mTypes.size(); ct++) {
         Type *t = stc->mTypes[ct];
         if (t->getElement() != e) continue;
@@ -437,11 +439,11 @@
         if (t->getDimZ() != dimZ) continue;
         if (t->getDimLOD() != dimLOD) continue;
         if (t->getDimFaces() != dimFaces) continue;
-        t->prelockedIncUserRef();
-        ObjectBase::unlockUserRef();
+        t->incUserRef();
+        ObjectBase::asyncUnlock();
         return t;
     }
-    ObjectBase::unlockUserRef();
+    ObjectBase::asyncUnlock();
 
     Type * st = new Type(rsc);
     st->incUserRef();
@@ -453,9 +455,9 @@
     st->setDimFaces(dimFaces);
     st->compute();
 
-    ObjectBase::lockUserRef();
+    ObjectBase::asyncLock();
     stc->mTypes.push(st);
-    ObjectBase::unlockUserRef();
+    ObjectBase::asyncUnlock();
     return st;
 }
 
diff --git a/libs/rs/rsType.h b/libs/rs/rsType.h
index a0c77ab..6b89413 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -124,6 +124,9 @@
     bool isValidGLComponent(uint32_t fieldIdx);
     void makeGLComponents();
 
+protected:
+    virtual void preDestroy();
+
 private:
     Type(const Type &);
 };
diff --git a/libs/rs/rslib.bc b/libs/rs/rslib.bc
new file mode 100644
index 0000000..1897c3b
--- /dev/null
+++ b/libs/rs/rslib.bc
Binary files differ
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 97a7528..9c7e7f4 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -403,6 +403,7 @@
                 outEvent->deviceId = device->id;
             }
             outEvent->type = DEVICE_REMOVED;
+            outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
             delete device;
             mNeedToSendFinishedDeviceScan = true;
             return true;
@@ -419,6 +420,7 @@
                 outEvent->deviceId = device->id;
             }
             outEvent->type = DEVICE_ADDED;
+            outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
             mNeedToSendFinishedDeviceScan = true;
             return true;
         }
@@ -426,6 +428,7 @@
         if (mNeedToSendFinishedDeviceScan) {
             mNeedToSendFinishedDeviceScan = false;
             outEvent->type = FINISHED_DEVICE_SCAN;
+            outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
             return true;
         }
 
@@ -839,7 +842,7 @@
         }
 
         // See if this device has a gamepad.
-        for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES); i++) {
+        for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) {
             if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
                 device->classes |= INPUT_DEVICE_CLASS_GAMEPAD;
                 break;
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index d679ea2..629234b 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -124,12 +124,19 @@
                 pointerCount, MAX_POINTERS);
         return false;
     }
+    BitSet32 pointerIdBits;
     for (size_t i = 0; i < pointerCount; i++) {
-        if (pointerIds[i] < 0 || pointerIds[i] > MAX_POINTER_ID) {
+        int32_t id = pointerIds[i];
+        if (id < 0 || id > MAX_POINTER_ID) {
             LOGE("Motion event has invalid pointer id %d; value must be between 0 and %d",
-                    pointerIds[i], MAX_POINTER_ID);
+                    id, MAX_POINTER_ID);
             return false;
         }
+        if (pointerIdBits.hasBit(id)) {
+            LOGE("Motion event has duplicate pointer id %d", id);
+            return false;
+        }
+        pointerIdBits.markBit(id);
     }
     return true;
 }
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 88b91e0..120222c 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -266,7 +266,7 @@
         break;
 
     case EventHubInterface::FINISHED_DEVICE_SCAN:
-        handleConfigurationChanged();
+        handleConfigurationChanged(rawEvent->when);
         break;
 
     default:
@@ -404,7 +404,7 @@
     } // release device registry reader lock
 }
 
-void InputReader::handleConfigurationChanged() {
+void InputReader::handleConfigurationChanged(nsecs_t when) {
     // Reset global meta state because it depends on the list of all configured devices.
     updateGlobalMetaState();
 
@@ -412,7 +412,6 @@
     updateInputConfiguration();
 
     // Enqueue configuration changed.
-    nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
     mDispatcher->notifyConfigurationChanged(when);
 }
 
@@ -2189,7 +2188,7 @@
                 mLocked.currentVirtualKey.down = false;
 #if DEBUG_VIRTUAL_KEYS
                 LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
-                        mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+                        mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode);
 #endif
                 keyEventAction = AKEY_EVENT_ACTION_UP;
                 keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
@@ -2214,13 +2213,22 @@
             mLocked.currentVirtualKey.down = false;
 #if DEBUG_VIRTUAL_KEYS
             LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
-                    mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+                    mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode);
 #endif
             keyEventAction = AKEY_EVENT_ACTION_UP;
             keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
                     | AKEY_EVENT_FLAG_CANCELED;
-            touchResult = DROP_STROKE;
-            goto DispatchVirtualKey;
+
+            // Check whether the pointer moved inside the display area where we should
+            // start a new stroke.
+            int32_t x = mCurrentTouch.pointers[0].x;
+            int32_t y = mCurrentTouch.pointers[0].y;
+            if (isPointInsideSurfaceLocked(x, y)) {
+                mLastTouch.clear();
+                touchResult = DISPATCH_TOUCH;
+            } else {
+                touchResult = DROP_STROKE;
+            }
         } else {
             if (mCurrentTouch.pointerCount >= 1 && mLastTouch.pointerCount == 0) {
                 // Pointer just went down.  Handle off-screen touches, if needed.
@@ -2238,7 +2246,8 @@
                             mLocked.currentVirtualKey.scanCode = virtualKey->scanCode;
 #if DEBUG_VIRTUAL_KEYS
                             LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
-                                    mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+                                    mLocked.currentVirtualKey.keyCode,
+                                    mLocked.currentVirtualKey.scanCode);
 #endif
                             keyEventAction = AKEY_EVENT_ACTION_DOWN;
                             keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM
@@ -2285,14 +2294,35 @@
         dispatchTouch(when, policyFlags, & mCurrentTouch,
                 currentIdBits, -1, currentPointerCount, motionEventAction);
     } else {
-        // There may be pointers going up and pointers going down at the same time when pointer
-        // ids are reported by the device driver.
+        // There may be pointers going up and pointers going down and pointers moving
+        // all at the same time.
         BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value);
         BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value);
         BitSet32 activeIdBits(lastIdBits.value);
         uint32_t pointerCount = lastPointerCount;
 
-        while (! upIdBits.isEmpty()) {
+        // Produce an intermediate representation of the touch data that consists of the
+        // old location of pointers that have just gone up and the new location of pointers that
+        // have just moved but omits the location of pointers that have just gone down.
+        TouchData interimTouch;
+        interimTouch.copyFrom(mLastTouch);
+
+        BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
+        bool moveNeeded = false;
+        while (!moveIdBits.isEmpty()) {
+            uint32_t moveId = moveIdBits.firstMarkedBit();
+            moveIdBits.clearBit(moveId);
+
+            int32_t oldIndex = mLastTouch.idToIndex[moveId];
+            int32_t newIndex = mCurrentTouch.idToIndex[moveId];
+            if (mLastTouch.pointers[oldIndex] != mCurrentTouch.pointers[newIndex]) {
+                interimTouch.pointers[oldIndex] = mCurrentTouch.pointers[newIndex];
+                moveNeeded = true;
+            }
+        }
+
+        // Dispatch pointer up events using the interim pointer locations.
+        while (!upIdBits.isEmpty()) {
             uint32_t upId = upIdBits.firstMarkedBit();
             upIdBits.clearBit(upId);
             BitSet32 oldActiveIdBits = activeIdBits;
@@ -2305,12 +2335,21 @@
                 motionEventAction = AMOTION_EVENT_ACTION_POINTER_UP;
             }
 
-            dispatchTouch(when, policyFlags, & mLastTouch,
+            dispatchTouch(when, policyFlags, &interimTouch,
                     oldActiveIdBits, upId, pointerCount, motionEventAction);
             pointerCount -= 1;
         }
 
-        while (! downIdBits.isEmpty()) {
+        // Dispatch move events if any of the remaining pointers moved from their old locations.
+        // Although applications receive new locations as part of individual pointer up
+        // events, they do not generally handle them except when presented in a move event.
+        if (moveNeeded) {
+            dispatchTouch(when, policyFlags, &mCurrentTouch,
+                    activeIdBits, -1, pointerCount, AMOTION_EVENT_ACTION_MOVE);
+        }
+
+        // Dispatch pointer down events using the new pointer locations.
+        while (!downIdBits.isEmpty()) {
             uint32_t downId = downIdBits.firstMarkedBit();
             downIdBits.clearBit(downId);
             BitSet32 oldActiveIdBits = activeIdBits;
@@ -2325,7 +2364,7 @@
             }
 
             pointerCount += 1;
-            dispatchTouch(when, policyFlags, & mCurrentTouch,
+            dispatchTouch(when, policyFlags, &mCurrentTouch,
                     activeIdBits, downId, pointerCount, motionEventAction);
         }
     }
@@ -3434,8 +3473,8 @@
 
         if (fields & Accumulator::FIELD_ABS_MT_PRESSURE) {
             if (inPointer.absMTPressure <= 0) {
-                // Some devices send sync packets with X / Y but with a 0 presure to indicate
-                // a pointer up.  Drop this finger.
+                // Some devices send sync packets with X / Y but with a 0 pressure to indicate
+                // a pointer going up.  Drop this finger.
                 continue;
             }
             outPointer.pressure = inPointer.absMTPressure;
diff --git a/libs/ui/tests/Android.mk b/libs/ui/tests/Android.mk
index 62f824f..aa017b9 100644
--- a/libs/ui/tests/Android.mk
+++ b/libs/ui/tests/Android.mk
@@ -7,6 +7,7 @@
 # Build the unit tests.
 test_src_files := \
     InputChannel_test.cpp \
+    InputReader_test.cpp \
     InputDispatcher_test.cpp \
     InputPublisherAndConsumer_test.cpp
 
diff --git a/libs/ui/tests/InputDispatcher_test.cpp b/libs/ui/tests/InputDispatcher_test.cpp
index 1dc6e46..8874dfe 100644
--- a/libs/ui/tests/InputDispatcher_test.cpp
+++ b/libs/ui/tests/InputDispatcher_test.cpp
@@ -4,15 +4,223 @@
 
 #include <ui/InputDispatcher.h>
 #include <gtest/gtest.h>
+#include <linux/input.h>
 
 namespace android {
 
-class InputDispatcherTest : public testing::Test {
+// An arbitrary time value.
+static const nsecs_t ARBITRARY_TIME = 1234;
+
+// An arbitrary device id.
+static const int32_t DEVICE_ID = 1;
+
+// An arbitrary injector pid / uid pair that has permission to inject events.
+static const int32_t INJECTOR_PID = 999;
+static const int32_t INJECTOR_UID = 1001;
+
+
+// --- FakeInputDispatcherPolicy ---
+
+class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
+protected:
+    virtual ~FakeInputDispatcherPolicy() {
+    }
+
 public:
+    FakeInputDispatcherPolicy() {
+    }
+
+private:
+    virtual void notifyConfigurationChanged(nsecs_t when) {
+    }
+
+    virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+            const sp<InputChannel>& inputChannel) {
+        return 0;
+    }
+
+    virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel) {
+    }
+
+    virtual nsecs_t getKeyRepeatTimeout() {
+        return 500 * 1000000LL;
+    }
+
+    virtual nsecs_t getKeyRepeatDelay() {
+        return 50 * 1000000LL;
+    }
+
+    virtual int32_t getMaxEventsPerSecond() {
+        return 60;
+    }
+
+    virtual void interceptKeyBeforeQueueing(nsecs_t when, int32_t deviceId,
+            int32_t action, int32_t& flags, int32_t keyCode, int32_t scanCode,
+            uint32_t& policyFlags) {
+    }
+
+    virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
+    }
+
+    virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
+            const KeyEvent* keyEvent, uint32_t policyFlags) {
+        return false;
+    }
+
+    virtual void notifySwitch(nsecs_t when,
+            int32_t switchCode, int32_t switchValue, uint32_t policyFlags) {
+    }
+
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
+    }
+
+    virtual bool checkInjectEventsPermissionNonReentrant(
+            int32_t injectorPid, int32_t injectorUid) {
+        return false;
+    }
 };
 
-TEST_F(InputDispatcherTest, Dummy) {
-    // TODO
+
+// --- InputDispatcherTest ---
+
+class InputDispatcherTest : public testing::Test {
+protected:
+    sp<FakeInputDispatcherPolicy> mFakePolicy;
+    sp<InputDispatcher> mDispatcher;
+
+    virtual void SetUp() {
+        mFakePolicy = new FakeInputDispatcherPolicy();
+        mDispatcher = new InputDispatcher(mFakePolicy);
+    }
+
+    virtual void TearDown() {
+        mFakePolicy.clear();
+        mDispatcher.clear();
+    }
+};
+
+
+TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) {
+    KeyEvent event;
+
+    // Rejects undefined key actions.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+            /*action*/ -1, 0,
+            AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject key events with undefined action.";
+
+    // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+            AKEY_EVENT_ACTION_MULTIPLE, 0,
+            AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject key events with ACTION_MULTIPLE.";
+}
+
+TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
+    MotionEvent event;
+    int32_t pointerIds[MAX_POINTERS + 1];
+    PointerCoords pointerCoords[MAX_POINTERS + 1];
+    for (int i = 0; i <= MAX_POINTERS; i++) {
+        pointerIds[i] = i;
+    }
+
+    // Rejects undefined motion actions.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            /*action*/ -1, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with undefined action.";
+
+    // Rejects pointer down with invalid index.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer down index too large.";
+
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_POINTER_DOWN | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer down index too small.";
+
+    // Rejects pointer up with invalid index.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer up index too large.";
+
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_POINTER_UP | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer up index too small.";
+
+    // Rejects motion events with invalid number of pointers.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 0, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with 0 pointers.";
+
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ MAX_POINTERS + 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with more than MAX_POINTERS pointers.";
+
+    // Rejects motion events with invalid pointer ids.
+    pointerIds[0] = -1;
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer ids less than 0.";
+
+    pointerIds[0] = MAX_POINTER_ID + 1;
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer ids greater than MAX_POINTER_ID.";
+
+    // Rejects motion events with duplicate pointer ids.
+    pointerIds[0] = 1;
+    pointerIds[1] = 1;
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 2, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with duplicate pointer ids.";
 }
 
 } // namespace android
diff --git a/libs/ui/tests/InputReader_test.cpp b/libs/ui/tests/InputReader_test.cpp
new file mode 100644
index 0000000..c19147f
--- /dev/null
+++ b/libs/ui/tests/InputReader_test.cpp
@@ -0,0 +1,3381 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include <ui/InputReader.h>
+#include <utils/List.h>
+#include <gtest/gtest.h>
+#include <math.h>
+
+namespace android {
+
+// An arbitrary time value.
+static const nsecs_t ARBITRARY_TIME = 1234;
+
+// Arbitrary display properties.
+static const int32_t DISPLAY_ID = 0;
+static const int32_t DISPLAY_WIDTH = 480;
+static const int32_t DISPLAY_HEIGHT = 800;
+
+// Error tolerance for floating point assertions.
+static const float EPSILON = 0.001f;
+
+template<typename T>
+static inline T min(T a, T b) {
+    return a < b ? a : b;
+}
+
+static inline float avg(float x, float y) {
+    return (x + y) / 2;
+}
+
+
+// --- FakeInputReaderPolicy ---
+
+class FakeInputReaderPolicy : public InputReaderPolicyInterface {
+    struct DisplayInfo {
+        int32_t width;
+        int32_t height;
+        int32_t orientation;
+    };
+
+    KeyedVector<int32_t, DisplayInfo> mDisplayInfos;
+    bool mFilterTouchEvents;
+    bool mFilterJumpyTouchEvents;
+    KeyedVector<String8, Vector<VirtualKeyDefinition> > mVirtualKeyDefinitions;
+    KeyedVector<String8, InputDeviceCalibration> mInputDeviceCalibrations;
+    Vector<String8> mExcludedDeviceNames;
+
+protected:
+    virtual ~FakeInputReaderPolicy() { }
+
+public:
+    FakeInputReaderPolicy() :
+            mFilterTouchEvents(false), mFilterJumpyTouchEvents(false) {
+    }
+
+    void removeDisplayInfo(int32_t displayId) {
+        mDisplayInfos.removeItem(displayId);
+    }
+
+    void setDisplayInfo(int32_t displayId, int32_t width, int32_t height, int32_t orientation) {
+        removeDisplayInfo(displayId);
+
+        DisplayInfo info;
+        info.width = width;
+        info.height = height;
+        info.orientation = orientation;
+        mDisplayInfos.add(displayId, info);
+    }
+
+    void setFilterTouchEvents(bool enabled) {
+        mFilterTouchEvents = enabled;
+    }
+
+    void setFilterJumpyTouchEvents(bool enabled) {
+        mFilterJumpyTouchEvents = enabled;
+    }
+
+    void addInputDeviceCalibration(const String8& deviceName,
+            const InputDeviceCalibration& calibration) {
+        mInputDeviceCalibrations.add(deviceName, calibration);
+    }
+
+    void addInputDeviceCalibrationProperty(const String8& deviceName,
+            const String8& key, const String8& value) {
+        ssize_t index = mInputDeviceCalibrations.indexOfKey(deviceName);
+        if (index < 0) {
+            index = mInputDeviceCalibrations.add(deviceName, InputDeviceCalibration());
+        }
+        mInputDeviceCalibrations.editValueAt(index).addProperty(key, value);
+    }
+
+    void addVirtualKeyDefinition(const String8& deviceName,
+            const VirtualKeyDefinition& definition) {
+        if (mVirtualKeyDefinitions.indexOfKey(deviceName) < 0) {
+            mVirtualKeyDefinitions.add(deviceName, Vector<VirtualKeyDefinition>());
+        }
+
+        mVirtualKeyDefinitions.editValueFor(deviceName).push(definition);
+    }
+
+    void addExcludedDeviceName(const String8& deviceName) {
+        mExcludedDeviceNames.push(deviceName);
+    }
+
+private:
+    virtual bool getDisplayInfo(int32_t displayId,
+            int32_t* width, int32_t* height, int32_t* orientation) {
+        ssize_t index = mDisplayInfos.indexOfKey(displayId);
+        if (index >= 0) {
+            const DisplayInfo& info = mDisplayInfos.valueAt(index);
+            if (width) {
+                *width = info.width;
+            }
+            if (height) {
+                *height = info.height;
+            }
+            if (orientation) {
+                *orientation = info.orientation;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    virtual bool filterTouchEvents() {
+        return mFilterTouchEvents;
+    }
+
+    virtual bool filterJumpyTouchEvents() {
+        return mFilterJumpyTouchEvents;
+    }
+
+    virtual void getVirtualKeyDefinitions(const String8& deviceName,
+            Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) {
+        ssize_t index = mVirtualKeyDefinitions.indexOfKey(deviceName);
+        if (index >= 0) {
+            outVirtualKeyDefinitions.appendVector(mVirtualKeyDefinitions.valueAt(index));
+        }
+    }
+
+    virtual void getInputDeviceCalibration(const String8& deviceName,
+            InputDeviceCalibration& outCalibration) {
+        ssize_t index = mInputDeviceCalibrations.indexOfKey(deviceName);
+        if (index >= 0) {
+            outCalibration = mInputDeviceCalibrations.valueAt(index);
+        }
+    }
+
+    virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) {
+        outExcludedDeviceNames.appendVector(mExcludedDeviceNames);
+    }
+};
+
+
+// --- FakeInputDispatcher ---
+
+class FakeInputDispatcher : public InputDispatcherInterface {
+public:
+    struct NotifyConfigurationChangedArgs {
+        nsecs_t eventTime;
+    };
+
+    struct NotifyKeyArgs {
+        nsecs_t eventTime;
+        int32_t deviceId;
+        int32_t source;
+        uint32_t policyFlags;
+        int32_t action;
+        int32_t flags;
+        int32_t keyCode;
+        int32_t scanCode;
+        int32_t metaState;
+        nsecs_t downTime;
+    };
+
+    struct NotifyMotionArgs {
+        nsecs_t eventTime;
+        int32_t deviceId;
+        int32_t source;
+        uint32_t policyFlags;
+        int32_t action;
+        int32_t flags;
+        int32_t metaState;
+        int32_t edgeFlags;
+        uint32_t pointerCount;
+        Vector<int32_t> pointerIds;
+        Vector<PointerCoords> pointerCoords;
+        float xPrecision;
+        float yPrecision;
+        nsecs_t downTime;
+    };
+
+    struct NotifySwitchArgs {
+        nsecs_t when;
+        int32_t switchCode;
+        int32_t switchValue;
+        uint32_t policyFlags;
+    };
+
+private:
+    List<NotifyConfigurationChangedArgs> mNotifyConfigurationChangedArgs;
+    List<NotifyKeyArgs> mNotifyKeyArgs;
+    List<NotifyMotionArgs> mNotifyMotionArgs;
+    List<NotifySwitchArgs> mNotifySwitchArgs;
+
+protected:
+    virtual ~FakeInputDispatcher() { }
+
+public:
+    FakeInputDispatcher() {
+    }
+
+    void assertNotifyConfigurationChangedWasCalled(NotifyConfigurationChangedArgs* outArgs = NULL) {
+        ASSERT_FALSE(mNotifyConfigurationChangedArgs.empty())
+                << "Expected notifyConfigurationChanged() to have been called.";
+        if (outArgs) {
+            *outArgs = *mNotifyConfigurationChangedArgs.begin();
+        }
+        mNotifyConfigurationChangedArgs.erase(mNotifyConfigurationChangedArgs.begin());
+    }
+
+    void assertNotifyKeyWasCalled(NotifyKeyArgs* outArgs = NULL) {
+        ASSERT_FALSE(mNotifyKeyArgs.empty())
+                << "Expected notifyKey() to have been called.";
+        if (outArgs) {
+            *outArgs = *mNotifyKeyArgs.begin();
+        }
+        mNotifyKeyArgs.erase(mNotifyKeyArgs.begin());
+    }
+
+    void assertNotifyKeyWasNotCalled() {
+        ASSERT_TRUE(mNotifyKeyArgs.empty())
+                << "Expected notifyKey() to not have been called.";
+    }
+
+    void assertNotifyMotionWasCalled(NotifyMotionArgs* outArgs = NULL) {
+        ASSERT_FALSE(mNotifyMotionArgs.empty())
+                << "Expected notifyMotion() to have been called.";
+        if (outArgs) {
+            *outArgs = *mNotifyMotionArgs.begin();
+        }
+        mNotifyMotionArgs.erase(mNotifyMotionArgs.begin());
+    }
+
+    void assertNotifyMotionWasNotCalled() {
+        ASSERT_TRUE(mNotifyMotionArgs.empty())
+                << "Expected notifyMotion() to not have been called.";
+    }
+
+    void assertNotifySwitchWasCalled(NotifySwitchArgs* outArgs = NULL) {
+        ASSERT_FALSE(mNotifySwitchArgs.empty())
+                << "Expected notifySwitch() to have been called.";
+        if (outArgs) {
+            *outArgs = *mNotifySwitchArgs.begin();
+        }
+        mNotifySwitchArgs.erase(mNotifySwitchArgs.begin());
+    }
+
+private:
+    virtual void notifyConfigurationChanged(nsecs_t eventTime) {
+        NotifyConfigurationChangedArgs args;
+        args.eventTime = eventTime;
+        mNotifyConfigurationChangedArgs.push_back(args);
+    }
+
+    virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
+            uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+            int32_t scanCode, int32_t metaState, nsecs_t downTime) {
+        NotifyKeyArgs args;
+        args.eventTime = eventTime;
+        args.deviceId = deviceId;
+        args.source = source;
+        args.policyFlags = policyFlags;
+        args.action = action;
+        args.flags = flags;
+        args.keyCode = keyCode;
+        args.scanCode = scanCode;
+        args.metaState = metaState;
+        args.downTime = downTime;
+        mNotifyKeyArgs.push_back(args);
+    }
+
+    virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
+            uint32_t policyFlags, int32_t action, int32_t flags,
+            int32_t metaState, int32_t edgeFlags,
+            uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+            float xPrecision, float yPrecision, nsecs_t downTime) {
+        NotifyMotionArgs args;
+        args.eventTime = eventTime;
+        args.deviceId = deviceId;
+        args.source = source;
+        args.policyFlags = policyFlags;
+        args.action = action;
+        args.flags = flags;
+        args.metaState = metaState;
+        args.edgeFlags = edgeFlags;
+        args.pointerCount = pointerCount;
+        args.pointerIds.clear();
+        args.pointerIds.appendArray(pointerIds, pointerCount);
+        args.pointerCoords.clear();
+        args.pointerCoords.appendArray(pointerCoords, pointerCount);
+        args.xPrecision = xPrecision;
+        args.yPrecision = yPrecision;
+        args.downTime = downTime;
+        mNotifyMotionArgs.push_back(args);
+    }
+
+    virtual void notifySwitch(nsecs_t when,
+            int32_t switchCode, int32_t switchValue, uint32_t policyFlags) {
+        NotifySwitchArgs args;
+        args.when = when;
+        args.switchCode = switchCode;
+        args.switchValue = switchValue;
+        args.policyFlags = policyFlags;
+        mNotifySwitchArgs.push_back(args);
+    }
+
+    virtual void dump(String8& dump) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual void dispatchOnce() {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual int32_t injectInputEvent(const InputEvent* event,
+            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+        return INPUT_EVENT_INJECTION_FAILED;
+    }
+
+    virtual void setInputWindows(const Vector<InputWindow>& inputWindows) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual void setFocusedApplication(const InputApplication* inputApplication) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual void setInputDispatchMode(bool enabled, bool frozen) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+            const sp<InputChannel>& toChannel) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+        return 0;
+    }
+
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+        return 0;
+    }
+
+    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+        return 0;
+    }
+};
+
+
+// --- FakeEventHub ---
+
+class FakeEventHub : public EventHubInterface {
+    struct KeyInfo {
+        int32_t keyCode;
+        uint32_t flags;
+    };
+
+    struct Device {
+        String8 name;
+        uint32_t classes;
+        KeyedVector<int, RawAbsoluteAxisInfo> axes;
+        KeyedVector<int32_t, int32_t> keyCodeStates;
+        KeyedVector<int32_t, int32_t> scanCodeStates;
+        KeyedVector<int32_t, int32_t> switchStates;
+        KeyedVector<int32_t, KeyInfo> keys;
+
+        Device(const String8& name, uint32_t classes) :
+                name(name), classes(classes) {
+        }
+    };
+
+    KeyedVector<int32_t, Device*> mDevices;
+    Vector<String8> mExcludedDevices;
+    List<RawEvent> mEvents;
+
+protected:
+    virtual ~FakeEventHub() {
+        for (size_t i = 0; i < mDevices.size(); i++) {
+            delete mDevices.valueAt(i);
+        }
+    }
+
+public:
+    FakeEventHub() { }
+
+    void addDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+        Device* device = new Device(name, classes);
+        mDevices.add(deviceId, device);
+
+        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0, 0, 0);
+    }
+
+    void removeDevice(int32_t deviceId) {
+        delete mDevices.valueFor(deviceId);
+        mDevices.removeItem(deviceId);
+
+        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0, 0, 0);
+    }
+
+    void finishDeviceScan() {
+        enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0, 0, 0);
+    }
+
+    void addAxis(int32_t deviceId, int axis,
+            int32_t minValue, int32_t maxValue, int flat, int fuzz) {
+        Device* device = getDevice(deviceId);
+
+        RawAbsoluteAxisInfo info;
+        info.valid = true;
+        info.minValue = minValue;
+        info.maxValue = maxValue;
+        info.flat = flat;
+        info.fuzz = fuzz;
+        device->axes.add(axis, info);
+    }
+
+    void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) {
+        Device* device = getDevice(deviceId);
+        device->keyCodeStates.replaceValueFor(keyCode, state);
+    }
+
+    void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
+        Device* device = getDevice(deviceId);
+        device->scanCodeStates.replaceValueFor(scanCode, state);
+    }
+
+    void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) {
+        Device* device = getDevice(deviceId);
+        device->switchStates.replaceValueFor(switchCode, state);
+    }
+
+    void addKey(int32_t deviceId, int32_t scanCode, int32_t keyCode, uint32_t flags) {
+        Device* device = getDevice(deviceId);
+        KeyInfo info;
+        info.keyCode = keyCode;
+        info.flags = flags;
+        device->keys.add(scanCode, info);
+    }
+
+    Vector<String8>& getExcludedDevices() {
+        return mExcludedDevices;
+    }
+
+    void enqueueEvent(nsecs_t when, int32_t deviceId, int32_t type,
+            int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+        RawEvent event;
+        event.when = when;
+        event.deviceId = deviceId;
+        event.type = type;
+        event.scanCode = scanCode;
+        event.keyCode = keyCode;
+        event.value = value;
+        event.flags = flags;
+        mEvents.push_back(event);
+    }
+
+    void assertQueueIsEmpty() {
+        ASSERT_EQ(size_t(0), mEvents.size())
+                << "Expected the event queue to be empty (fully consumed).";
+    }
+
+private:
+    Device* getDevice(int32_t deviceId) const {
+        ssize_t index = mDevices.indexOfKey(deviceId);
+        return index >= 0 ? mDevices.valueAt(index) : NULL;
+    }
+
+    virtual uint32_t getDeviceClasses(int32_t deviceId) const {
+        Device* device = getDevice(deviceId);
+        return device ? device->classes : 0;
+    }
+
+    virtual String8 getDeviceName(int32_t deviceId) const {
+        Device* device = getDevice(deviceId);
+        return device ? device->name : String8("unknown");
+    }
+
+    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+            RawAbsoluteAxisInfo* outAxisInfo) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->axes.indexOfKey(axis);
+            if (index >= 0) {
+                *outAxisInfo = device->axes.valueAt(index);
+                return OK;
+            }
+        }
+        return -1;
+    }
+
+    virtual status_t scancodeToKeycode(int32_t deviceId, int scancode,
+            int32_t* outKeycode, uint32_t* outFlags) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->keys.indexOfKey(scancode);
+            if (index >= 0) {
+                if (outKeycode) {
+                    *outKeycode = device->keys.valueAt(index).keyCode;
+                }
+                if (outFlags) {
+                    *outFlags = device->keys.valueAt(index).flags;
+                }
+                return OK;
+            }
+        }
+        return NAME_NOT_FOUND;
+    }
+
+    virtual void addExcludedDevice(const char* deviceName) {
+        mExcludedDevices.add(String8(deviceName));
+    }
+
+    virtual bool getEvent(RawEvent* outEvent) {
+        if (mEvents.empty()) {
+            return false;
+        }
+
+        *outEvent = *mEvents.begin();
+        mEvents.erase(mEvents.begin());
+        return true;
+    }
+
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->scanCodeStates.indexOfKey(scanCode);
+            if (index >= 0) {
+                return device->scanCodeStates.valueAt(index);
+            }
+        }
+        return AKEY_STATE_UNKNOWN;
+    }
+
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->keyCodeStates.indexOfKey(keyCode);
+            if (index >= 0) {
+                return device->keyCodeStates.valueAt(index);
+            }
+        }
+        return AKEY_STATE_UNKNOWN;
+    }
+
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->switchStates.indexOfKey(sw);
+            if (index >= 0) {
+                return device->switchStates.valueAt(index);
+            }
+        }
+        return AKEY_STATE_UNKNOWN;
+    }
+
+    virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+            uint8_t* outFlags) const {
+        bool result = false;
+        Device* device = getDevice(deviceId);
+        if (device) {
+            for (size_t i = 0; i < numCodes; i++) {
+                for (size_t j = 0; j < device->keys.size(); j++) {
+                    if (keyCodes[i] == device->keys.valueAt(j).keyCode) {
+                        outFlags[i] = 1;
+                        result = true;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    virtual bool hasLed(int32_t deviceId, int32_t led) const {
+        return false;
+    }
+
+    virtual void setLedState(int32_t deviceId, int32_t led, bool on) {
+    }
+
+    virtual void dump(String8& dump) {
+    }
+};
+
+
+// --- FakeInputReaderContext ---
+
+class FakeInputReaderContext : public InputReaderContext {
+    sp<EventHubInterface> mEventHub;
+    sp<InputReaderPolicyInterface> mPolicy;
+    sp<InputDispatcherInterface> mDispatcher;
+    int32_t mGlobalMetaState;
+    bool mUpdateGlobalMetaStateWasCalled;
+
+public:
+    FakeInputReaderContext(const sp<EventHubInterface>& eventHub,
+            const sp<InputReaderPolicyInterface>& policy,
+            const sp<InputDispatcherInterface>& dispatcher) :
+            mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher),
+            mGlobalMetaState(0) {
+    }
+
+    virtual ~FakeInputReaderContext() { }
+
+    void assertUpdateGlobalMetaStateWasCalled() {
+        ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled)
+                << "Expected updateGlobalMetaState() to have been called.";
+        mUpdateGlobalMetaStateWasCalled = false;
+    }
+
+    void setGlobalMetaState(int32_t state) {
+        mGlobalMetaState = state;
+    }
+
+private:
+    virtual void updateGlobalMetaState() {
+        mUpdateGlobalMetaStateWasCalled = true;
+    }
+
+    virtual int32_t getGlobalMetaState() {
+        return mGlobalMetaState;
+    }
+
+    virtual EventHubInterface* getEventHub() {
+        return mEventHub.get();
+    }
+
+    virtual InputReaderPolicyInterface* getPolicy() {
+        return mPolicy.get();
+    }
+
+    virtual InputDispatcherInterface* getDispatcher() {
+        return mDispatcher.get();
+    }
+};
+
+
+// --- FakeInputMapper ---
+
+class FakeInputMapper : public InputMapper {
+    uint32_t mSources;
+    int32_t mKeyboardType;
+    int32_t mMetaState;
+    KeyedVector<int32_t, int32_t> mKeyCodeStates;
+    KeyedVector<int32_t, int32_t> mScanCodeStates;
+    KeyedVector<int32_t, int32_t> mSwitchStates;
+    Vector<int32_t> mSupportedKeyCodes;
+    RawEvent mLastEvent;
+
+    bool mConfigureWasCalled;
+    bool mResetWasCalled;
+    bool mProcessWasCalled;
+
+public:
+    FakeInputMapper(InputDevice* device, uint32_t sources) :
+            InputMapper(device),
+            mSources(sources), mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE),
+            mMetaState(0),
+            mConfigureWasCalled(false), mResetWasCalled(false), mProcessWasCalled(false) {
+    }
+
+    virtual ~FakeInputMapper() { }
+
+    void setKeyboardType(int32_t keyboardType) {
+        mKeyboardType = keyboardType;
+    }
+
+    void setMetaState(int32_t metaState) {
+        mMetaState = metaState;
+    }
+
+    void assertConfigureWasCalled() {
+        ASSERT_TRUE(mConfigureWasCalled)
+                << "Expected configure() to have been called.";
+        mConfigureWasCalled = false;
+    }
+
+    void assertResetWasCalled() {
+        ASSERT_TRUE(mResetWasCalled)
+                << "Expected reset() to have been called.";
+        mResetWasCalled = false;
+    }
+
+    void assertProcessWasCalled(RawEvent* outLastEvent = NULL) {
+        ASSERT_TRUE(mProcessWasCalled)
+                << "Expected process() to have been called.";
+        if (outLastEvent) {
+            *outLastEvent = mLastEvent;
+        }
+        mProcessWasCalled = false;
+    }
+
+    void setKeyCodeState(int32_t keyCode, int32_t state) {
+        mKeyCodeStates.replaceValueFor(keyCode, state);
+    }
+
+    void setScanCodeState(int32_t scanCode, int32_t state) {
+        mScanCodeStates.replaceValueFor(scanCode, state);
+    }
+
+    void setSwitchState(int32_t switchCode, int32_t state) {
+        mSwitchStates.replaceValueFor(switchCode, state);
+    }
+
+    void addSupportedKeyCode(int32_t keyCode) {
+        mSupportedKeyCodes.add(keyCode);
+    }
+
+private:
+    virtual uint32_t getSources() {
+        return mSources;
+    }
+
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) {
+        InputMapper::populateDeviceInfo(deviceInfo);
+
+        if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) {
+            deviceInfo->setKeyboardType(mKeyboardType);
+        }
+    }
+
+    virtual void configure() {
+        mConfigureWasCalled = true;
+    }
+
+    virtual void reset() {
+        mResetWasCalled = true;
+    }
+
+    virtual void process(const RawEvent* rawEvent) {
+        mLastEvent = *rawEvent;
+        mProcessWasCalled = true;
+    }
+
+    virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+        ssize_t index = mKeyCodeStates.indexOfKey(keyCode);
+        return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+    }
+
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+        ssize_t index = mScanCodeStates.indexOfKey(scanCode);
+        return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+    }
+
+    virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+        ssize_t index = mSwitchStates.indexOfKey(switchCode);
+        return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+    }
+
+    virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags) {
+        bool result = false;
+        for (size_t i = 0; i < numCodes; i++) {
+            for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) {
+                if (keyCodes[i] == mSupportedKeyCodes[j]) {
+                    outFlags[i] = 1;
+                    result = true;
+                }
+            }
+        }
+        return result;
+    }
+
+    virtual int32_t getMetaState() {
+        return mMetaState;
+    }
+};
+
+
+// --- InstrumentedInputReader ---
+
+class InstrumentedInputReader : public InputReader {
+    InputDevice* mNextDevice;
+
+public:
+    InstrumentedInputReader(const sp<EventHubInterface>& eventHub,
+            const sp<InputReaderPolicyInterface>& policy,
+            const sp<InputDispatcherInterface>& dispatcher) :
+            InputReader(eventHub, policy, dispatcher) {
+    }
+
+    virtual ~InstrumentedInputReader() {
+        if (mNextDevice) {
+            delete mNextDevice;
+        }
+    }
+
+    void setNextDevice(InputDevice* device) {
+        mNextDevice = device;
+    }
+
+protected:
+    virtual InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+        if (mNextDevice) {
+            InputDevice* device = mNextDevice;
+            mNextDevice = NULL;
+            return device;
+        }
+        return InputReader::createDevice(deviceId, name, classes);
+    }
+
+    friend class InputReaderTest;
+};
+
+
+// --- InputReaderTest ---
+
+class InputReaderTest : public testing::Test {
+protected:
+    sp<FakeInputDispatcher> mFakeDispatcher;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    sp<FakeEventHub> mFakeEventHub;
+    sp<InstrumentedInputReader> mReader;
+
+    virtual void SetUp() {
+        mFakeEventHub = new FakeEventHub();
+        mFakePolicy = new FakeInputReaderPolicy();
+        mFakeDispatcher = new FakeInputDispatcher();
+
+        mReader = new InstrumentedInputReader(mFakeEventHub, mFakePolicy, mFakeDispatcher);
+    }
+
+    virtual void TearDown() {
+        mReader.clear();
+
+        mFakeDispatcher.clear();
+        mFakePolicy.clear();
+        mFakeEventHub.clear();
+    }
+
+    void addDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+        mFakeEventHub->addDevice(deviceId, name, classes);
+        mFakeEventHub->finishDeviceScan();
+        mReader->loopOnce();
+        mReader->loopOnce();
+        mFakeEventHub->assertQueueIsEmpty();
+    }
+
+    FakeInputMapper* addDeviceWithFakeInputMapper(int32_t deviceId,
+            const String8& name, uint32_t classes, uint32_t sources) {
+        InputDevice* device = new InputDevice(mReader.get(), deviceId, name);
+        FakeInputMapper* mapper = new FakeInputMapper(device, sources);
+        device->addMapper(mapper);
+        mReader->setNextDevice(device);
+        addDevice(deviceId, name, classes);
+        return mapper;
+    }
+};
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenNoDevices_ReturnsDefaults) {
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenAlphabeticKeyboardPresent_ReturnsQwertyKeyboard) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("keyboard"),
+            INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY));
+
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_QWERTY, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenTouchScreenPresent_ReturnsFingerTouchScreen) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("touchscreen"),
+            INPUT_DEVICE_CLASS_TOUCHSCREEN));
+
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_FINGER, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenTrackballPresent_ReturnsTrackballNavigation) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("trackball"),
+            INPUT_DEVICE_CLASS_TRACKBALL));
+
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_TRACKBALL, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenDPadPresent_ReturnsDPadNavigation) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("dpad"),
+            INPUT_DEVICE_CLASS_DPAD));
+
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_DPAD, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsValid) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
+            INPUT_DEVICE_CLASS_KEYBOARD));
+
+    InputDeviceInfo info;
+    status_t result = mReader->getInputDeviceInfo(1, &info);
+
+    ASSERT_EQ(OK, result);
+    ASSERT_EQ(1, info.getId());
+    ASSERT_STREQ("keyboard", info.getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, info.getKeyboardType());
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, info.getSources());
+    ASSERT_EQ(size_t(0), info.getMotionRanges().size());
+}
+
+TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsInvalid) {
+    InputDeviceInfo info;
+    status_t result = mReader->getInputDeviceInfo(-1, &info);
+
+    ASSERT_EQ(NAME_NOT_FOUND, result);
+}
+
+TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsIgnored) {
+    addDevice(1, String8("ignored"), 0); // no classes so device will be ignored
+
+    InputDeviceInfo info;
+    status_t result = mReader->getInputDeviceInfo(1, &info);
+
+    ASSERT_EQ(NAME_NOT_FOUND, result);
+}
+
+TEST_F(InputReaderTest, GetInputDeviceIds) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
+            INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY));
+    ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("trackball"),
+            INPUT_DEVICE_CLASS_TRACKBALL));
+
+    Vector<int32_t> ids;
+    mReader->getInputDeviceIds(ids);
+
+    ASSERT_EQ(size_t(2), ids.size());
+    ASSERT_EQ(1, ids[0]);
+    ASSERT_EQ(2, ids[1]);
+}
+
+TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD));
+    mapper->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(0,
+            AINPUT_SOURCE_ANY, AKEYCODE_A))
+            << "Should return unknown when the device id is >= 0 but unknown.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(1,
+            AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(-1,
+            AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(-1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD));
+    mapper->setScanCodeState(KEY_A, AKEY_STATE_DOWN);
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(0,
+            AINPUT_SOURCE_ANY, KEY_A))
+            << "Should return unknown when the device id is >= 0 but unknown.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(1,
+            AINPUT_SOURCE_TRACKBALL, KEY_A))
+            << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A))
+            << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(-1,
+            AINPUT_SOURCE_TRACKBALL, KEY_A))
+            << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(-1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A))
+            << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, GetSwitchState_ForwardsRequestsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD));
+    mapper->setSwitchState(SW_LID, AKEY_STATE_DOWN);
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(0,
+            AINPUT_SOURCE_ANY, SW_LID))
+            << "Should return unknown when the device id is >= 0 but unknown.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(1,
+            AINPUT_SOURCE_TRACKBALL, SW_LID))
+            << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID))
+            << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(-1,
+            AINPUT_SOURCE_TRACKBALL, SW_LID))
+            << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(-1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID))
+            << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD));
+    mapper->addSupportedKeyCode(AKEYCODE_A);
+    mapper->addSupportedKeyCode(AKEYCODE_B);
+
+    const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+    uint8_t flags[4] = { 0, 0, 0, 1 };
+
+    ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, 4, keyCodes, flags))
+            << "Should return false when device id is >= 0 but unknown.";
+    ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+    flags[3] = 1;
+    ASSERT_FALSE(mReader->hasKeys(1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should return false when device id is valid but the sources are not supported by the device.";
+    ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+    flags[3] = 1;
+    ASSERT_TRUE(mReader->hasKeys(1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+    ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
+
+    flags[3] = 1;
+    ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should return false when the device id is < 0 but the sources are not supported by any device.";
+    ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+    flags[3] = 1;
+    ASSERT_TRUE(mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+    ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
+}
+
+TEST_F(InputReaderTest, LoopOnce_WhenDeviceScanFinished_SendsConfigurationChanged) {
+    addDevice(1, String8("ignored"), INPUT_DEVICE_CLASS_KEYBOARD);
+
+    FakeInputDispatcher::NotifyConfigurationChangedArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyConfigurationChangedWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+}
+
+TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD));
+
+    mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, AKEYCODE_A, 1, POLICY_FLAG_WAKE);
+    mReader->loopOnce();
+    ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
+
+    RawEvent event;
+    ASSERT_NO_FATAL_FAILURE(mapper->assertProcessWasCalled(&event));
+    ASSERT_EQ(0, event.when);
+    ASSERT_EQ(1, event.deviceId);
+    ASSERT_EQ(EV_KEY, event.type);
+    ASSERT_EQ(KEY_A, event.scanCode);
+    ASSERT_EQ(AKEYCODE_A, event.keyCode);
+    ASSERT_EQ(1, event.value);
+    ASSERT_EQ(POLICY_FLAG_WAKE, event.flags);
+}
+
+
+// --- InputDeviceTest ---
+
+class InputDeviceTest : public testing::Test {
+protected:
+    static const char* DEVICE_NAME;
+    static const int32_t DEVICE_ID;
+
+    sp<FakeEventHub> mFakeEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    sp<FakeInputDispatcher> mFakeDispatcher;
+    FakeInputReaderContext* mFakeContext;
+
+    InputDevice* mDevice;
+
+    virtual void SetUp() {
+        mFakeEventHub = new FakeEventHub();
+        mFakePolicy = new FakeInputReaderPolicy();
+        mFakeDispatcher = new FakeInputDispatcher();
+        mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeDispatcher);
+
+        mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME));
+    }
+
+    virtual void TearDown() {
+        delete mDevice;
+
+        delete mFakeContext;
+        mFakeDispatcher.clear();
+        mFakePolicy.clear();
+        mFakeEventHub.clear();
+    }
+};
+
+const char* InputDeviceTest::DEVICE_NAME = "device";
+const int32_t InputDeviceTest::DEVICE_ID = 1;
+
+TEST_F(InputDeviceTest, ImmutableProperties) {
+    ASSERT_EQ(DEVICE_ID, mDevice->getId());
+    ASSERT_STREQ(DEVICE_NAME, mDevice->getName());
+}
+
+TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
+    // Configuration.
+    mDevice->configure();
+
+    // Metadata.
+    ASSERT_TRUE(mDevice->isIgnored());
+    ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mDevice->getSources());
+
+    InputDeviceInfo info;
+    mDevice->getDeviceInfo(&info);
+    ASSERT_EQ(DEVICE_ID, info.getId());
+    ASSERT_STREQ(DEVICE_NAME, info.getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NONE, info.getKeyboardType());
+    ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, info.getSources());
+
+    // State queries.
+    ASSERT_EQ(0, mDevice->getMetaState());
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, 0))
+            << "Ignored device should return unknown key code state.";
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 0))
+            << "Ignored device should return unknown scan code state.";
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 0))
+            << "Ignored device should return unknown switch state.";
+
+    const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
+    uint8_t flags[2] = { 0, 1 };
+    ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 2, keyCodes, flags))
+            << "Ignored device should never mark any key codes.";
+    ASSERT_EQ(0, flags[0]) << "Flag for unsupported key should be unchanged.";
+    ASSERT_EQ(1, flags[1]) << "Flag for unsupported key should be unchanged.";
+
+    // Reset.
+    mDevice->reset();
+}
+
+TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRequestsToMappers) {
+    // Configuration.
+    InputDeviceCalibration calibration;
+    calibration.addProperty(String8("key"), String8("value"));
+    mFakePolicy->addInputDeviceCalibration(String8(DEVICE_NAME), calibration);
+
+    FakeInputMapper* mapper1 = new FakeInputMapper(mDevice, AINPUT_SOURCE_KEYBOARD);
+    mapper1->setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    mapper1->setMetaState(AMETA_ALT_ON);
+    mapper1->addSupportedKeyCode(AKEYCODE_A);
+    mapper1->addSupportedKeyCode(AKEYCODE_B);
+    mapper1->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
+    mapper1->setKeyCodeState(AKEYCODE_B, AKEY_STATE_UP);
+    mapper1->setScanCodeState(2, AKEY_STATE_DOWN);
+    mapper1->setScanCodeState(3, AKEY_STATE_UP);
+    mapper1->setSwitchState(4, AKEY_STATE_DOWN);
+    mDevice->addMapper(mapper1);
+
+    FakeInputMapper* mapper2 = new FakeInputMapper(mDevice, AINPUT_SOURCE_TOUCHSCREEN);
+    mapper2->setMetaState(AMETA_SHIFT_ON);
+    mDevice->addMapper(mapper2);
+
+    mDevice->configure();
+
+    String8 propertyValue;
+    ASSERT_TRUE(mDevice->getCalibration().tryGetProperty(String8("key"), propertyValue))
+            << "Device should have read calibration during configuration phase.";
+    ASSERT_STREQ("value", propertyValue.string());
+
+    ASSERT_NO_FATAL_FAILURE(mapper1->assertConfigureWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mapper2->assertConfigureWasCalled());
+
+    // Metadata.
+    ASSERT_FALSE(mDevice->isIgnored());
+    ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), mDevice->getSources());
+
+    InputDeviceInfo info;
+    mDevice->getDeviceInfo(&info);
+    ASSERT_EQ(DEVICE_ID, info.getId());
+    ASSERT_STREQ(DEVICE_NAME, info.getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_ALPHABETIC, info.getKeyboardType());
+    ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), info.getSources());
+
+    // State queries.
+    ASSERT_EQ(AMETA_ALT_ON | AMETA_SHIFT_ON, mDevice->getMetaState())
+            << "Should query mappers and combine meta states.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown key code state when source not supported.";
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown scan code state when source not supported.";
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown switch state when source not supported.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, AKEYCODE_A))
+            << "Should query mapper when source is supported.";
+    ASSERT_EQ(AKEY_STATE_UP, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 3))
+            << "Should query mapper when source is supported.";
+    ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 4))
+            << "Should query mapper when source is supported.";
+
+    const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+    uint8_t flags[4] = { 0, 0, 0, 1 };
+    ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should do nothing when source is unsupported.";
+    ASSERT_EQ(0, flags[0]) << "Flag should be unchanged when source is unsupported.";
+    ASSERT_EQ(0, flags[1]) << "Flag should be unchanged when source is unsupported.";
+    ASSERT_EQ(0, flags[2]) << "Flag should be unchanged when source is unsupported.";
+    ASSERT_EQ(1, flags[3]) << "Flag should be unchanged when source is unsupported.";
+
+    ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 4, keyCodes, flags))
+            << "Should query mapper when source is supported.";
+    ASSERT_EQ(1, flags[0]) << "Flag for supported key should be set.";
+    ASSERT_EQ(1, flags[1]) << "Flag for supported key should be set.";
+    ASSERT_EQ(0, flags[2]) << "Flag for unsupported key should be unchanged.";
+    ASSERT_EQ(1, flags[3]) << "Flag for unsupported key should be unchanged.";
+
+    // Event handling.
+    RawEvent event;
+    mDevice->process(&event);
+
+    ASSERT_NO_FATAL_FAILURE(mapper1->assertProcessWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mapper2->assertProcessWasCalled());
+
+    // Reset.
+    mDevice->reset();
+
+    ASSERT_NO_FATAL_FAILURE(mapper1->assertResetWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mapper2->assertResetWasCalled());
+}
+
+
+// --- InputMapperTest ---
+
+class InputMapperTest : public testing::Test {
+protected:
+    static const char* DEVICE_NAME;
+    static const int32_t DEVICE_ID;
+
+    sp<FakeEventHub> mFakeEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    sp<FakeInputDispatcher> mFakeDispatcher;
+    FakeInputReaderContext* mFakeContext;
+    InputDevice* mDevice;
+
+    virtual void SetUp() {
+        mFakeEventHub = new FakeEventHub();
+        mFakePolicy = new FakeInputReaderPolicy();
+        mFakeDispatcher = new FakeInputDispatcher();
+        mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeDispatcher);
+        mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME));
+
+        mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
+    }
+
+    virtual void TearDown() {
+        delete mDevice;
+        delete mFakeContext;
+        mFakeDispatcher.clear();
+        mFakePolicy.clear();
+        mFakeEventHub.clear();
+    }
+
+    void prepareCalibration(const char* key, const char* value) {
+        mFakePolicy->addInputDeviceCalibrationProperty(String8(DEVICE_NAME),
+                String8(key), String8(value));
+    }
+
+    void addMapperAndConfigure(InputMapper* mapper) {
+        mDevice->addMapper(mapper);
+        mDevice->configure();
+    }
+
+    static void process(InputMapper* mapper, nsecs_t when, int32_t deviceId, int32_t type,
+            int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+        RawEvent event;
+        event.when = when;
+        event.deviceId = deviceId;
+        event.type = type;
+        event.scanCode = scanCode;
+        event.keyCode = keyCode;
+        event.value = value;
+        event.flags = flags;
+        mapper->process(&event);
+    }
+
+    static void assertMotionRange(const InputDeviceInfo& info,
+            int32_t rangeType, float min, float max, float flat, float fuzz) {
+        const InputDeviceInfo::MotionRange* range = info.getMotionRange(rangeType);
+        ASSERT_TRUE(range != NULL) << "Range: " << rangeType;
+        ASSERT_NEAR(min, range->min, EPSILON) << "Range: " << rangeType;
+        ASSERT_NEAR(max, range->max, EPSILON) << "Range: " << rangeType;
+        ASSERT_NEAR(flat, range->flat, EPSILON) << "Range: " << rangeType;
+        ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Range: " << rangeType;
+    }
+
+    static void assertPointerCoords(const PointerCoords& coords,
+            float x, float y, float pressure, float size,
+            float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+            float orientation) {
+        ASSERT_NEAR(x, coords.x, 1);
+        ASSERT_NEAR(y, coords.y, 1);
+        ASSERT_NEAR(pressure, coords.pressure, EPSILON);
+        ASSERT_NEAR(size, coords.size, EPSILON);
+        ASSERT_NEAR(touchMajor, coords.touchMajor, 1);
+        ASSERT_NEAR(touchMinor, coords.touchMinor, 1);
+        ASSERT_NEAR(toolMajor, coords.toolMajor, 1);
+        ASSERT_NEAR(toolMinor, coords.toolMinor, 1);
+        ASSERT_NEAR(orientation, coords.orientation, EPSILON);
+    }
+};
+
+const char* InputMapperTest::DEVICE_NAME = "device";
+const int32_t InputMapperTest::DEVICE_ID = 1;
+
+
+// --- SwitchInputMapperTest ---
+
+class SwitchInputMapperTest : public InputMapperTest {
+protected:
+};
+
+TEST_F(SwitchInputMapperTest, GetSources) {
+    SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(uint32_t(0), mapper->getSources());
+}
+
+TEST_F(SwitchInputMapperTest, GetSwitchState) {
+    SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+    addMapperAndConfigure(mapper);
+
+    mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 1);
+    ASSERT_EQ(1, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+
+    mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 0);
+    ASSERT_EQ(0, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+}
+
+TEST_F(SwitchInputMapperTest, Process) {
+    SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+    addMapperAndConfigure(mapper);
+
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_LID, 0, 1, 0);
+
+    FakeInputDispatcher::NotifySwitchArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifySwitchWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.when);
+    ASSERT_EQ(SW_LID, args.switchCode);
+    ASSERT_EQ(1, args.switchValue);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+}
+
+
+// --- KeyboardInputMapperTest ---
+
+class KeyboardInputMapperTest : public InputMapperTest {
+protected:
+    void testDPadKeyRotation(KeyboardInputMapper* mapper,
+            int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode);
+};
+
+void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper* mapper,
+        int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode) {
+    FakeInputDispatcher::NotifyKeyArgs args;
+
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(originalScanCode, args.scanCode);
+    ASSERT_EQ(rotatedKeyCode, args.keyCode);
+
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(originalScanCode, args.scanCode);
+    ASSERT_EQ(rotatedKeyCode, args.keyCode);
+}
+
+
+TEST_F(KeyboardInputMapperTest, GetSources) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper->getSources());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    // Key down.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE);
+    FakeInputDispatcher::NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key up.
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+            EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(KeyboardInputMapperTest, Reset_WhenKeysAreNotDown_DoesNotSynthesizeKeyUp) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    // Key down.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Key up.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Reset.  Since no keys still down, should not synthesize any key ups.
+    mapper->reset();
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Reset_WhenKeysAreDown_SynthesizesKeyUps) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    // Metakey down.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Key down.
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+            EV_KEY, KEY_A, AKEYCODE_A, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Reset.  Since two keys are still down, should synthesize two key ups in reverse order.
+    mapper->reset();
+
+    FakeInputDispatcher::NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_A, args.keyCode);
+    ASSERT_EQ(KEY_A, args.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.downTime);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_SHIFT_LEFT, args.keyCode);
+    ASSERT_EQ(KEY_LEFTSHIFT, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.downTime);
+
+    // And that's it.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    // Initial metastate.
+    ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+
+    // Metakey down.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0);
+    FakeInputDispatcher::NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+    ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled());
+
+    // Key down.
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+            EV_KEY, KEY_A, AKEYCODE_A, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+
+    // Key up.
+    process(mapper, ARBITRARY_TIME + 2, DEVICE_ID,
+            EV_KEY, KEY_A, AKEYCODE_A, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+
+    // Metakey up.
+    process(mapper, ARBITRARY_TIME + 3, DEVICE_ID,
+            EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+    ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_WhenNotAttachedToDisplay_ShouldNotRotateDPad) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+}
+
+TEST_F(KeyboardInputMapperTest, Process_WhenAttachedToDisplay_ShouldRotateDPad) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, DISPLAY_ID,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_0);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_90);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_180);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_LEFT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_RIGHT));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_270);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_LEFT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_UP));
+
+    // Special case: if orientation changes while key is down, we still emit the same keycode
+    // in the key up as we did in the key down.
+    FakeInputDispatcher::NotifyKeyArgs args;
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_270);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(KEY_UP, args.scanCode);
+    ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_180);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(KEY_UP, args.scanCode);
+    ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+}
+
+TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 1);
+    ASSERT_EQ(1, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+
+    mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 0);
+    ASSERT_EQ(0, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+}
+
+TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 1);
+    ASSERT_EQ(1, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+
+    mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 0);
+    ASSERT_EQ(0, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+}
+
+TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    mFakeEventHub->addKey(DEVICE_ID, KEY_A, AKEYCODE_A, 0);
+
+    const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
+    uint8_t flags[2] = { 0, 0 };
+    ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 1, keyCodes, flags));
+    ASSERT_TRUE(flags[0]);
+    ASSERT_FALSE(flags[1]);
+}
+
+
+// --- TrackballInputMapperTest ---
+
+class TrackballInputMapperTest : public InputMapperTest {
+protected:
+    static const int32_t TRACKBALL_MOVEMENT_THRESHOLD;
+
+    void testMotionRotation(TrackballInputMapper* mapper,
+            int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY);
+};
+
+const int32_t TrackballInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+void TrackballInputMapperTest::testMotionRotation(TrackballInputMapper* mapper,
+        int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY) {
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, originalX, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, originalY, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
+            float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD,
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(TrackballInputMapperTest, GetSources) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, -1);
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper->getSources());
+}
+
+TEST_F(TrackballInputMapperTest, PopulateDeviceInfo) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, -1);
+    addMapperAndConfigure(mapper);
+
+    InputDeviceInfo info;
+    mapper->populateDeviceInfo(&info);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_X,
+            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_Y,
+            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+}
+
+TEST_F(TrackballInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, -1);
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Button press.
+    // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(0, args.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(0, args.edgeFlags);
+    ASSERT_EQ(uint32_t(1), args.pointerCount);
+    ASSERT_EQ(0, args.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
+    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Button release.  Should have same down time.
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(0, args.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(0, args.edgeFlags);
+    ASSERT_EQ(uint32_t(1), args.pointerCount);
+    ASSERT_EQ(0, args.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
+    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(TrackballInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, -1);
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Motion in X but not Y.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+    // Motion in Y but not X.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_NEAR(0.0f, args.pointerCoords[0].x, EPSILON);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(TrackballInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, -1);
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Button press without following sync.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+    // Button release without following sync.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(TrackballInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, -1);
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Combined X, Y and Button.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            1.0f / TRACKBALL_MOVEMENT_THRESHOLD, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+            1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+    // Move X, Y a bit while pressed.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 2, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+            1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+    // Release Button.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(TrackballInputMapperTest, Reset_WhenButtonIsNotDown_ShouldNotSynthesizeButtonUp) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, -1);
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Button press.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+
+    // Button release.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+
+    // Reset.  Should not synthesize button up since button is not pressed.
+    mapper->reset();
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(TrackballInputMapperTest, Reset_WhenButtonIsDown_ShouldSynthesizeButtonUp) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, -1);
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Button press.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+
+    // Reset.  Should synthesize button up.
+    mapper->reset();
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(TrackballInputMapperTest, Process_WhenNotAttachedToDisplay_ShouldNotRotateMotions) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, -1);
+    addMapperAndConfigure(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
+}
+
+TEST_F(TrackballInputMapperTest, Process_WhenAttachedToDisplay_ShouldRotateMotions) {
+    TrackballInputMapper* mapper = new TrackballInputMapper(mDevice, DISPLAY_ID);
+    addMapperAndConfigure(mapper);
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_0);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_90);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1,  1));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_180);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1, -1));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            InputReaderPolicyInterface::ROTATION_270);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1, -1));
+}
+
+
+// --- TouchInputMapperTest ---
+
+class TouchInputMapperTest : public InputMapperTest {
+protected:
+    static const int32_t RAW_X_MIN;
+    static const int32_t RAW_X_MAX;
+    static const int32_t RAW_Y_MIN;
+    static const int32_t RAW_Y_MAX;
+    static const int32_t RAW_TOUCH_MIN;
+    static const int32_t RAW_TOUCH_MAX;
+    static const int32_t RAW_TOOL_MIN;
+    static const int32_t RAW_TOOL_MAX;
+    static const int32_t RAW_PRESSURE_MIN;
+    static const int32_t RAW_PRESSURE_MAX;
+    static const int32_t RAW_ORIENTATION_MIN;
+    static const int32_t RAW_ORIENTATION_MAX;
+    static const int32_t RAW_ID_MIN;
+    static const int32_t RAW_ID_MAX;
+    static const float X_PRECISION;
+    static const float Y_PRECISION;
+
+    static const VirtualKeyDefinition VIRTUAL_KEYS[2];
+
+    enum Axes {
+        POSITION = 1 << 0,
+        TOUCH = 1 << 1,
+        TOOL = 1 << 2,
+        PRESSURE = 1 << 3,
+        ORIENTATION = 1 << 4,
+        MINOR = 1 << 5,
+        ID = 1 << 6,
+    };
+
+    void prepareDisplay(int32_t orientation);
+    void prepareVirtualKeys();
+    int32_t toRawX(float displayX);
+    int32_t toRawY(float displayY);
+    float toDisplayX(int32_t rawX);
+    float toDisplayY(int32_t rawY);
+};
+
+const int32_t TouchInputMapperTest::RAW_X_MIN = 25;
+const int32_t TouchInputMapperTest::RAW_X_MAX = 1020;
+const int32_t TouchInputMapperTest::RAW_Y_MIN = 30;
+const int32_t TouchInputMapperTest::RAW_Y_MAX = 1010;
+const int32_t TouchInputMapperTest::RAW_TOUCH_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_TOUCH_MAX = 31;
+const int32_t TouchInputMapperTest::RAW_TOOL_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_TOOL_MAX = 15;
+const int32_t TouchInputMapperTest::RAW_PRESSURE_MIN = RAW_TOUCH_MIN;
+const int32_t TouchInputMapperTest::RAW_PRESSURE_MAX = RAW_TOUCH_MAX;
+const int32_t TouchInputMapperTest::RAW_ORIENTATION_MIN = -7;
+const int32_t TouchInputMapperTest::RAW_ORIENTATION_MAX = 7;
+const int32_t TouchInputMapperTest::RAW_ID_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_ID_MAX = 9;
+const float TouchInputMapperTest::X_PRECISION = float(RAW_X_MAX - RAW_X_MIN) / DISPLAY_WIDTH;
+const float TouchInputMapperTest::Y_PRECISION = float(RAW_Y_MAX - RAW_Y_MIN) / DISPLAY_HEIGHT;
+
+const VirtualKeyDefinition TouchInputMapperTest::VIRTUAL_KEYS[2] = {
+        { KEY_HOME, 60, DISPLAY_HEIGHT + 15, 20, 20 },
+        { KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 },
+};
+
+void TouchInputMapperTest::prepareDisplay(int32_t orientation) {
+    mFakePolicy->setDisplayInfo(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation);
+}
+
+void TouchInputMapperTest::prepareVirtualKeys() {
+    mFakePolicy->addVirtualKeyDefinition(String8(DEVICE_NAME), VIRTUAL_KEYS[0]);
+    mFakePolicy->addVirtualKeyDefinition(String8(DEVICE_NAME), VIRTUAL_KEYS[1]);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, AKEYCODE_MENU, POLICY_FLAG_WAKE);
+}
+
+int32_t TouchInputMapperTest::toRawX(float displayX) {
+    return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN) / DISPLAY_WIDTH + RAW_X_MIN);
+}
+
+int32_t TouchInputMapperTest::toRawY(float displayY) {
+    return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN) / DISPLAY_HEIGHT + RAW_Y_MIN);
+}
+
+float TouchInputMapperTest::toDisplayX(int32_t rawX) {
+    return float(rawX - RAW_X_MIN) * DISPLAY_WIDTH / (RAW_X_MAX - RAW_X_MIN);
+}
+
+float TouchInputMapperTest::toDisplayY(int32_t rawY) {
+    return float(rawY - RAW_Y_MIN) * DISPLAY_HEIGHT / (RAW_Y_MAX - RAW_Y_MIN);
+}
+
+
+// --- SingleTouchInputMapperTest ---
+
+class SingleTouchInputMapperTest : public TouchInputMapperTest {
+protected:
+    void prepareAxes(int axes);
+
+    void processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y);
+    void processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y);
+    void processUp(SingleTouchInputMapper* mappery);
+    void processPressure(SingleTouchInputMapper* mapper, int32_t pressure);
+    void processToolMajor(SingleTouchInputMapper* mapper, int32_t toolMajor);
+    void processSync(SingleTouchInputMapper* mapper);
+};
+
+void SingleTouchInputMapperTest::prepareAxes(int axes) {
+    if (axes & POSITION) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+    }
+    if (axes & PRESSURE) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_PRESSURE, RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0);
+    }
+    if (axes & TOOL) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_TOOL_WIDTH, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0);
+    }
+}
+
+void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+}
+
+void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+}
+
+void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper* mapper) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 0, 0);
+}
+
+void SingleTouchInputMapperTest::processPressure(
+        SingleTouchInputMapper* mapper, int32_t pressure) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, 0, pressure, 0);
+}
+
+void SingleTouchInputMapperTest::processToolMajor(
+        SingleTouchInputMapper* mapper, int32_t toolMajor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, 0, toolMajor, 0);
+}
+
+void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+}
+
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenNotAttachedToADisplay_ReturnsTouchPad) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, -1);
+    prepareAxes(POSITION);
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenAttachedToADisplay_ReturnsTouchScreen) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareAxes(POSITION);
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    // Unknown key.
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+
+    // Virtual key is down.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME));
+
+    // Virtual key is up.
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    ASSERT_EQ(AKEY_STATE_UP, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME));
+}
+
+TEST_F(SingleTouchInputMapperTest, GetScanCodeState) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    // Unknown key.
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+
+    // Virtual key is down.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME));
+
+    // Virtual key is up.
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    ASSERT_EQ(AKEY_STATE_UP, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME));
+}
+
+TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    const int32_t keys[2] = { AKEYCODE_HOME, AKEYCODE_A };
+    uint8_t flags[2] = { 0, 0 };
+    ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 2, keys, flags));
+    ASSERT_TRUE(flags[0]);
+    ASSERT_FALSE(flags[1]);
+}
+
+TEST_F(SingleTouchInputMapperTest, Reset_WhenVirtualKeysAreDown_SendsUp) {
+    // Note: Ideally we should send cancels but the implementation is more straightforward
+    // with up and this will only happen if a device is forcibly removed.
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    // Press virtual key.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Reset.  Since key is down, synthesize key up.
+    mapper->reset();
+
+    FakeInputDispatcher::NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    //ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(SingleTouchInputMapperTest, Reset_WhenNothingIsPressed_NothingMuchHappens) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    // Press virtual key.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Release virtual key.
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Reset.  Since no key is down, nothing happens.
+    mapper->reset();
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyKeyArgs args;
+
+    // Press virtual key.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Release virtual key.
+    processUp(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Should not have sent any motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyKeyArgs keyArgs;
+
+    // Press virtual key.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&keyArgs));
+    ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, keyArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, keyArgs.flags);
+    ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode);
+    ASSERT_EQ(KEY_HOME, keyArgs.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime);
+
+    // Move out of bounds.  This should generate a cancel and a pointer down since we moved
+    // into the display area.
+    y -= 100;
+    processMove(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&keyArgs));
+    ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, keyArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+            | AKEY_EVENT_FLAG_CANCELED, keyArgs.flags);
+    ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode);
+    ASSERT_EQ(KEY_HOME, keyArgs.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Keep moving out of bounds.  Should generate a pointer move.
+    y -= 50;
+    processMove(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Release out of bounds.  Should generate a pointer up.
+    processUp(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+    // Initially go down out of bounds.
+    int32_t x = -10;
+    int32_t y = -10;
+    processDown(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+
+    // Move into the display area.  Should generate a pointer down.
+    x = 50;
+    y = 75;
+    processMove(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Release.  Should generate a pointer up.
+    processUp(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+    // Down.
+    int32_t x = 100;
+    int32_t y = 125;
+    processDown(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Move.
+    x += 50;
+    y += 75;
+    processMove(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Up.
+    processUp(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_Rotation) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareAxes(POSITION);
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Rotation 0.
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NEAR(50, args.pointerCoords[0].x, 1);
+    ASSERT_NEAR(75, args.pointerCoords[0].y, 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+
+    // Rotation 90.
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_90);
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NEAR(75, args.pointerCoords[0].x, 1);
+    ASSERT_NEAR(DISPLAY_WIDTH - 50, args.pointerCoords[0].y, 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+
+    // Rotation 180.
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_180);
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NEAR(DISPLAY_WIDTH - 50, args.pointerCoords[0].x, 1);
+    ASSERT_NEAR(DISPLAY_HEIGHT - 75, args.pointerCoords[0].y, 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+
+    // Rotation 270.
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_270);
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NEAR(DISPLAY_HEIGHT - 75, args.pointerCoords[0].x, 1);
+    ASSERT_NEAR(50, args.pointerCoords[0].y, 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION | PRESSURE | TOOL);
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawPressure = 10;
+    int32_t rawToolMajor = 12;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float pressure = float(rawPressure) / RAW_PRESSURE_MAX;
+    float size = float(rawToolMajor) / RAW_TOOL_MAX;
+    float tool = min(DISPLAY_WIDTH, DISPLAY_HEIGHT) * size;
+    float touch = min(tool * pressure, tool);
+
+    processDown(mapper, rawX, rawY);
+    processPressure(mapper, rawPressure);
+    processToolMajor(mapper, rawToolMajor);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touch, touch, tool, tool, 0));
+}
+
+
+// --- MultiTouchInputMapperTest ---
+
+class MultiTouchInputMapperTest : public TouchInputMapperTest {
+protected:
+    void prepareAxes(int axes);
+
+    void processPosition(MultiTouchInputMapper* mapper, int32_t x, int32_t y);
+    void processTouchMajor(MultiTouchInputMapper* mapper, int32_t touchMajor);
+    void processTouchMinor(MultiTouchInputMapper* mapper, int32_t touchMinor);
+    void processToolMajor(MultiTouchInputMapper* mapper, int32_t toolMajor);
+    void processToolMinor(MultiTouchInputMapper* mapper, int32_t toolMinor);
+    void processOrientation(MultiTouchInputMapper* mapper, int32_t orientation);
+    void processPressure(MultiTouchInputMapper* mapper, int32_t pressure);
+    void processId(MultiTouchInputMapper* mapper, int32_t id);
+    void processMTSync(MultiTouchInputMapper* mapper);
+    void processSync(MultiTouchInputMapper* mapper);
+};
+
+void MultiTouchInputMapperTest::prepareAxes(int axes) {
+    if (axes & POSITION) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+    }
+    if (axes & TOUCH) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TOUCH_MAJOR, RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0);
+        if (axes & MINOR) {
+            mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TOUCH_MINOR,
+                    RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0);
+        }
+    }
+    if (axes & TOOL) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_WIDTH_MAJOR, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0);
+        if (axes & MINOR) {
+            mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_WIDTH_MINOR,
+                    RAW_TOOL_MAX, RAW_TOOL_MAX, 0, 0);
+        }
+    }
+    if (axes & ORIENTATION) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_ORIENTATION,
+                RAW_ORIENTATION_MIN, RAW_ORIENTATION_MAX, 0, 0);
+    }
+    if (axes & PRESSURE) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_PRESSURE,
+                RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0);
+    }
+    if (axes & ID) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TRACKING_ID,
+                RAW_ID_MIN, RAW_ID_MAX, 0, 0);
+    }
+}
+
+void MultiTouchInputMapperTest::processPosition(
+        MultiTouchInputMapper* mapper, int32_t x, int32_t y) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_X, 0, x, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, 0, y, 0);
+}
+
+void MultiTouchInputMapperTest::processTouchMajor(
+        MultiTouchInputMapper* mapper, int32_t touchMajor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, 0, touchMajor, 0);
+}
+
+void MultiTouchInputMapperTest::processTouchMinor(
+        MultiTouchInputMapper* mapper, int32_t touchMinor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, 0, touchMinor, 0);
+}
+
+void MultiTouchInputMapperTest::processToolMajor(
+        MultiTouchInputMapper* mapper, int32_t toolMajor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, 0, toolMajor, 0);
+}
+
+void MultiTouchInputMapperTest::processToolMinor(
+        MultiTouchInputMapper* mapper, int32_t toolMinor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, 0, toolMinor, 0);
+}
+
+void MultiTouchInputMapperTest::processOrientation(
+        MultiTouchInputMapper* mapper, int32_t orientation) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, 0, orientation, 0);
+}
+
+void MultiTouchInputMapperTest::processPressure(
+        MultiTouchInputMapper* mapper, int32_t pressure) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, 0, pressure, 0);
+}
+
+void MultiTouchInputMapperTest::processId(
+        MultiTouchInputMapper* mapper, int32_t id) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, 0, id, 0);
+}
+
+void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper* mapper) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0, 0, 0);
+}
+
+void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper* mapper) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+}
+
+
+TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+    // Two fingers down at once.
+    int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
+    processPosition(mapper, x1, y1);
+    processMTSync(mapper);
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Move.
+    x1 += 10; y1 += 15; x2 += 5; y2 -= 10;
+    processPosition(mapper, x1, y1);
+    processMTSync(mapper);
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // First finger up.
+    x2 += 15; y2 -= 20;
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Move.
+    x2 += 20; y2 -= 25;
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // New finger down.
+    int32_t x3 = 700, y3 = 300;
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processPosition(mapper, x3, y3);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Second finger up.
+    x3 += 30; y3 -= 20;
+    processPosition(mapper, x3, y3);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Last finger up.
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION | ID);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+    // Two fingers down at once.
+    int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
+    processPosition(mapper, x1, y1);
+    processId(mapper, 1);
+    processMTSync(mapper);
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_EQ(2, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    // Move.
+    x1 += 10; y1 += 15; x2 += 5; y2 -= 10;
+    processPosition(mapper, x1, y1);
+    processId(mapper, 1);
+    processMTSync(mapper);
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_EQ(2, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    // First finger up.
+    x2 += 15; y2 -= 20;
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_EQ(2, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(2, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    // Move.
+    x2 += 20; y2 -= 25;
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(2, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    // New finger down.
+    int32_t x3 = 700, y3 = 300;
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processPosition(mapper, x3, y3);
+    processId(mapper, 3);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(2, motionArgs.pointerIds[0]);
+    ASSERT_EQ(3, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+    // Second finger up.
+    x3 += 30; y3 -= 20;
+    processPosition(mapper, x3, y3);
+    processId(mapper, 3);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(2, motionArgs.pointerIds[0]);
+    ASSERT_EQ(3, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(3, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+    // Last finger up.
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(3, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR);
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawTouchMajor = 7;
+    int32_t rawTouchMinor = 6;
+    int32_t rawToolMajor = 9;
+    int32_t rawToolMinor = 8;
+    int32_t rawPressure = 11;
+    int32_t rawOrientation = 3;
+    int32_t id = 5;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float pressure = float(rawPressure) / RAW_PRESSURE_MAX;
+    float size = avg(rawToolMajor, rawToolMinor) / RAW_TOOL_MAX;
+    float toolMajor = float(min(DISPLAY_WIDTH, DISPLAY_HEIGHT)) * rawToolMajor / RAW_TOOL_MAX;
+    float toolMinor = float(min(DISPLAY_WIDTH, DISPLAY_HEIGHT)) * rawToolMinor / RAW_TOOL_MAX;
+    float touchMajor = min(toolMajor * pressure, toolMajor);
+    float touchMinor = min(toolMinor * pressure, toolMinor);
+    float orientation = float(rawOrientation) / RAW_ORIENTATION_MAX * M_PI_2;
+
+    processPosition(mapper, rawX, rawY);
+    processTouchMajor(mapper, rawTouchMajor);
+    processTouchMinor(mapper, rawTouchMinor);
+    processToolMajor(mapper, rawToolMajor);
+    processToolMinor(mapper, rawToolMinor);
+    processPressure(mapper, rawPressure);
+    processOrientation(mapper, rawOrientation);
+    processId(mapper, id);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(id, args.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, orientation));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION | TOUCH | TOOL | MINOR);
+    prepareCalibration("touch.touchSize.calibration", "geometric");
+    prepareCalibration("touch.toolSize.calibration", "geometric");
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawTouchMajor = 140;
+    int32_t rawTouchMinor = 120;
+    int32_t rawToolMajor = 180;
+    int32_t rawToolMinor = 160;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float pressure = float(rawTouchMajor) / RAW_TOUCH_MAX;
+    float size = avg(rawToolMajor, rawToolMinor) / RAW_TOOL_MAX;
+    float scale = avg(float(DISPLAY_WIDTH) / (RAW_X_MAX - RAW_X_MIN),
+            float(DISPLAY_HEIGHT) / (RAW_Y_MAX - RAW_Y_MIN));
+    float toolMajor = float(rawToolMajor) * scale;
+    float toolMinor = float(rawToolMinor) * scale;
+    float touchMajor = min(float(rawTouchMajor) * scale, toolMajor);
+    float touchMinor = min(float(rawTouchMinor) * scale, toolMinor);
+
+    processPosition(mapper, rawX, rawY);
+    processTouchMajor(mapper, rawTouchMajor);
+    processTouchMinor(mapper, rawTouchMinor);
+    processToolMajor(mapper, rawToolMajor);
+    processToolMinor(mapper, rawToolMinor);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchToolPressureSizeAxes_SummedLinearCalibration) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION | TOUCH | TOOL);
+    prepareCalibration("touch.touchSize.calibration", "pressure");
+    prepareCalibration("touch.toolSize.calibration", "linear");
+    prepareCalibration("touch.toolSize.linearScale", "10");
+    prepareCalibration("touch.toolSize.linearBias", "160");
+    prepareCalibration("touch.toolSize.isSummed", "1");
+    prepareCalibration("touch.pressure.calibration", "amplitude");
+    prepareCalibration("touch.pressure.source", "touch");
+    prepareCalibration("touch.pressure.scale", "0.01");
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    // Note: We only provide a single common touch/tool value because the device is assumed
+    //       not to emit separate values for each pointer (isSummed = 1).
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawX2 = 150;
+    int32_t rawY2 = 250;
+    int32_t rawTouchMajor = 60;
+    int32_t rawToolMajor = 5;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float x2 = toDisplayX(rawX2);
+    float y2 = toDisplayY(rawY2);
+    float pressure = float(rawTouchMajor) * 0.01f;
+    float size = float(rawToolMajor) / RAW_TOOL_MAX;
+    float tool = (float(rawToolMajor) * 10.0f + 160.0f) / 2;
+    float touch = min(tool * pressure, tool);
+
+    processPosition(mapper, rawX, rawY);
+    processTouchMajor(mapper, rawTouchMajor);
+    processToolMajor(mapper, rawToolMajor);
+    processMTSync(mapper);
+    processPosition(mapper, rawX2, rawY2);
+    processTouchMajor(mapper, rawTouchMajor);
+    processToolMajor(mapper, rawToolMajor);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            args.action);
+    ASSERT_EQ(size_t(2), args.pointerCount);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touch, touch, tool, tool, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[1],
+            x2, y2, pressure, size, touch, touch, tool, tool, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchToolPressureSizeAxes_AreaCalibration) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice, DISPLAY_ID);
+    prepareDisplay(InputReaderPolicyInterface::ROTATION_0);
+    prepareAxes(POSITION | TOUCH | TOOL);
+    prepareCalibration("touch.touchSize.calibration", "pressure");
+    prepareCalibration("touch.toolSize.calibration", "area");
+    prepareCalibration("touch.toolSize.areaScale", "22");
+    prepareCalibration("touch.toolSize.areaBias", "1");
+    prepareCalibration("touch.toolSize.linearScale", "9.2");
+    prepareCalibration("touch.toolSize.linearBias", "3");
+    prepareCalibration("touch.pressure.calibration", "amplitude");
+    prepareCalibration("touch.pressure.source", "touch");
+    prepareCalibration("touch.pressure.scale", "0.01");
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawTouchMajor = 60;
+    int32_t rawToolMajor = 5;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float pressure = float(rawTouchMajor) * 0.01f;
+    float size = float(rawToolMajor) / RAW_TOOL_MAX;
+    float tool = sqrtf(float(rawToolMajor) * 22.0f + 1.0f) * 9.2f + 3.0f;
+    float touch = min(tool * pressure, tool);
+
+    processPosition(mapper, rawX, rawY);
+    processTouchMajor(mapper, rawTouchMajor);
+    processToolMajor(mapper, rawToolMajor);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touch, touch, tool, tool, 0));
+}
+
+} // namespace android
diff --git a/libs/utils/StreamingZipInflater.cpp b/libs/utils/StreamingZipInflater.cpp
index 7ebde78..1f62ac5 100644
--- a/libs/utils/StreamingZipInflater.cpp
+++ b/libs/utils/StreamingZipInflater.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#define LOG_NDEBUG 1
 #define LOG_TAG "szipinf"
 #include <utils/Log.h>
 
@@ -157,7 +158,7 @@
             */
             int result = Z_OK;
             if (mStreamNeedsInit) {
-                LOGI("Initializing zlib to inflate");
+                LOGD("Initializing zlib to inflate");
                 result = inflateInit2(&mInflateState, -MAX_WBITS);
                 mStreamNeedsInit = false;
             }
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index 5ff1f8f..4261196 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -412,10 +412,18 @@
 /*
  * Find a matching entry.
  *
- * Returns 0 if not found.
+ * Returns NULL if not found.
  */
 ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const
 {
+    /*
+     * If the ZipFileRO instance is not initialized, the entry number will
+     * end up being garbage since mHashTableSize is -1.
+     */
+    if (mHashTableSize <= 0) {
+        return NULL;
+    }
+
     int nameLen = strlen(fileName);
     unsigned int hash = computeHash(fileName, nameLen);
     int ent = hash & (mHashTableSize-1);
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 6aa1ae6..23f34d2 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -58,6 +58,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
@@ -1112,18 +1113,16 @@
         new BluetoothProfile.ServiceListener() {
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             mBluetoothHeadset = (BluetoothHeadset) proxy;
-            Set<BluetoothDevice> deviceSet = mBluetoothHeadset.getConnectedDevices();
-            if (deviceSet.size() > 0) {
-                BluetoothDevice[] devices =
-                    deviceSet.toArray(new BluetoothDevice[deviceSet.size()]);
-                mBluetoothHeadsetDevice = devices[0];
+            List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+            if (deviceList.size() > 0) {
+                mBluetoothHeadsetDevice = deviceList.get(0);
             } else {
                 mBluetoothHeadsetDevice = null;
             }
         }
         public void onServiceDisconnected(int profile) {
             if (mBluetoothHeadset != null) {
-                Set<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
+                List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
                 if (devices.size() == 0) {
                     mBluetoothHeadsetDevice = null;
                     clearAllScoClients();
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index 57cafd4..27b28ee 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -131,6 +131,7 @@
                 samplingrate,
                 0,
                 jArray);
+            env->DeleteLocalRef(jArray);
         }
     }
 
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index 40801a2..f975217 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -24,6 +24,7 @@
     ENABLE_GRAPHIC_BUFFERS,
     USE_BUFFER,
     USE_GRAPHIC_BUFFER,
+    STORE_META_DATA_IN_BUFFERS,
     ALLOC_BUFFER,
     ALLOC_BUFFER_WITH_BACKUP,
     FREE_BUFFER,
@@ -276,6 +277,19 @@
         return err;
     }
 
+    virtual status_t storeMetaDataInBuffers(
+            node_id node, OMX_U32 port_index, OMX_BOOL enable) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+        data.writeIntPtr((intptr_t)node);
+        data.writeInt32(port_index);
+        data.writeInt32((uint32_t)enable);
+        remote()->transact(STORE_META_DATA_IN_BUFFERS, data, &reply);
+
+        status_t err = reply.readInt32();
+        return err;
+    }
+
     virtual status_t allocateBuffer(
             node_id node, OMX_U32 port_index, size_t size,
             buffer_id *buffer, void **buffer_data) {
@@ -634,6 +648,20 @@
             return NO_ERROR;
         }
 
+        case STORE_META_DATA_IN_BUFFERS:
+        {
+            CHECK_INTERFACE(IOMX, data, reply);
+
+            node_id node = (void*)data.readIntPtr();
+            OMX_U32 port_index = data.readInt32();
+            OMX_BOOL enable = (OMX_BOOL)data.readInt32();
+
+            status_t err = storeMetaDataInBuffers(node, port_index, enable);
+            reply->writeInt32(err);
+
+            return NO_ERROR;
+        }
+
         case ALLOC_BUFFER:
         {
             CHECK_INTERFACE(IOMX, data, reply);
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 6c8287c..9e79aa9 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -41,7 +41,10 @@
 static const int64_t kMax32BitFileSize = 0x007fffffffLL;
 static const uint8_t kNalUnitTypeSeqParamSet = 0x07;
 static const uint8_t kNalUnitTypePicParamSet = 0x08;
-static const int64_t kVideoMediaTimeAdjustPeriodTimeUs = 10000000LL;  // 10s
+
+// Using longer adjustment period to suppress fluctuations in
+// the audio encoding paths
+static const int64_t kVideoMediaTimeAdjustPeriodTimeUs = 600000000LL;  // 10 minutes
 
 class MPEG4Writer::Track {
 public:
@@ -309,11 +312,10 @@
     //
     // Statistical analysis shows that metadata usually accounts
     // for a small portion of the total file size, usually < 0.6%.
-    // Currently, lets set to 0.4% for now.
 
-    // The default MIN_MOOV_BOX_SIZE is set to 0.4% x 1MB,
+    // The default MIN_MOOV_BOX_SIZE is set to 0.6% x 1MB / 2,
     // where 1MB is the common file size limit for MMS application.
-    // The default MAX _MOOV_BOX_SIZE value is based on about 4
+    // The default MAX _MOOV_BOX_SIZE value is based on about 3
     // minute video recording with a bit rate about 3 Mbps, because
     // statistics also show that most of the video captured are going
     // to be less than 3 minutes.
@@ -321,20 +323,33 @@
     // If the estimation is wrong, we will pay the price of wasting
     // some reserved space. This should not happen so often statistically.
     static const int32_t factor = mUse32BitOffset? 1: 2;
-    static const int64_t MIN_MOOV_BOX_SIZE = 4 * 1024;  // 4 KB
+    static const int64_t MIN_MOOV_BOX_SIZE = 3 * 1024;  // 3 KB
     static const int64_t MAX_MOOV_BOX_SIZE = (180 * 3000000 * 6LL / 8000);
     int64_t size = MIN_MOOV_BOX_SIZE;
 
+    // Max file size limit is set
     if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) {
-        size = mMaxFileSizeLimitBytes * 4 / 1000;
-    } else if (mMaxFileDurationLimitUs != 0) {
-        if (bitRate <= 0) {
-            // We could not estimate the file size since bitRate is not set.
-            size = MIN_MOOV_BOX_SIZE;
-        } else {
-            size = ((mMaxFileDurationLimitUs * bitRate * 4) / 1000 / 8000000);
+        size = mMaxFileSizeLimitBytes * 6 / 1000;
+    }
+
+    // Max file duration limit is set
+    if (mMaxFileDurationLimitUs != 0) {
+        if (bitRate > 0) {
+            int64_t size2 =
+                ((mMaxFileDurationLimitUs * bitRate * 6) / 1000 / 8000000);
+            if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) {
+                // When both file size and duration limits are set,
+                // we use the smaller limit of the two.
+                if (size > size2) {
+                    size = size2;
+                }
+            } else {
+                // Only max file duration limit is set
+                size = size2;
+            }
         }
     }
+
     if (size < MIN_MOOV_BOX_SIZE) {
         size = MIN_MOOV_BOX_SIZE;
     }
@@ -787,6 +802,10 @@
     write(data, 1, size, mFile);
 }
 
+bool MPEG4Writer::isFileStreamable() const {
+    return mStreamableFile;
+}
+
 bool MPEG4Writer::exceedsFileSizeLimit() {
     // No limit
     if (mMaxFileSizeLimitBytes == 0) {
@@ -799,7 +818,7 @@
         nTotalBytesEstimate += (*it)->getEstimatedTrackSizeBytes();
     }
 
-    return (nTotalBytesEstimate  + 1024 >= mMaxFileSizeLimitBytes);
+    return (nTotalBytesEstimate >= mMaxFileSizeLimitBytes);
 }
 
 bool MPEG4Writer::exceedsFileDurationLimit() {
@@ -887,12 +906,16 @@
 
     int64_t stszBoxSizeBytes = mSamplesHaveSameSize? 4: (mNumSamples * 4);
 
-    mEstimatedTrackSizeBytes = mMdatSizeBytes +             // media data size
-                               mNumStscTableEntries * 12 +  // stsc box size
-                               mNumStssTableEntries * 4 +   // stss box size
-                               mNumSttsTableEntries * 8 +   // stts box size
-                               stcoBoxSizeBytes +           // stco box size
-                               stszBoxSizeBytes;            // stsz box size
+    mEstimatedTrackSizeBytes = mMdatSizeBytes;  // media data size
+    if (!mOwner->isFileStreamable()) {
+        // Reserved free space is not large enough to hold
+        // all meta data and thus wasted.
+        mEstimatedTrackSizeBytes += mNumStscTableEntries * 12 +  // stsc box size
+                                    mNumStssTableEntries * 4 +   // stss box size
+                                    mNumSttsTableEntries * 8 +   // stts box size
+                                    stcoBoxSizeBytes +           // stco box size
+                                    stszBoxSizeBytes;            // stsz box size
+    }
 }
 
 void MPEG4Writer::Track::addOneStscTableEntry(
@@ -1155,7 +1178,7 @@
         startTimeUs = 0;
     }
 
-    mIsRealTimeRecording = false;
+    mIsRealTimeRecording = true;
     {
         int32_t isNotRealTime;
         if (params && params->findInt32(kKeyNotRealTime, &isNotRealTime)) {
diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h
index 83b75ad..5a6c96f9 100644
--- a/media/libstagefright/include/OMX.h
+++ b/media/libstagefright/include/OMX.h
@@ -62,6 +62,9 @@
     virtual status_t enableGraphicBuffers(
             node_id node, OMX_U32 port_index, OMX_BOOL enable);
 
+    virtual status_t storeMetaDataInBuffers(
+            node_id node, OMX_U32 port_index, OMX_BOOL enable);
+
     virtual status_t useBuffer(
             node_id node, OMX_U32 port_index, const sp<IMemory> &params,
             buffer_id *buffer);
diff --git a/media/libstagefright/include/OMXNodeInstance.h b/media/libstagefright/include/OMXNodeInstance.h
index 8c7c562..86c102c 100644
--- a/media/libstagefright/include/OMXNodeInstance.h
+++ b/media/libstagefright/include/OMXNodeInstance.h
@@ -50,6 +50,7 @@
     status_t setConfig(OMX_INDEXTYPE index, const void *params, size_t size);
 
     status_t enableGraphicBuffers(OMX_U32 portIndex, OMX_BOOL enable);
+    status_t storeMetaDataInBuffers(OMX_U32 portIndex, OMX_BOOL enable);
 
     status_t useBuffer(
             OMX_U32 portIndex, const sp<IMemory> &params,
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index d89f54b..f9f638f 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -294,6 +294,11 @@
     return findInstance(node)->enableGraphicBuffers(port_index, enable);
 }
 
+status_t OMX::storeMetaDataInBuffers(
+        node_id node, OMX_U32 port_index, OMX_BOOL enable) {
+    return findInstance(node)->storeMetaDataInBuffers(port_index, enable);
+}
+
 status_t OMX::useBuffer(
         node_id node, OMX_U32 port_index, const sp<IMemory> &params,
         buffer_id *buffer) {
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index ba4d765..9b6d441 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -284,6 +284,37 @@
     return OK;
 }
 
+status_t OMXNodeInstance::storeMetaDataInBuffers(
+        OMX_U32 portIndex,
+        OMX_BOOL enable) {
+    Mutex::Autolock autolock(mLock);
+
+    OMX_INDEXTYPE index;
+    OMX_STRING name = const_cast<OMX_STRING>(
+            "OMX.google.android.index.storeMetaDataInBuffers");
+
+    OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index);
+    if (err != OMX_ErrorNone) {
+        LOGE("OMX_GetExtensionIndex %s failed", name);
+        return StatusFromOMXError(err);
+    }
+
+    StoreMetaDataInBuffersParams params;
+    memset(&params, 0, sizeof(params));
+    params.nSize = sizeof(params);
+
+    // Version: 1.0.0.0
+    params.nVersion.s.nVersionMajor = 1;
+
+    params.nPortIndex = portIndex;
+    params.bStoreMetaData = enable;
+    if ((err = OMX_SetParameter(mHandle, index, &params)) != OMX_ErrorNone) {
+        LOGE("OMX_SetParameter() failed for StoreMetaDataInBuffers: 0x%08x", err);
+        return UNKNOWN_ERROR;
+    }
+    return err;
+}
+
 status_t OMXNodeInstance::useBuffer(
         OMX_U32 portIndex, const sp<IMemory> &params,
         OMX::buffer_id *buffer) {
diff --git a/native/android/native_activity.cpp b/native/android/native_activity.cpp
index 0c6823a..056255f 100644
--- a/native/android/native_activity.cpp
+++ b/native/android/native_activity.cpp
@@ -21,6 +21,10 @@
 
 using namespace android;
 
+void ANativeActivity_finish(ANativeActivity* activity) {
+    android_NativeActivity_finish(activity);
+}
+
 void ANativeActivity_setWindowFormat(ANativeActivity* activity, int32_t format) {
 	android_NativeActivity_setWindowFormat(activity, format);
 }
diff --git a/native/copy-to-ndk.sh b/native/copy-to-ndk.sh
index 4f5a16a..6470892 100644
--- a/native/copy-to-ndk.sh
+++ b/native/copy-to-ndk.sh
@@ -10,7 +10,7 @@
     local DST_HEADERS=$NDK_PLATFORMS/$CURR_PLATFORM
 
     local SRC_LIB_ANDROID=$ANDROID_PRODUCT_OUT/system/lib/libandroid.so
-    local DST_LIB_ANDROID=$NDK_PLATFORMS/$CURR_PLATFORM/arch-arm/usr/lib/libandroid.so
+    local DST_LIB_ANDROID=$NDK_PLATFORMS/$CURR_PLATFORM/arch-arm/lib/libandroid.so
 
     local didsomething=""
 
@@ -20,9 +20,9 @@
         local src=$SRC_HEADERS/$i
         local changed=""
         for j in $ALL_PLATFORMS; do
-            local dst=$NDK_PLATFORMS/$j/arch-arm/usr/include/android/$i
+            local dst=$NDK_PLATFORMS/$j/include/android/$i
             if [ "$changed" == "" -a -e $dst ]; then
-                #echo "Exists: $dst"
+                echo "Exists: $dst"
                 if diff $src $dst >/dev/null; then
                     echo "$i: has not changed from $j" >/dev/null
                     changed="false"
@@ -34,13 +34,13 @@
         done
         if [ "$changed" == "true" -o "$changed" == "" ]; then
             echo "Updating: $i"
-            cp $src $NDK_PLATFORMS/$CURR_PLATFORM/arch-arm/usr/include/android/$i
+            cp $src $NDK_PLATFORMS/$CURR_PLATFORM/include/android/$i
             didsomething="true"
         fi
     done
 
     if diff $SRC_LIB_ANDROID $DST_LIB_ANDROID >/dev/null; then
-        echo "libandroid.so: has not changed" >/dev/null
+        echo "libandroid.so: has not changed"
     else
         echo "Updating: $DST_LIB_ANDROID"
         cp $SRC_LIB_ANDROID $DST_LIB_ANDROID
diff --git a/native/include/android/input.h b/native/include/android/input.h
index d190e49..729dd2e 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -336,6 +336,8 @@
     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_ANY = 0xffffff00,
 };
 
 /*
diff --git a/native/include/android/native_activity.h b/native/include/android/native_activity.h
index d74e1ce..d89bc8b 100644
--- a/native/include/android/native_activity.h
+++ b/native/include/android/native_activity.h
@@ -223,12 +223,34 @@
 
 /**
  * The name of the function that NativeInstance looks for when launching its
- * native code.
+ * native code.  This is the default function that is used, you can specify
+ * "android.app.func_name" string meta-data in your manifest to use a different
+ * function.
  */
 extern ANativeActivity_createFunc ANativeActivity_onCreate;
 
+/**
+ * Finish the given activity.  Its finish() method will be called, causing it
+ * to be stopped and destroyed.  Note that this method can be called from
+ * *any* thread; it will send a message to the main thread of the process
+ * where the Java finish call will take place.
+ */
+void ANativeActivity_finish(ANativeActivity* activity);
+
+/**
+ * Change the window format of the given activity.  Calls getWindow().setFormat()
+ * of the given activity.  Note that this method can be called from
+ * *any* thread; it will send a message to the main thread of the process
+ * where the Java finish call will take place.
+ */
 void ANativeActivity_setWindowFormat(ANativeActivity* activity, int32_t format);
 
+/**
+ * Change the window flags of the given activity.  Calls getWindow().setFlags()
+ * of the given activity.  Note that this method can be called from
+ * *any* thread; it will send a message to the main thread of the process
+ * where the Java finish call will take place.  See window.h for flag constants.
+ */
 void ANativeActivity_setWindowFlags(ANativeActivity* activity,
         uint32_t addFlags, uint32_t removeFlags);
 
@@ -241,6 +263,12 @@
     ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
 };
 
+/**
+ * Show the IME while in the given activity.  Calls InputMethodManager.showSoftInput()
+ * for the given activity.  Note that this method can be called from
+ * *any* thread; it will send a message to the main thread of the process
+ * where the Java finish call will take place.
+ */
 void ANativeActivity_showSoftInput(ANativeActivity* activity, uint32_t flags);
 
 /**
@@ -252,6 +280,12 @@
     ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
 };
 
+/**
+ * Hide the IME while in the given activity.  Calls InputMethodManager.hideSoftInput()
+ * for the given activity.  Note that this method can be called from
+ * *any* thread; it will send a message to the main thread of the process
+ * where the Java finish call will take place.
+ */
 void ANativeActivity_hideSoftInput(ANativeActivity* activity, uint32_t flags);
 
 #ifdef __cplusplus
diff --git a/opengl/libagl/egl.cpp b/opengl/libagl/egl.cpp
index 239dc05..e38b9cc 100644
--- a/opengl/libagl/egl.cpp
+++ b/opengl/libagl/egl.cpp
@@ -840,7 +840,7 @@
 #define VERSION_MAJOR 1
 #define VERSION_MINOR 2
 static char const * const gVendorString     = "Google Inc.";
-static char const * const gVersionString    = "1.2 Android Driver 1.1.0";
+static char const * const gVersionString    = "1.2 Android Driver 1.2.0";
 static char const * const gClientApiString  = "OpenGL ES";
 static char const * const gExtensionsString =
         "EGL_KHR_image_base "
@@ -947,6 +947,7 @@
         { EGL_RED_SIZE,         5 },
         { EGL_DEPTH_SIZE,       0 },
         { EGL_CONFIG_ID,        0 },
+        { EGL_NATIVE_VISUAL_ID, GGL_PIXEL_FORMAT_RGB_565 },
         { EGL_SURFACE_TYPE,     EGL_WINDOW_BIT|EGL_PBUFFER_BIT|EGL_PIXMAP_BIT },
 };
 
@@ -958,6 +959,7 @@
         { EGL_RED_SIZE,         5 },
         { EGL_DEPTH_SIZE,      16 },
         { EGL_CONFIG_ID,        1 },
+        { EGL_NATIVE_VISUAL_ID, GGL_PIXEL_FORMAT_RGB_565 },
         { EGL_SURFACE_TYPE,     EGL_WINDOW_BIT|EGL_PBUFFER_BIT|EGL_PIXMAP_BIT },
 };
 
@@ -970,6 +972,7 @@
         { EGL_RED_SIZE,         8 },
         { EGL_DEPTH_SIZE,       0 },
         { EGL_CONFIG_ID,        6 },
+        { EGL_NATIVE_VISUAL_ID, GGL_PIXEL_FORMAT_RGBX_8888 },
         { EGL_SURFACE_TYPE,     EGL_WINDOW_BIT|EGL_PBUFFER_BIT|EGL_PIXMAP_BIT },
 };
 
@@ -981,6 +984,7 @@
         { EGL_RED_SIZE,         8 },
         { EGL_DEPTH_SIZE,      16 },
         { EGL_CONFIG_ID,        7 },
+        { EGL_NATIVE_VISUAL_ID, GGL_PIXEL_FORMAT_RGBX_8888 },
         { EGL_SURFACE_TYPE,     EGL_WINDOW_BIT|EGL_PBUFFER_BIT|EGL_PIXMAP_BIT },
 };
 
@@ -993,6 +997,7 @@
         { EGL_RED_SIZE,         8 },
         { EGL_DEPTH_SIZE,       0 },
         { EGL_CONFIG_ID,        2 },
+        { EGL_NATIVE_VISUAL_ID, GGL_PIXEL_FORMAT_RGBA_8888 },
         { EGL_SURFACE_TYPE,     EGL_WINDOW_BIT|EGL_PBUFFER_BIT|EGL_PIXMAP_BIT },
 };
 
@@ -1004,6 +1009,7 @@
         { EGL_RED_SIZE,         8 },
         { EGL_DEPTH_SIZE,      16 },
         { EGL_CONFIG_ID,        3 },
+        { EGL_NATIVE_VISUAL_ID, GGL_PIXEL_FORMAT_RGBA_8888 },
         { EGL_SURFACE_TYPE,     EGL_WINDOW_BIT|EGL_PBUFFER_BIT|EGL_PIXMAP_BIT },
 };
 
@@ -1016,6 +1022,7 @@
         { EGL_RED_SIZE,         0 },
         { EGL_DEPTH_SIZE,       0 },
         { EGL_CONFIG_ID,        4 },
+        { EGL_NATIVE_VISUAL_ID, GGL_PIXEL_FORMAT_A_8 },
         { EGL_SURFACE_TYPE,     EGL_WINDOW_BIT|EGL_PBUFFER_BIT|EGL_PIXMAP_BIT },
 };
 
@@ -1027,6 +1034,7 @@
         { EGL_RED_SIZE,         0 },
         { EGL_DEPTH_SIZE,      16 },
         { EGL_CONFIG_ID,        5 },
+        { EGL_NATIVE_VISUAL_ID, GGL_PIXEL_FORMAT_A_8 },
         { EGL_SURFACE_TYPE,     EGL_WINDOW_BIT|EGL_PBUFFER_BIT|EGL_PIXMAP_BIT },
 };
 
diff --git a/opengl/libagl/state.cpp b/opengl/libagl/state.cpp
index a0f720a..8b4136a 100644
--- a/opengl/libagl/state.cpp
+++ b/opengl/libagl/state.cpp
@@ -33,7 +33,7 @@
 // ----------------------------------------------------------------------------
 
 static char const * const gVendorString     = "Android";
-static char const * const gRendererString   = "Android PixelFlinger 1.3";
+static char const * const gRendererString   = "Android PixelFlinger 1.4";
 static char const * const gVersionString    = "OpenGL ES-CM 1.0";
 static char const * const gExtensionsString =
     "GL_OES_byte_coordinates "              // OK
diff --git a/opengl/tests/configdump/Android.mk b/opengl/tests/configdump/Android.mk
new file mode 100644
index 0000000..3f7c915
--- /dev/null
+++ b/opengl/tests/configdump/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	configdump.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+    libEGL \
+    libGLESv1_CM
+
+LOCAL_MODULE:= test-opengl-configdump
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_EXECUTABLE)
diff --git a/opengl/tests/configdump/configdump.cpp b/opengl/tests/configdump/configdump.cpp
new file mode 100644
index 0000000..69b9eb6
--- /dev/null
+++ b/opengl/tests/configdump/configdump.cpp
@@ -0,0 +1,89 @@
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <EGL/egl.h>
+
+#define ATTRIBUTE(_attr) { _attr, #_attr }
+
+struct Attribute {
+    EGLint attribute;
+    char const* name;
+};
+
+Attribute attributes[] = {
+        ATTRIBUTE( EGL_BUFFER_SIZE ),
+        ATTRIBUTE( EGL_ALPHA_SIZE ),
+        ATTRIBUTE( EGL_BLUE_SIZE ),
+        ATTRIBUTE( EGL_GREEN_SIZE ),
+        ATTRIBUTE( EGL_RED_SIZE ),
+        ATTRIBUTE( EGL_DEPTH_SIZE ),
+        ATTRIBUTE( EGL_STENCIL_SIZE ),
+        ATTRIBUTE( EGL_CONFIG_CAVEAT ),
+        ATTRIBUTE( EGL_CONFIG_ID ),
+        ATTRIBUTE( EGL_LEVEL ),
+        ATTRIBUTE( EGL_MAX_PBUFFER_HEIGHT ),
+        ATTRIBUTE( EGL_MAX_PBUFFER_WIDTH ),
+        ATTRIBUTE( EGL_MAX_PBUFFER_PIXELS ),
+        ATTRIBUTE( EGL_NATIVE_RENDERABLE ),
+        ATTRIBUTE( EGL_NATIVE_VISUAL_ID ),
+        ATTRIBUTE( EGL_NATIVE_VISUAL_TYPE ),
+        ATTRIBUTE( EGL_SAMPLES ),
+        ATTRIBUTE( EGL_SAMPLE_BUFFERS ),
+        ATTRIBUTE( EGL_SURFACE_TYPE ),
+        ATTRIBUTE( EGL_TRANSPARENT_TYPE ),
+        ATTRIBUTE( EGL_TRANSPARENT_BLUE_VALUE ),
+        ATTRIBUTE( EGL_TRANSPARENT_GREEN_VALUE ),
+        ATTRIBUTE( EGL_TRANSPARENT_RED_VALUE ),
+        ATTRIBUTE( EGL_BIND_TO_TEXTURE_RGB ),
+        ATTRIBUTE( EGL_BIND_TO_TEXTURE_RGBA ),
+        ATTRIBUTE( EGL_MIN_SWAP_INTERVAL ),
+        ATTRIBUTE( EGL_MAX_SWAP_INTERVAL ),
+        ATTRIBUTE( EGL_LUMINANCE_SIZE ),
+        ATTRIBUTE( EGL_ALPHA_MASK_SIZE ),
+        ATTRIBUTE( EGL_COLOR_BUFFER_TYPE ),
+        ATTRIBUTE( EGL_RENDERABLE_TYPE ),
+        ATTRIBUTE( EGL_MATCH_NATIVE_PIXMAP ),
+        ATTRIBUTE( EGL_CONFORMANT ),
+};
+
+
+int main(int argc, char** argv)
+{
+    EGLConfig* configs;
+    EGLint n;
+
+    EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    eglInitialize(dpy, 0, 0);
+    eglGetConfigs(dpy, NULL, 0, &n);
+    configs = new EGLConfig[n];
+    eglGetConfigs(dpy, configs, n, &n);
+
+    for (EGLint i=0 ; i<n ; i++) {
+        printf("EGLConfig[%d]\n", i);
+        for (int attr = 0 ; attr<sizeof(attributes)/sizeof(Attribute) ; attr++) {
+            EGLint value;
+            eglGetConfigAttrib(dpy, configs[i], attributes[attr].attribute, &value);
+            printf("\t%-32s: %10d (0x%08x)\n", attributes[attr].name, value, value);
+        }
+    }
+
+    delete [] configs;
+    eglTerminate(dpy);
+    return 0;
+}
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
index d19f318..165107c 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
@@ -97,8 +97,8 @@
 public class KeyguardViewMediator implements KeyguardViewCallback,
         KeyguardUpdateMonitor.SimStateCallback {
     private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
-    private final static boolean DEBUG = false && Config.LOGD;
-    private final static boolean DBG_WAKE = DEBUG || true;
+    private final static boolean DEBUG = false;
+    private final static boolean DBG_WAKE = false;
 
     private final static String TAG = "KeyguardViewMediator";
 
@@ -642,7 +642,7 @@
      * @see #onWakeKeyWhenKeyguardShowingTq(int)
      */
     private void wakeWhenReadyLocked(int keyCode) {
-        if (DBG_WAKE) Log.d(TAG, "wakeWhenReadyLocked(" + keyCode + ")");
+        if (true || DBG_WAKE) Log.d(TAG, "wakeWhenReadyLocked(" + keyCode + ")");
 
         /**
          * acquire the handoff lock that will keep the cpu running.  this will
@@ -1169,5 +1169,3 @@
         }
     }
 }
-
-
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index db5f6c1..39224ba 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -58,6 +58,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.TypedValue;
 import android.view.ActionMode;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
@@ -83,6 +84,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
+import android.widget.PopupWindow;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -1700,6 +1702,7 @@
 
         private ActionMode mActionMode;
         private ActionBarContextView mActionModeView;
+        private PopupWindow mActionModePopup;
 
         public DecorView(Context context, int featureId) {
             super(context);
@@ -2019,9 +2022,18 @@
                 if (mActionModeView == null) {
                     if (hasFeature(FEATURE_ACTION_MODE_OVERLAY)) {
                         mActionModeView = new ActionBarContextView(mContext);
-                        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
-                                MATCH_PARENT, WRAP_CONTENT);
-                        addView(mActionModeView, params);
+                        mActionModePopup = new PopupWindow(mContext);
+                        mActionModePopup.setLayoutInScreenEnabled(true);
+                        mActionModePopup.setClippingEnabled(false);
+                        mActionModePopup.setContentView(mActionModeView);
+                        mActionModePopup.setWidth(MATCH_PARENT);
+
+                        TypedValue heightValue = new TypedValue();
+                        mContext.getTheme().resolveAttribute(
+                                com.android.internal.R.attr.actionBarSize, heightValue, false);
+                        final int height = TypedValue.complexToDimensionPixelSize(heightValue.data,
+                                mContext.getResources().getDisplayMetrics());
+                        mActionModePopup.setHeight(height);
                     } else {
                         ViewStub stub = (ViewStub) findViewById(
                                 com.android.internal.R.id.action_mode_bar_stub);
@@ -2038,6 +2050,10 @@
                         mActionModeView.initForMode(mode);
                         mActionModeView.setVisibility(View.VISIBLE);
                         mActionMode = mode;
+                        if (mActionModePopup != null) {
+                            mActionModePopup.showAtLocation(this,
+                                    Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
+                        }
                     } else {
                         mActionMode = null;
                     }
@@ -2250,6 +2266,11 @@
 
             public void onDestroyActionMode(ActionMode mode) {
                 mWrapped.onDestroyActionMode(mode);
+                if (mActionModePopup != null) {
+                    mActionModePopup.dismiss();
+                } else if (mActionModeView != null) {
+                    mActionModeView.setVisibility(GONE);
+                }
                 if (mActionModeView != null) {
                     mActionModeView.removeAllViews();
                 }
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 2bfdc29..535f07f 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -151,7 +151,10 @@
         LOGE("Fail to open camera hardware (id=%d)", cameraId);
         return NULL;
     }
-    client = new Client(this, cameraClient, hardware, cameraId, callingPid);
+    CameraInfo info;
+    HAL_getCameraInfo(cameraId, &info);
+    client = new Client(this, cameraClient, hardware, cameraId, info.facing,
+                        callingPid);
     mClient[cameraId] = client;
     LOG1("CameraService::connect X");
     return client;
@@ -293,7 +296,7 @@
 CameraService::Client::Client(const sp<CameraService>& cameraService,
         const sp<ICameraClient>& cameraClient,
         const sp<CameraHardwareInterface>& hardware,
-        int cameraId, int clientPid) {
+        int cameraId, int cameraFacing, int clientPid) {
     int callingPid = getCallingPid();
     LOG1("Client::Client E (pid %d)", callingPid);
 
@@ -301,6 +304,7 @@
     mCameraClient = cameraClient;
     mHardware = hardware;
     mCameraId = cameraId;
+    mCameraFacing = cameraFacing;
     mClientPid = clientPid;
     mUseOverlay = mHardware->useOverlay();
     mMsgEnabled = 0;
@@ -318,8 +322,7 @@
 
     // Callback is disabled by default
     mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP;
-    mOrientation = 0;
-    mPreviewWindowFlag = 0;
+    mOrientation = getOrientation(0, mCameraFacing == CAMERA_FACING_FRONT);
     mOrientationChanged = false;
     mPlayShutterSound = true;
     cameraService->setCameraBusy(cameraId);
@@ -509,7 +512,7 @@
             result = setOverlay();
         } else if (mPreviewWindow != 0) {
             native_window_set_buffers_transform(mPreviewWindow.get(),
-                                                mPreviewWindowFlag);
+                                                mOrientation);
             result = mHardware->setPreviewWindow(mPreviewWindow);
         }
     }
@@ -637,7 +640,7 @@
     } else {
         if (mPreviewWindow != 0) {
             native_window_set_buffers_transform(mPreviewWindow.get(),
-                                                mPreviewWindowFlag);
+                                                mOrientation);
         }
         mHardware->setPreviewWindow(mPreviewWindow);
         result = mHardware->startPreview();
@@ -844,26 +847,10 @@
         if (mHardware->previewEnabled()) {
             return INVALID_OPERATION;
         }
-        switch (arg1) {
-            case 0:
-                orientation = ISurface::BufferHeap::ROT_0;
-                mPreviewWindowFlag = 0;
-                break;
-            case 90:
-                orientation = ISurface::BufferHeap::ROT_90;
-                mPreviewWindowFlag = NATIVE_WINDOW_TRANSFORM_ROT_90;
-                break;
-            case 180:
-                orientation = ISurface::BufferHeap::ROT_180;
-                mPreviewWindowFlag = NATIVE_WINDOW_TRANSFORM_ROT_180;
-                break;
-            case 270:
-                orientation = ISurface::BufferHeap::ROT_270;
-                mPreviewWindowFlag = NATIVE_WINDOW_TRANSFORM_ROT_270;
-                break;
-            default:
-                return BAD_VALUE;
-        }
+        // Mirror the preview if the camera is front-facing.
+        orientation = getOrientation(arg1, mCameraFacing == CAMERA_FACING_FRONT);
+        if (orientation == -1) return BAD_VALUE;
+
         if (mOrientation != orientation) {
             mOrientation = orientation;
             if (mOverlayRef != 0) mOrientationChanged = true;
@@ -1226,6 +1213,28 @@
     client->dataCallback(CAMERA_MSG_PREVIEW_FRAME, frame);
 }
 
+int CameraService::Client::getOrientation(int degrees, bool mirror) {
+    if (!mirror) {
+        if (degrees == 0) return 0;
+        else if (degrees == 90) return HAL_TRANSFORM_ROT_90;
+        else if (degrees == 180) return HAL_TRANSFORM_ROT_180;
+        else if (degrees == 270) return HAL_TRANSFORM_ROT_270;
+    } else {  // Do mirror (horizontal flip)
+        if (degrees == 0) {           // FLIP_H and ROT_0
+            return HAL_TRANSFORM_FLIP_H;
+        } else if (degrees == 90) {   // FLIP_H and ROT_90
+            return HAL_TRANSFORM_FLIP_H | HAL_TRANSFORM_ROT_90;
+        } else if (degrees == 180) {  // FLIP_H and ROT_180
+            return HAL_TRANSFORM_FLIP_V;
+        } else if (degrees == 270) {  // FLIP_H and ROT_270
+            return HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_ROT_90;
+        }
+    }
+    LOGE("Invalid setDisplayOrientation degrees=%d", degrees);
+    return -1;
+}
+
+
 // ----------------------------------------------------------------------------
 
 static const int kDumpLockRetries = 50;
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index b5b85bc..60e0d04 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -117,6 +117,7 @@
                                        const sp<ICameraClient>& cameraClient,
                                        const sp<CameraHardwareInterface>& hardware,
                                        int cameraId,
+                                       int cameraFacing,
                                        int clientPid);
                                 ~Client();
 
@@ -165,6 +166,8 @@
                                     const sp<IMemoryHeap>& heap,
                                     size_t offset, size_t size);
 
+        int                     getOrientation(int orientation, bool mirror);
+
         // these are initialized in the constructor.
         sp<CameraService>               mCameraService;  // immutable after constructor
         sp<ICameraClient>               mCameraClient;
@@ -180,7 +183,6 @@
         int                             mOrientation;     // Current display orientation
         // True if display orientation has been changed. This is only used in overlay.
         int                             mOrientationChanged;
-        int                             mPreviewWindowFlag;
         bool                            mPlayShutterSound;
 
         // Ensures atomicity among the public methods
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 50b3abe..90ebf3f 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -664,11 +664,11 @@
                 // backup.
                 RandomAccessFile in = null;
                 try {
-                    Slog.i(TAG, "Found stale backup journal, scheduling:");
+                    Slog.i(TAG, "Found stale backup journal, scheduling");
                     in = new RandomAccessFile(f, "r");
                     while (true) {
                         String packageName = in.readUTF();
-                        Slog.i(TAG, "    + " + packageName);
+                        Slog.i(TAG, "  " + packageName);
                         dataChangedImpl(packageName);
                     }
                 } catch (EOFException e) {
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 15eaa9e..8066fa7 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -700,9 +700,6 @@
     public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
             String tag, int id, Notification notification, int[] idOut)
     {
-        Slog.d(TAG, "enqueueNotificationWithTag: calling uid=" + callingUid 
-                + ", pid=" + callingPid);
-        
         checkIncomingCall(pkg);
 
         // Limit the number of notifications that any given package except the android
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 666bb26..7c758a2 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -2500,7 +2500,9 @@
             return;
         }
 
-        Log.d(TAG, "Scanning app dir " + dir);
+        if (false) {
+            Log.d(TAG, "Scanning app dir " + dir);
+        }
 
         int i;
         for (i=0; i<files.length; i++) {
@@ -2866,10 +2868,8 @@
                 TAG, "Scanning package " + pkg.packageName);
         if (mPackages.containsKey(pkg.packageName)
                 || mSharedLibraries.containsKey(pkg.packageName)) {
-            Slog.w(TAG, "*************************************************");
             Slog.w(TAG, "Application package " + pkg.packageName
                     + " already installed.  Skipping duplicate.");
-            Slog.w(TAG, "*************************************************");
             mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
             return null;
         }
@@ -7166,7 +7166,9 @@
                     pw.print("    resourcePath="); pw.println(ps.resourcePathString);
                     pw.print("    nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
                     pw.print("    obbPath="); pw.println(ps.obbPathString);
+                    pw.print("    versionCode="); pw.println(ps.versionCode);
                     if (ps.pkg != null) {
+                        pw.print("    versionName="); pw.println(ps.pkg.mVersionName);
                         pw.print("    dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
                         pw.print("    targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion);
                         if (ps.pkg.mOperationPending) {
@@ -7224,8 +7226,6 @@
                     pw.print("    pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags));
                             pw.print(" installStatus="); pw.print(ps.installStatus);
                             pw.print(" enabled="); pw.println(ps.enabled);
-                    pw.print("    versionCode="); pw.print(ps.versionCode);
-                            pw.print(" versionName="); pw.println(ps.pkg.mVersionName);
                     if (ps.disabledComponents.size() > 0) {
                         pw.println("    disabledComponents:");
                         for (String s : ps.disabledComponents) {
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 76588eb..b3e9f9d 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -1794,8 +1794,12 @@
 
     private void updateLightsLocked(int newState, int forceState) {
         final int oldState = mPowerState;
-        newState = applyButtonState(newState);
-        newState = applyKeyboardState(newState);
+        if ((newState & SCREEN_ON_BIT) != 0) {
+            // Only turn on the buttons or keyboard if the screen is also on.
+            // We should never see the buttons on but not the screen.
+            newState = applyButtonState(newState);
+            newState = applyKeyboardState(newState);
+        }
         final int realDifference = (newState ^ oldState);
         final int difference = realDifference | forceState;
         if (difference == 0) {
@@ -1898,6 +1902,16 @@
                     INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue);
         }
 
+        if (mSpew) {
+            Slog.d(TAG, "offMask=0x" + Integer.toHexString(offMask)
+                    + " dimMask=0x" + Integer.toHexString(dimMask)
+                    + " onMask=0x" + Integer.toHexString(onMask)
+                    + " difference=0x" + Integer.toHexString(difference)
+                    + " realDifference=0x" + Integer.toHexString(realDifference)
+                    + " forceState=0x" + Integer.toHexString(forceState)
+                    );
+        }
+
         if (offMask != 0) {
             if (mSpew) Slog.i(TAG, "Setting brightess off: " + offMask);
             setLightBrightness(offMask, Power.BRIGHTNESS_OFF);
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java
index 66e02146..400b31f 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/StatusBarManagerService.java
@@ -54,7 +54,7 @@
 public class StatusBarManagerService extends IStatusBarService.Stub
 {
     static final String TAG = "StatusBarManagerService";
-    static final boolean SPEW = true;
+    static final boolean SPEW = false;
 
     final Context mContext;
     Handler mHandler = new Handler();
@@ -111,22 +111,6 @@
     }
 
     // ================================================================================
-    // Constructing the view
-    // ================================================================================
-
-    public void systemReady() {
-    }
-
-    public void systemReady2() {
-        ComponentName cn = ComponentName.unflattenFromString(
-                mContext.getString(com.android.internal.R.string.config_statusBarComponent));
-        Intent intent = new Intent();
-        intent.setComponent(cn);
-        Slog.i(TAG, "Starting service: " + cn);
-        mContext.startService(intent);
-    }
-
-    // ================================================================================
     // From IStatusBarService
     // ================================================================================
     public void expand() {
@@ -161,7 +145,6 @@
         synchronized (mDisableRecords) {
             manageDisableListLocked(what, token, pkg);
             final int net = gatherDisableActionsLocked();
-            Slog.d(TAG, "disable... net=0x" + Integer.toHexString(net));
             if (net != mDisabled) {
                 mDisabled = net;
                 mHandler.post(new Runnable() {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ec12e80..46797c5 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -27,9 +27,11 @@
 import android.accounts.AccountManagerService;
 import android.app.ActivityManagerNative;
 import android.bluetooth.BluetoothAdapter;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentService;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
@@ -502,9 +504,6 @@
             notification.systemReady();
         }
 
-        if (statusBar != null) {
-            statusBar.systemReady();
-        }
         wm.systemReady();
 
         // Update the configuration for this context by hand, because we're going
@@ -523,7 +522,7 @@
         }
 
         // These are needed to propagate to the runnable below.
-        final StatusBarManagerService statusBarF = statusBar;
+        final Context contextF = context;
         final BatteryService batteryF = battery;
         final ConnectivityService connectivityF = connectivity;
         final DockObserver dockF = dock;
@@ -548,7 +547,7 @@
             public void run() {
                 Slog.i(TAG, "Making services ready");
 
-                if (statusBarF != null) statusBarF.systemReady2();
+                startSystemUi(contextF);
                 if (batteryF != null) batteryF.systemReady();
                 if (connectivityF != null) connectivityF.systemReady();
                 if (dockF != null) dockF.systemReady();
@@ -578,6 +577,14 @@
         Looper.loop();
         Slog.d(TAG, "System ServerThread is exiting!");
     }
+
+    static final void startSystemUi(Context context) {
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com.android.systemui",
+                    "com.android.systemui.SystemUIService"));
+        Slog.d(TAG, "Starting service: " + intent);
+        context.startService(intent);
+    }
 }
 
 public class SystemServer {
diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java
index 2b4845b..747af26 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -65,7 +65,9 @@
 
     private final Context mContext;
 
-    private final ArrayList<Record> mRecords = new ArrayList();
+    // access should be inside synchronized (mRecords) for these two fields
+    private final ArrayList<IBinder> mRemoveList = new ArrayList<IBinder>();
+    private final ArrayList<Record> mRecords = new ArrayList<Record>();
 
     private final IBatteryStats mBatteryStats;
 
@@ -158,7 +160,11 @@
                 r.events = events;
                 if (notifyNow) {
                     if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
-                        sendServiceState(r, mServiceState);
+                        try {
+                            r.callback.onServiceStateChanged(new ServiceState(mServiceState));
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
                     if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
                         try {
@@ -184,7 +190,11 @@
                         }
                     }
                     if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
-                        sendCellLocation(r, mCellLocation);
+                        try {
+                            r.callback.onCellLocationChanged(new Bundle(mCellLocation));
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
                     if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
                         try {
@@ -238,7 +248,6 @@
         if (!checkNotifyPermission("notifyCallState()")) {
             return;
         }
-        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mCallState = state;
             mCallIncomingNumber = incomingNumber;
@@ -247,11 +256,11 @@
                     try {
                         r.callback.onCallStateChanged(state, incomingNumber);
                     } catch (RemoteException ex) {
-                        removeList.add(r.binder);
+                        mRemoveList.add(r.binder);
                     }
                 }
             }
-            for (IBinder b : removeList) remove(b);
+            handleRemoveListLocked();
         }
         broadcastCallStateChanged(state, incomingNumber);
     }
@@ -260,13 +269,19 @@
         if (!checkNotifyPermission("notifyServiceState()")){
             return;
         }
+        Slog.i(TAG, "notifyServiceState: " + state);
         synchronized (mRecords) {
             mServiceState = state;
             for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
-                    sendServiceState(r, state);
+                    try {
+                        r.callback.onServiceStateChanged(new ServiceState(state));
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
                 }
             }
+            handleRemoveListLocked();
         }
         broadcastServiceStateChanged(state);
     }
@@ -275,12 +290,15 @@
         if (!checkNotifyPermission("notifySignalStrength()")) {
             return;
         }
-        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mSignalStrength = signalStrength;
             for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
-                    sendSignalStrength(r, signalStrength);
+                    try {
+                        r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
                 }
                 if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
                     try {
@@ -288,11 +306,11 @@
                         r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
                                 : gsmSignalStrength));
                     } catch (RemoteException ex) {
-                        removeList.add(r.binder);
+                        mRemoveList.add(r.binder);
                     }
                 }
             }
-            for (IBinder b : removeList) remove(b);
+            handleRemoveListLocked();
         }
         broadcastSignalStrengthChanged(signalStrength);
     }
@@ -301,7 +319,6 @@
         if (!checkNotifyPermission("notifyMessageWaitingChanged()")) {
             return;
         }
-        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mMessageWaiting = mwi;
             for (Record r : mRecords) {
@@ -309,11 +326,11 @@
                     try {
                         r.callback.onMessageWaitingIndicatorChanged(mwi);
                     } catch (RemoteException ex) {
-                        removeList.add(r.binder);
+                        mRemoveList.add(r.binder);
                     }
                 }
             }
-            for (IBinder b : removeList) remove(b);
+            handleRemoveListLocked();
         }
     }
 
@@ -321,7 +338,6 @@
         if (!checkNotifyPermission("notifyCallForwardingChanged()")) {
             return;
         }
-        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mCallForwarding = cfi;
             for (Record r : mRecords) {
@@ -329,11 +345,11 @@
                     try {
                         r.callback.onCallForwardingIndicatorChanged(cfi);
                     } catch (RemoteException ex) {
-                        removeList.add(r.binder);
+                        mRemoveList.add(r.binder);
                     }
                 }
             }
-            for (IBinder b : removeList) remove(b);
+            handleRemoveListLocked();
         }
     }
 
@@ -341,7 +357,6 @@
         if (!checkNotifyPermission("notifyDataActivity()" )) {
             return;
         }
-        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mDataActivity = state;
             for (Record r : mRecords) {
@@ -349,11 +364,11 @@
                     try {
                         r.callback.onDataActivity(state);
                     } catch (RemoteException ex) {
-                        removeList.add(r.binder);
+                        mRemoveList.add(r.binder);
                     }
                 }
             }
-            for (IBinder b : removeList) remove(b);
+            handleRemoveListLocked();
         }
     }
 
@@ -363,6 +378,9 @@
         if (!checkNotifyPermission("notifyDataConnection()" )) {
             return;
         }
+        Slog.i(TAG, "notifyDataConnection: state=" + state + " isDataConnectivityPossible="
+                + isDataConnectivityPossible + " reason=" + reason
+                + " apn=" + apn + " networkType=" + networkType);
         synchronized (mRecords) {
             boolean modified = false;
             if (state == TelephonyManager.DATA_CONNECTED) {
@@ -392,17 +410,16 @@
                 modified = true;
             }
             if (modified) {
-                ArrayList<IBinder> removeList = new ArrayList<IBinder>();
                 for (Record r : mRecords) {
                     if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
                         try {
                             r.callback.onDataConnectionStateChanged(state, networkType);
                         } catch (RemoteException ex) {
-                            removeList.add(r.binder);
+                            mRemoveList.add(r.binder);
                         }
                     }
                 }
-                for (IBinder b : removeList) remove(b);
+                handleRemoveListLocked();
             }
         }
         broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
@@ -438,36 +455,15 @@
             mCellLocation = cellLocation;
             for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
-                    sendCellLocation(r, cellLocation);
+                    try {
+                        r.callback.onCellLocationChanged(new Bundle(cellLocation));
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+
                 }
             }
-        }
-    }
-
-    /**
-     * Copy the service state object so they can't mess it up in the local calls
-     */
-    private void sendServiceState(Record r, ServiceState state) {
-        try {
-            r.callback.onServiceStateChanged(new ServiceState(state));
-        } catch (RemoteException ex) {
-            remove(r.binder);
-        }
-    }
-
-    private void sendCellLocation(Record r, Bundle cellLocation) {
-        try {
-            r.callback.onCellLocationChanged(new Bundle(cellLocation));
-        } catch (RemoteException ex) {
-            remove(r.binder);
-        }
-    }
-
-    private void sendSignalStrength(Record r, SignalStrength signalStrength) {
-        try {
-            r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
-        } catch (RemoteException ex) {
-            remove(r.binder);
+            handleRemoveListLocked();
         }
     }
 
@@ -628,4 +624,13 @@
                     android.Manifest.permission.READ_PHONE_STATE, null);
         }
     }
+
+    private void handleRemoveListLocked() {
+        if (mRemoveList.size() > 0) {
+            for (IBinder b: mRemoveList) {
+                remove(b);
+            }
+            mRemoveList.clear();
+        }
+    }
 }
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 9cd8fbf..4f75366 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -889,7 +889,9 @@
                 Settings.System.getInt(mContext.getContentResolver(),
                                        Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0);
             if (action.equals(Intent.ACTION_SCREEN_ON)) {
-                Slog.d(TAG, "ACTION_SCREEN_ON");
+                if (DBG) {
+                    Slog.d(TAG, "ACTION_SCREEN_ON");
+                }
                 mAlarmManager.cancel(mIdleIntent);
                 mDeviceIdle = false;
                 mScreenOff = false;
@@ -899,7 +901,9 @@
                 mWifiStateMachine.enableRssiPolling(true);
                 updateWifiState();
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                Slog.d(TAG, "ACTION_SCREEN_OFF");
+                if (DBG) {
+                    Slog.d(TAG, "ACTION_SCREEN_OFF");
+                }
                 mScreenOff = true;
                 mWifiStateMachine.enableRssiPolling(false);
                 /*
@@ -916,19 +920,26 @@
                         // but not as long as we would if connected (below)
                         // TODO - fix the race conditions and switch back to the immediate turn-off
                         long triggerTime = System.currentTimeMillis() + (2*60*1000); // 2 min
-                        Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for 120,000 ms");
+                        if (DBG) {
+                            Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for 120,000 ms");
+                        }
                         mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
                         //  // do not keep Wifi awake when screen is off if Wifi is not associated
                         //  mDeviceIdle = true;
                         //  updateWifiState();
                     } else {
                         long triggerTime = System.currentTimeMillis() + idleMillis;
-                        Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms");
+                        if (DBG) {
+                            Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis
+                                    + "ms");
+                        }
                         mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
                     }
                 }
             } else if (action.equals(ACTION_DEVICE_IDLE)) {
-                Slog.d(TAG, "got ACTION_DEVICE_IDLE");
+                if (DBG) {
+                    Slog.d(TAG, "got ACTION_DEVICE_IDLE");
+                }
                 mDeviceIdle = true;
                 reportStartWorkSource();
                 updateWifiState();
@@ -941,10 +952,15 @@
                  * the already-set timer.
                  */
                 int pluggedType = intent.getIntExtra("plugged", 0);
+                if (DBG) {
+                    Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType);
+                }
                 if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) &&
                         !shouldWifiStayAwake(stayAwakeConditions, pluggedType)) {
                     long triggerTime = System.currentTimeMillis() + idleMillis;
-                    Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms");
+                    if (DBG) {
+                        Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms");
+                    }
                     mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
                 }
                 mPluggedType = pluggedType;
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index af2fece..9bc24d2 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -501,6 +501,7 @@
         boolean mLocalOnly;
         ClipData mData;
         ClipDescription mDataDescription;
+        boolean mDragResult;
         float mCurrentX, mCurrentY;
         float mThumbOffsetX, mThumbOffsetY;
         InputChannel mServerChannel, mClientChannel;
@@ -551,6 +552,7 @@
                 mInputManager.unregisterInputChannel(mServerChannel);
                 InputQueue.unregisterInputChannel(mClientChannel);
                 mClientChannel.dispose();
+                mServerChannel.dispose();
                 mClientChannel = null;
                 mServerChannel = null;
             }
@@ -594,7 +596,7 @@
             if (mDragInProgress && newWin.isPotentialDragTarget()) {
                 DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED,
                         touchX - newWin.mFrame.left, touchY - newWin.mFrame.top,
-                        desc, null);
+                        desc, null, false);
                 try {
                     newWin.mClient.dispatchDragEvent(event);
                     // track each window that we've notified that the drag is starting
@@ -623,31 +625,42 @@
                     }
                 }
                 if (DEBUG_DRAG) {
-                    Slog.d(TAG, "sending DRAG_STARTED to new window " + newWin);
+                    Slog.d(TAG, "need to send DRAG_STARTED to new window " + newWin);
                 }
                 sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
             }
         }
 
-        void broadcastDragEnded() {
+        void broadcastDragEndedLw() {
             if (DEBUG_DRAG) {
                 Slog.d(TAG, "broadcasting DRAG_ENDED");
             }
-            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, 0, 0, null, null);
-            synchronized (mWindowMap) {
-                for (WindowState ws: mNotifiedWindows) {
-                    try {
-                        ws.mClient.dispatchDragEvent(evt);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Unable to drag-end window " + ws);
-                    }
+            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+                    0, 0, null, null, mDragResult);
+            for (WindowState ws: mNotifiedWindows) {
+                try {
+                    ws.mClient.dispatchDragEvent(evt);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to drag-end window " + ws);
                 }
-                mNotifiedWindows.clear();
-                mDragInProgress = false;
             }
+            mNotifiedWindows.clear();
+            mDragInProgress = false;
             evt.recycle();
         }
 
+        void endDragLw() {
+            mDragState.broadcastDragEndedLw();
+
+            // stop intercepting input
+            mDragState.unregister();
+            mInputMonitor.updateInputWindowsLw();
+
+            // free our resources and drop all the object references
+            mDragState.reset();
+            mDragState = null;
+        }
+
         void notifyMoveLw(float x, float y) {
             final int myPid = Process.myPid();
 
@@ -667,7 +680,7 @@
                     // force DRAG_EXITED_EVENT if appropriate
                     DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED,
                             x - mTargetWindow.mFrame.left, y - mTargetWindow.mFrame.top,
-                            null, null);
+                            null, null, false);
                     mTargetWindow.mClient.dispatchDragEvent(evt);
                     if (myPid != mTargetWindow.mSession.mPid) {
                         evt.recycle();
@@ -679,7 +692,7 @@
                     }
                     DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION,
                             x - touchedWin.mFrame.left, y - touchedWin.mFrame.top,
-                            null, null);
+                            null, null, false);
                     touchedWin.mClient.dispatchDragEvent(evt);
                     if (myPid != touchedWin.mSession.mPid) {
                         evt.recycle();
@@ -691,26 +704,43 @@
             mTargetWindow = touchedWin;
         }
 
-        // Tell the drop target about the data, and then broadcast the drag-ended notification
-        void notifyDropLw(float x, float y) {
+        // Tell the drop target about the data.  Returns 'true' if we can immediately
+        // dispatch the global drag-ended message, 'false' if we need to wait for a
+        // result from the recipient.
+        boolean notifyDropLw(float x, float y) {
             WindowState touchedWin = getTouchedWinAtPointLw(x, y);
-            if (touchedWin != null) {
-                if (DEBUG_DRAG) {
-                    Slog.d(TAG, "sending DROP to " + touchedWin);
-                }
-                final int myPid = Process.myPid();
-                DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP,
-                        x - touchedWin.mFrame.left, y - touchedWin.mFrame.top,
-                        null, mData);
-                try {
-                    touchedWin.mClient.dispatchDragEvent(evt);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "can't send drop notification to win " + touchedWin);
-                }
+            if (touchedWin == null) {
+                // "drop" outside a valid window -- no recipient to apply a
+                // timeout to, and we can send the drag-ended message immediately.
+                mDragResult = false;
+                return true;
+            }
+
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "sending DROP to " + touchedWin);
+            }
+            final int myPid = Process.myPid();
+            final IBinder token = touchedWin.mClient.asBinder();
+            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP,
+                    x - touchedWin.mFrame.left, y - touchedWin.mFrame.top,
+                    null, mData, false);
+            try {
+                touchedWin.mClient.dispatchDragEvent(evt);
+
+                // 5 second timeout for this window to respond to the drop
+                mH.removeMessages(H.DRAG_END_TIMEOUT, token);
+                Message msg = mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
+                mH.sendMessageDelayed(msg, 5000);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "can't send drop notification to win " + touchedWin);
+                return true;
+            } finally {
                 if (myPid != touchedWin.mSession.mPid) {
                     evt.recycle();
                 }
             }
+            mToken = token;
+            return false;
         }
 
         // Find the visible, touch-deliverable window under the given point
@@ -794,9 +824,8 @@
                         if (DEBUG_DRAG) Slog.d(TAG, "Got UP on move channel; dropping at "
                                 + newX + "," + newY);
                         synchronized (mWindowMap) {
-                            mDragState.notifyDropLw(newX, newY);
+                            endDrag = mDragState.notifyDropLw(newX, newY);
                         }
-                        endDrag = true;
                     } break;
 
                     case MotionEvent.ACTION_CANCEL: {
@@ -808,17 +837,9 @@
                     if (endDrag) {
                         if (DEBUG_DRAG) Slog.d(TAG, "Drag ended; tearing down state");
                         // tell all the windows that the drag has ended
-                        mDragState.broadcastDragEnded();
-
-                        // stop intercepting input
-                        mDragState.unregister();
                         synchronized (mWindowMap) {
-                            mInputMonitor.updateInputWindowsLw();
+                            mDragState.endDragLw();
                         }
-
-                        // free our resources and drop all the object references
-                        mDragState.reset();
-                        mDragState = null;
                     }
                 }
             } catch (Exception e) {
@@ -5356,8 +5377,8 @@
                     // SVGA or larger screens at medium density are the point
                     // at which we consider it to be an extra large screen.
                     mScreenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE;
-                } else if (longSize >= 640 && shortSize >= 480) {
-                    // VGA or larger screens at medium density are the point
+                } else if (longSize >= 530 && shortSize >= 400) {
+                    // SVGA or larger screens at high density are the point
                     // at which we consider it to be a large screen.
                     mScreenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE;
                 } else {
@@ -6222,8 +6243,14 @@
 
                 mDragState.register();
                 mInputMonitor.updateInputWindowsLw();
-                mInputManager.transferTouchFocus(callingWin.mInputChannel,
-                        mDragState.mServerChannel);
+                if (!mInputManager.transferTouchFocus(callingWin.mInputChannel,
+                        mDragState.mServerChannel)) {
+                    Slog.e(TAG, "Unable to transfer touch focus");
+                    mDragState.unregister();
+                    mDragState = null;
+                    mInputMonitor.updateInputWindowsLw();
+                    return false;
+                }
 
                 mDragState.mData = data;
                 mDragState.mCurrentX = touchX;
@@ -6251,15 +6278,43 @@
             return true;    // success!
         }
 
+        public void reportDropResult(IWindow window, boolean consumed) {
+            IBinder token = window.asBinder();
+            if (DEBUG_DRAG) {
+                Slog.d(TAG, "Drop result=" + consumed + " reported by " + token);
+            }
+
+            synchronized (mWindowMap) {
+                if (mDragState.mToken != token) {
+                    Slog.w(TAG, "Invalid drop-result claim by " + window);
+                    throw new IllegalStateException("reportDropResult() by non-recipient");
+                }
+
+                // The right window has responded, even if it's no longer around,
+                // so be sure to halt the timeout even if the later WindowState
+                // lookup fails.
+                mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
+
+                WindowState callingWin = windowForClientLocked(null, window, false);
+                if (callingWin == null) {
+                    Slog.w(TAG, "Bad result-reporting window " + window);
+                    return;  // !!! TODO: throw here?
+                }
+
+                mDragState.mDragResult = consumed;
+                mDragState.endDragLw();
+            }
+        }
+
         public void dragRecipientEntered(IWindow window) {
             if (DEBUG_DRAG) {
-                Slog.d(TAG, "Drag into new candidate view @ " + window);
+                Slog.d(TAG, "Drag into new candidate view @ " + window.asBinder());
             }
         }
 
         public void dragRecipientExited(IWindow window) {
             if (DEBUG_DRAG) {
-                Slog.d(TAG, "Drag from old candidate view @ " + window);
+                Slog.d(TAG, "Drag from old candidate view @ " + window.asBinder());
             }
         }
 
@@ -8321,6 +8376,7 @@
         public static final int SEND_NEW_CONFIGURATION = 18;
         public static final int REPORT_WINDOWS_CHANGE = 19;
         public static final int DRAG_START_TIMEOUT = 20;
+        public static final int DRAG_END_TIMEOUT = 21;
 
         private Session mLastReportedHold;
 
@@ -8671,12 +8727,27 @@
                     synchronized (mWindowMap) {
                         // !!! TODO: ANR the app that has failed to start the drag in time
                         if (mDragState != null) {
+                            mDragState.unregister();
+                            mInputMonitor.updateInputWindowsLw();
                             mDragState.reset();
                             mDragState = null;
                         }
                     }
+                    break;
                 }
 
+                case DRAG_END_TIMEOUT: {
+                    IBinder win = (IBinder)msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG, "Timeout ending drag to win " + win);
+                    }
+                    synchronized (mWindowMap) {
+                        // !!! TODO: ANR the drag-receiving app
+                        mDragState.mDragResult = false;
+                        mDragState.endDragLw();
+                    }
+                    break;
+                }
             }
         }
     }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index b0ace2f..8d36e4f 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -6676,6 +6676,9 @@
             if (info.violationNumThisLoop != 0) {
                 sb.append("Loop-Violation-Number: ").append(info.violationNumThisLoop).append("\n");
             }
+            if (info.numAnimationsRunning != 0) {
+                sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n");
+            }
             if (info != null && info.durationMillis != -1) {
                 sb.append("Duration-Millis: ").append(info.durationMillis).append("\n");
             }
@@ -7152,7 +7155,7 @@
                     } else if (adj >= SECONDARY_SERVER_ADJ) {
                         currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
                     } else if (adj >= HEAVY_WEIGHT_APP_ADJ) {
-                        currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_HEAVY_WEIGHT;
+                        currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE;
                     } else if (adj >= PERCEPTIBLE_APP_ADJ) {
                         currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
                     } else if (adj >= VISIBLE_APP_ADJ) {
@@ -7284,7 +7287,7 @@
                 }
                 return;
             } else if ("service".equals(cmd)) {
-                dumpService(fd, pw, args, opti, dumpAll);
+                dumpService(fd, pw, args, opti);
                 return;
             } else if ("services".equals(cmd) || "s".equals(cmd)) {
                 synchronized (this) {
@@ -7643,8 +7646,7 @@
      *  - the first arg isn't the flattened component name of an existing service:
      *    dump all services whose component contains the first arg as a substring
      */
-    protected void dumpService(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll) {
+    protected void dumpService(FileDescriptor fd, PrintWriter pw, String[] args, int opti) {
         String[] newArgs;
         String componentNameString;
         ServiceRecord r;
@@ -7664,7 +7666,7 @@
         }
 
         if (r != null) {
-            dumpService(fd, pw, r, newArgs, dumpAll);
+            dumpService(fd, pw, r, newArgs);
         } else {
             ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
             synchronized (this) {
@@ -7676,7 +7678,7 @@
                 }
             }
             for (int i=0; i<services.size(); i++) {
-                dumpService(fd, pw, services.get(i), newArgs, dumpAll);
+                dumpService(fd, pw, services.get(i), newArgs);
             }
         }
     }
@@ -7685,16 +7687,10 @@
      * Invokes IApplicationThread.dumpService() on the thread of the specified service if
      * there is a thread associated with the service.
      */
-    private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args,
-            boolean dumpAll) {
-        pw.println("  Service " + r.name.flattenToString());
-        if (dumpAll) {
-            synchronized (this) {
-                pw.print("  * "); pw.println(r);
-                r.dump(pw, "    ");
-            }
-            pw.println("");
-        }
+    private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args) {
+        pw.println("------------------------------------------------------------"
+                + "-------------------");
+        pw.println("APP SERVICE: " + r.name.flattenToString());
         if (r.app != null && r.app.thread != null) {
             try {
                 // flush anything that is already in the PrintWriter since the thread is going
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 0623f5b..d469d67 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -136,7 +136,6 @@
     static final long USB_DISCONNECT_DELAY = 1000;
 
     public Tethering(Context context, Looper looper) {
-        Log.d(TAG, "Tethering starting");
         mContext = context;
         mLooper = looper;
 
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index 4a6b5f4..755a228 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -1451,23 +1451,30 @@
                 mContext.getSystemService(Context.TELEPHONY_SERVICE);
         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
             GsmCellLocation gsm_cell = (GsmCellLocation) phone.getCellLocation();
-            if ((gsm_cell != null) && (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM)
-                    && (phone.getNetworkOperator().length() > 3)) {
+            if ((gsm_cell != null) && (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) &&
+                    (phone.getNetworkOperator() != null) &&
+                        (phone.getNetworkOperator().length() > 3)) {
                 int type;
                 int mcc = Integer.parseInt(phone.getNetworkOperator().substring(0,3));
                 int mnc = Integer.parseInt(phone.getNetworkOperator().substring(3));
-                if (phone.getNetworkType() == TelephonyManager.NETWORK_TYPE_UMTS)
+                int networkType = phone.getNetworkType();
+                if (networkType == TelephonyManager.NETWORK_TYPE_UMTS
+                    || networkType == TelephonyManager.NETWORK_TYPE_HSDPA
+                    || networkType == TelephonyManager.NETWORK_TYPE_HSUPA
+                    || networkType == TelephonyManager.NETWORK_TYPE_HSPA) {
                     type = AGPS_REF_LOCATION_TYPE_UMTS_CELLID;
-                else
+                } else {
                     type = AGPS_REF_LOCATION_TYPE_GSM_CELLID;
+                }
                 native_agps_set_ref_location_cellid(type, mcc, mnc,
                         gsm_cell.getLac(), gsm_cell.getCid());
-            }
-            else
+            } else {
                 Log.e(TAG,"Error getting cell location info.");
+            }
         }
-        else
+        else {
             Log.e(TAG,"CDMA not supported.");
+        }
     }
 
     private void sendMessage(int message, int arg, Object obj) {
diff --git a/services/java/com/android/server/location/GpsXtraDownloader.java b/services/java/com/android/server/location/GpsXtraDownloader.java
index bc96980..813d255 100644
--- a/services/java/com/android/server/location/GpsXtraDownloader.java
+++ b/services/java/com/android/server/location/GpsXtraDownloader.java
@@ -44,6 +44,7 @@
 public class GpsXtraDownloader {
 
     private static final String TAG = "GpsXtraDownloader";
+    static final boolean DEBUG = false;
     
     private Context mContext;
     private String[] mXtraServers;
@@ -107,7 +108,7 @@
 
     protected static byte[] doDownload(String url, boolean isProxySet, 
             String proxyHost, int proxyPort) {
-        if (Config.LOGD) Log.d(TAG, "Downloading XTRA data from " + url);
+        if (DEBUG) Log.d(TAG, "Downloading XTRA data from " + url);
 
         AndroidHttpClient client = null;
         try {
@@ -130,7 +131,7 @@
             HttpResponse response = client.execute(req);
             StatusLine status = response.getStatusLine();
             if (status.getStatusCode() != 200) { // HTTP 200 is success.
-                if (Config.LOGD) Log.d(TAG, "HTTP error: " + status.getReasonPhrase());
+                if (DEBUG) Log.d(TAG, "HTTP error: " + status.getReasonPhrase());
                 return null;
             }
 
@@ -159,7 +160,7 @@
             }
             return body;
         } catch (Exception e) {
-            if (Config.LOGD) Log.d(TAG, "error " + e);
+            if (DEBUG) Log.d(TAG, "error " + e);
         } finally {
             if (client != null) {
                 client.close();
diff --git a/services/jni/com_android_server_BatteryService.cpp b/services/jni/com_android_server_BatteryService.cpp
index 397a84a..d4513e9 100644
--- a/services/jni/com_android_server_BatteryService.cpp
+++ b/services/jni/com_android_server_BatteryService.cpp
@@ -164,7 +164,7 @@
     
     jboolean value = false;
     if (readFromFile(path, buf, SIZE) > 0) {
-        if (buf[0] == '1') {
+        if (buf[0] != '0') {
             value = true;
         }
     }
diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index e4825d0..1c2a2c8 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -24,6 +24,10 @@
 ifeq ($(TARGET_BOARD_PLATFORM), omap3)
 	LOCAL_CFLAGS += -DNO_RGBX_8888 -DHAS_PUSH_BUFFERS
 endif
+ifeq ($(TARGET_BOARD_PLATFORM), s5pc110)
+	LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY
+endif
+
 
 # need "-lrt" on Linux simulator to pick up clock_gettime
 ifeq ($(TARGET_SIMULATOR),true)
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index bd348bf..8926c03 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -199,8 +199,18 @@
      * Create our OpenGL ES context
      */
     
-    context = eglCreateContext(display, config, NULL, NULL);
-    
+
+    EGLint contextAttributes[] = {
+#ifdef EGL_IMG_context_priority
+#ifdef HAS_CONTEXT_PRIORITY
+#warning "using EGL_IMG_context_priority"
+        EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG,
+#endif
+#endif
+        EGL_NONE, EGL_NONE
+    };
+    context = eglCreateContext(display, config, NULL, contextAttributes);
+
     mDisplay = display;
     mConfig  = config;
     mSurface = surface;
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index 3f0ec0a..ab7b601 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -1626,7 +1626,15 @@
     }
 
 
-
+    private boolean hasMoreThanOneRingingCall() {
+        int count = 0;
+        for (Call call : mRingingCalls) {
+            if (call.getState().isRinging()) {
+                if (++count > 1) return true;
+            }
+        }
+        return false;
+    }
 
     private Handler mHandler = new Handler() {
 
@@ -1644,7 +1652,17 @@
                     break;
                 case EVENT_NEW_RINGING_CONNECTION:
                     if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_NEW_RINGING_CONNECTION)");
-                    mNewRingingConnectionRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+                    if (getActiveFgCallState().isDialing() || hasMoreThanOneRingingCall()) {
+                        Connection c = (Connection) ((AsyncResult) msg.obj).result;
+                        try {
+                            Log.d(LOG_TAG, "silently drop incoming call: " + c.getCall());
+                            c.getCall().hangup();
+                        } catch (CallStateException e) {
+                            Log.w(LOG_TAG, "new ringing connection", e);
+                        }
+                    } else {
+                        mNewRingingConnectionRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+                    }
                     break;
                 case EVENT_UNKNOWN_CONNECTION:
                     if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_UNKNOWN_CONNECTION)");
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index db16dec..82fcb6a 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -267,6 +267,7 @@
         Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
 
         CallerInfo info = getCallerInfo(context, contactUri);
+        info = doSecondaryLookupIfNecessary(context, number, info);
 
         // if no query results were returned with a viable number,
         // fill in the original number value we used to query with.
@@ -278,6 +279,30 @@
     }
 
     /**
+     * Performs another lookup if previous lookup fails and it's a SIP call
+     * and the peer's username is all numeric. Look up the username as it
+     * could be a PSTN number in the contact database.
+     *
+     * @param context the query context
+     * @param number the original phone number, could be a SIP URI
+     * @param previousResult the result of previous lookup
+     * @return previousResult if it's not the case
+     */
+    static CallerInfo doSecondaryLookupIfNecessary(Context context,
+            String number, CallerInfo previousResult) {
+        if (!previousResult.contactExists
+                && PhoneNumberUtils.isUriNumber(number)) {
+            String username = number.substring(0, number.indexOf('@'));
+            if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
+                previousResult = getCallerInfo(context,
+                        Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+                                Uri.encode(username)));
+            }
+        }
+        return previousResult;
+    }
+
+    /**
      * getCallerId: a convenience method to get the caller id for a given
      * number.
      *
@@ -409,6 +434,7 @@
                 .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
                 .append("\nemergency: " + mIsEmergency)
                 .append("\nvoicemail " + mIsVoiceMail)
+                .append("\ncontactExists " + contactExists)
                 .toString();
     }
 }
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index 8471efa..c47e076 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -38,7 +38,7 @@
  */
 
 public class CallerInfoAsyncQuery {
-    private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    private static final boolean DBG = false;
     private static final String LOG_TAG = "CallerInfoAsyncQuery";
 
     private static final int EVENT_NEW_QUERY = 1;
@@ -129,13 +129,13 @@
                     // However, if there is any code that this Handler calls (such as in
                     // super.handleMessage) that DOES place unexpected messages on the
                     // queue, then we need pass these messages on.
-                    if (DBG) log("Unexpected command (CookieWrapper is null): " + msg.what +
+                    if (DBG) Log.d(LOG_TAG, "Unexpected command (CookieWrapper is null): " + msg.what +
                             " ignored by CallerInfoWorkerHandler, passing onto parent.");
 
                     super.handleMessage(msg);
                 } else {
 
-                    if (DBG) log("Processing event: " + cw.event + " token (arg1): " + msg.arg1 +
+                    if (DBG) Log.d(LOG_TAG, "Processing event: " + cw.event + " token (arg1): " + msg.arg1 +
                         " command: " + msg.what + " query URI: " + sanitizeUriToString(args.uri));
 
                     switch (cw.event) {
@@ -191,7 +191,7 @@
          */
         @Override
         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            if (DBG) log("##### onQueryComplete() #####   query complete for token: " + token);
+            if (DBG) Log.d(LOG_TAG, "##### onQueryComplete() #####   query complete for token: " + token);
 
             //get the cookie and notify the listener.
             CookieWrapper cw = (CookieWrapper) cookie;
@@ -200,7 +200,7 @@
                 // from within this code.
                 // However, if there is any code that calls this method, we should
                 // check the parameters to make sure they're viable.
-                if (DBG) log("Cookie is null, ignoring onQueryComplete() request.");
+                if (DBG) Log.d(LOG_TAG, "Cookie is null, ignoring onQueryComplete() request.");
                 return;
             }
 
@@ -229,7 +229,15 @@
                     mCallerInfo = new CallerInfo().markAsVoiceMail();
                 } else {
                     mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);
-                    if (DBG) log("==> Got mCallerInfo: " + mCallerInfo);
+                    if (DBG) Log.d(LOG_TAG, "==> Got mCallerInfo: " + mCallerInfo);
+
+                    CallerInfo newCallerInfo = CallerInfo.doSecondaryLookupIfNecessary(
+                            mQueryContext, cw.number, mCallerInfo);
+                    if (newCallerInfo != mCallerInfo) {
+                        mCallerInfo = newCallerInfo;
+                        if (DBG) Log.d(LOG_TAG, "#####async contact look up with numeric username"
+                                + mCallerInfo);
+                    }
 
                     // Use the number entered by the user for display.
                     if (!TextUtils.isEmpty(cw.number)) {
@@ -241,7 +249,7 @@
                     }
                 }
 
-                if (DBG) log("constructing CallerInfo object for token: " + token);
+                if (DBG) Log.d(LOG_TAG, "constructing CallerInfo object for token: " + token);
 
                 //notify that we can clean up the queue after this.
                 CookieWrapper endMarker = new CookieWrapper();
@@ -251,7 +259,7 @@
 
             //notify the listener that the query is complete.
             if (cw.listener != null) {
-                if (DBG) log("notifying listener: " + cw.listener.getClass().toString() +
+                if (DBG) Log.d(LOG_TAG, "notifying listener: " + cw.listener.getClass().toString() +
                              " for token: " + token + mCallerInfo);
                 cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
             }
@@ -274,7 +282,7 @@
         CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
         c.allocate(context, contactRef);
 
-        if (DBG) log("starting query for URI: " + contactRef + " handler: " + c.toString());
+        if (DBG) Log.d(LOG_TAG, "starting query for URI: " + contactRef + " handler: " + c.toString());
 
         //create cookieWrapper, start query
         CookieWrapper cw = new CookieWrapper();
@@ -301,9 +309,9 @@
     public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
             OnQueryCompleteListener listener, Object cookie) {
         if (DBG) {
-            log("##### CallerInfoAsyncQuery startQuery()... #####");
-            log("- number: " + /*number*/ "xxxxxxx");
-            log("- cookie: " + cookie);
+            Log.d(LOG_TAG, "##### CallerInfoAsyncQuery startQuery()... #####");
+            Log.d(LOG_TAG, "- number: " + /*number*/ "xxxxxxx");
+            Log.d(LOG_TAG, "- cookie: " + cookie);
         }
 
         // Construct the URI object and query params, and start the query.
@@ -314,7 +322,7 @@
 
         if (PhoneNumberUtils.isUriNumber(number)) {
             // "number" is really a SIP address.
-            if (DBG) log("  - Treating number as a SIP address: " + /*number*/ "xxxxxxx");
+            if (DBG) Log.d(LOG_TAG, "  - Treating number as a SIP address: " + /*number*/ "xxxxxxx");
 
             // We look up SIP addresses directly in the Data table:
             contactRef = Data.CONTENT_URI;
@@ -346,11 +354,11 @@
         }
 
         if (DBG) {
-            log("==> contactRef: " + sanitizeUriToString(contactRef));
-            log("==> selection: " + selection);
+            Log.d(LOG_TAG, "==> contactRef: " + sanitizeUriToString(contactRef));
+            Log.d(LOG_TAG, "==> selection: " + selection);
             if (selectionArgs != null) {
                 for (int i = 0; i < selectionArgs.length; i++) {
-                    log("==> selectionArgs[" + i + "]: " + selectionArgs[i]);
+                    Log.d(LOG_TAG, "==> selectionArgs[" + i + "]: " + selectionArgs[i]);
                 }
             }
         }
@@ -388,7 +396,7 @@
      */
     public void addQueryListener(int token, OnQueryCompleteListener listener, Object cookie) {
 
-        if (DBG) log("adding listener to query: " + sanitizeUriToString(mHandler.mQueryUri) +
+        if (DBG) Log.d(LOG_TAG, "adding listener to query: " + sanitizeUriToString(mHandler.mQueryUri) +
                 " handler: " + mHandler.toString());
 
         //create cookieWrapper, add query request to end of queue.
@@ -436,11 +444,4 @@
             return "";
         }
     }
-
-    /**
-     * static logging method
-     */
-    private static void log(String msg) {
-        Log.d(LOG_TAG, msg);
-    }
 }
diff --git a/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
index 209fe06..c83f696 100644
--- a/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
@@ -50,7 +50,7 @@
      * calculating deltas.
      */
     private long connectTimeReal;
-    private long duration;
+    private long duration = -1L;
     private long holdingStartTime;  // The time when the Connection last transitioned
                             // into HOLDING
 
@@ -75,7 +75,7 @@
                 }
                 break;
             case DISCONNECTED:
-                duration = SystemClock.elapsedRealtime() - connectTimeReal;
+                duration = getDurationMillis();
                 disconnectTime = System.currentTimeMillis();
                 break;
             case HOLDING:
@@ -103,7 +103,7 @@
     public long getDurationMillis() {
         if (connectTimeReal == 0) {
             return 0;
-        } else if (duration == 0) {
+        } else if (duration < 0) {
             return SystemClock.elapsedRealtime() - connectTimeReal;
         } else {
             return duration;
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index 09d3c8e..ef31ddd 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -28,6 +28,7 @@
 import android.os.Message;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.telephony.Call;
@@ -483,7 +484,12 @@
 
         void merge(SipCall that) throws CallStateException {
             AudioGroup audioGroup = getAudioGroup();
-            for (Connection c : that.connections) {
+
+            // copy to an array to avoid concurrent modification as connections
+            // in that.connections will be removed in add(SipConnection).
+            Connection[] cc = that.connections.toArray(
+                    new Connection[that.connections.size()]);
+            for (Connection c : cc) {
                 SipConnection conn = (SipConnection) c;
                 add(conn);
                 if (conn.getState() == Call.State.HOLDING) {
@@ -676,6 +682,18 @@
             this(owner, callee, getUriString(callee));
         }
 
+        @Override
+        public String getCnapName() {
+            String displayName = mPeer.getDisplayName();
+            return TextUtils.isEmpty(displayName) ? null
+                                                  : displayName;
+        }
+
+        @Override
+        public int getNumberPresentation() {
+            return Connection.PRESENTATION_ALLOWED;
+        }
+
         void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
             setState(newState);
             mSipAudioCall = sipAudioCall;
@@ -704,7 +722,6 @@
             setState(Call.State.DIALING);
             mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
                     TIMEOUT_MAKE_CALL);
-            mSipAudioCall.setRingbackToneEnabled(false);
             mSipAudioCall.setListener(mAdapter);
         }
 
@@ -789,7 +806,9 @@
         @Override
         public void separate() throws CallStateException {
             synchronized (SipPhone.class) {
-                SipCall call = (SipCall) SipPhone.this.getBackgroundCall();
+                SipCall call = (getPhone() == SipPhone.this)
+                        ? (SipCall) SipPhone.this.getBackgroundCall()
+                        : (SipCall) SipPhone.this.getForegroundCall();
                 if (call.getState() != Call.State.IDLE) {
                     throw new CallStateException(
                             "cannot put conn back to a call in non-idle state: "
@@ -799,10 +818,20 @@
                         + mPeer.getUriString() + " from " + mOwner + " back to "
                         + call);
 
+                // separate the AudioGroup and connection from the original call
+                Phone originalPhone = getPhone();
                 AudioGroup audioGroup = call.getAudioGroup(); // may be null
                 call.add(this);
                 mSipAudioCall.setAudioGroup(audioGroup);
-                call.hold();
+
+                // put the original call to bg; and the separated call becomes
+                // fg if it was in bg
+                originalPhone.switchHoldingAndActive();
+
+                // start audio and notify the phone app of the state change
+                call = (SipCall) SipPhone.this.getForegroundCall();
+                mSipAudioCall.startAudio();
+                call.onConnectionStateChanged(this);
             }
         }
 
diff --git a/tests/DpiTest/AndroidManifest.xml b/tests/DpiTest/AndroidManifest.xml
index 68ecc6e..a4d8c79 100644
--- a/tests/DpiTest/AndroidManifest.xml
+++ b/tests/DpiTest/AndroidManifest.xml
@@ -18,6 +18,12 @@
     package="com.google.android.test.dpi">
     <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="3" />
     <supports-screens android:smallScreens="true" />
+    <compatible-screens>
+        <screen android:screenSize="small" android:screenDensity="ldpi" />
+        <screen android:screenSize="small" android:screenDensity="xhdpi" />
+        <screen android:screenSize="large" android:screenDensity="hdpi" />
+        <screen android:screenSize="xlarge" android:screenDensity="mdpi" />
+    </compatible-screens>
     <application android:label="DpiTest">
         <activity android:name="DpiTestActivity">
             <intent-filter>
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 4469e68..e9833c9 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -334,13 +334,15 @@
     REQ_FIVE_WAY_NAV_ATTR = 0x01010232,
     TARGET_SDK_VERSION_ATTR = 0x01010270,
     TEST_ONLY_ATTR = 0x01010272,
-    DENSITY_ATTR = 0x0101026c,
+    ANY_DENSITY_ATTR = 0x0101026c,
     GL_ES_VERSION_ATTR = 0x01010281,
     SMALL_SCREEN_ATTR = 0x01010284,
     NORMAL_SCREEN_ATTR = 0x01010285,
     LARGE_SCREEN_ATTR = 0x01010286,
     XLARGE_SCREEN_ATTR = 0x010102bf,
     REQUIRED_ATTR = 0x0101028e,
+    SCREEN_SIZE_ATTR = 0x010102ca,
+    SCREEN_DENSITY_ATTR = 0x010102cb,
 };
 
 const char *getComponentName(String8 &pkgName, String8 &componentName) {
@@ -357,6 +359,42 @@
     return retStr.string();
 }
 
+static void printCompatibleScreens(ResXMLTree& tree) {
+    size_t len;
+    ResXMLTree::event_code_t code;
+    int depth = 0;
+    bool first = true;
+    printf("compatible-screens:");
+    while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+        if (code == ResXMLTree::END_TAG) {
+            depth--;
+            if (depth < 0) {
+                break;
+            }
+            continue;
+        }
+        if (code != ResXMLTree::START_TAG) {
+            continue;
+        }
+        depth++;
+        String8 tag(tree.getElementName(&len));
+        if (tag == "screen") {
+            int32_t screenSize = getIntegerAttribute(tree,
+                    SCREEN_SIZE_ATTR, NULL, -1);
+            int32_t screenDensity = getIntegerAttribute(tree,
+                    SCREEN_DENSITY_ATTR, NULL, -1);
+            if (screenSize > 0 && screenDensity > 0) {
+                if (!first) {
+                    printf(",");
+                }
+                first = false;
+                printf("'%d/%d'", screenSize, screenDensity);
+            }
+        }
+    }
+    printf("\n");
+}
+
 /*
  * Handle the "dump" command, to extract select data from an archive.
  */
@@ -575,6 +613,7 @@
             int normalScreen = 1;
             int largeScreen = 1;
             int xlargeScreen = 1;
+            int anyDensity = 1;
             String8 pkg;
             String8 activityName;
             String8 activityLabel;
@@ -742,14 +781,6 @@
                             printf(" reqFiveWayNav='%d'", reqFiveWayNav);
                         }
                         printf("\n");
-                    } else if (tag == "supports-density") {
-                        int32_t dens = getIntegerAttribute(tree, DENSITY_ATTR, &error);
-                        if (error != "") {
-                            fprintf(stderr, "ERROR getting 'android:density' attribute: %s\n",
-                                    error.string());
-                            goto bail;
-                        }
-                        printf("supports-density:'%d'\n", dens);
                     } else if (tag == "supports-screens") {
                         smallScreen = getIntegerAttribute(tree,
                                 SMALL_SCREEN_ATTR, NULL, 1);
@@ -759,6 +790,8 @@
                                 LARGE_SCREEN_ATTR, NULL, 1);
                         xlargeScreen = getIntegerAttribute(tree,
                                 XLARGE_SCREEN_ATTR, NULL, 1);
+                        anyDensity = getIntegerAttribute(tree,
+                                ANY_DENSITY_ATTR, NULL, 1);
                     } else if (tag == "uses-feature") {
                         String8 name = getAttribute(tree, NAME_ATTR, &error);
 
@@ -883,6 +916,9 @@
                                     error.string());
                                 goto bail;
                         }
+                    } else if (tag == "compatible-screens") {
+                        printCompatibleScreens(tree);
+                        depth--;
                     }
                 } else if (depth == 3 && withinApplication) {
                     withinActivity = false;
@@ -1109,6 +1145,9 @@
                 // Introduced in Honeycomb.
                 xlargeScreen = targetSdk >= 10 ? -1 : 0;
             }
+            if (anyDensity > 0) {
+                anyDensity = targetSdk >= 4 ? -1 : 0;
+            }
             printf("supports-screens:");
             if (smallScreen != 0) printf(" 'small'");
             if (normalScreen != 0) printf(" 'normal'");
@@ -1116,6 +1155,8 @@
             if (xlargeScreen != 0) printf(" 'xlarge'");
             printf("\n");
 
+            printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
+
             printf("locales:");
             Vector<String8> locales;
             res.getLocales(&locales);
diff --git a/tools/layoutlib/bridge/.classpath b/tools/layoutlib/bridge/.classpath
index 175a98b..70140d8 100644
--- a/tools/layoutlib/bridge/.classpath
+++ b/tools/layoutlib/bridge/.classpath
@@ -4,7 +4,7 @@
 	<classpathentry kind="src" path="tests"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/layoutlib_api"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/layoutlib_api/layoutlib_api-prebuilt.jar"/>
 	<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
 	<classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/layoutlib.jar" sourcepath="/ANDROID_SRC/frameworks/base/core/java"/>
 	<classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/ninepatch.jar" sourcepath="/ANDROID_SRC/development/tools/ninepatch/src"/>
diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk
index b2010d5..b7a602a 100644
--- a/tools/layoutlib/bridge/Android.mk
+++ b/tools/layoutlib/bridge/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_JAVA_LIBRARIES := \
 	kxml2-2.3.0 \
-	layoutlib_api \
+	layoutlib_api-prebuilt \
 	ninepatch
 
 LOCAL_STATIC_JAVA_LIBRARIES := temp_layoutlib
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 0553f5e..eb0eba2 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -1093,6 +1093,11 @@
         }
 
         @SuppressWarnings("unused")
+        public void reportDropResult(IWindow window, boolean consumed) throws RemoteException {
+            // pass for now
+        }
+
+        @SuppressWarnings("unused")
         public void dragRecipientEntered(IWindow window) throws RemoteException {
             // pass for now
         }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 590923f..a9ede26 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -96,6 +96,7 @@
             methods.add(methodName);
         }
         for (String className : createInfo.getDelegateClassNatives()) {
+            className = binaryToInternalClassName(className);
             Set<String> methods = mDelegateMethods.get(className);
             if (methods == null) {
                 methods = new HashSet<String>();
@@ -312,6 +313,11 @@
             rv = new RenameClassAdapter(cw, className, newName);
         }
 
+        ClassVisitor cv = new TransformClassAdapter(mLog, mStubMethods,
+                mDeleteReturns.get(className),
+                newName, rv,
+                stubNativesOnly, stubNativesOnly || hasNativeMethods);
+
         Set<String> delegateMethods = mDelegateMethods.get(className);
         if (delegateMethods != null && !delegateMethods.isEmpty()) {
             // If delegateMethods only contains one entry ALL_NATIVES and the class is
@@ -319,14 +325,10 @@
             if (hasNativeMethods ||
                     !(delegateMethods.size() == 1 &&
                             delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
-                rv = new DelegateClassAdapter(mLog, rv, className, delegateMethods);
+                cv = new DelegateClassAdapter(mLog, cv, className, delegateMethods);
             }
         }
 
-        TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
-                mDeleteReturns.get(className),
-                newName, rv,
-                stubNativesOnly, stubNativesOnly || hasNativeMethods);
         cr.accept(cv, 0 /* flags */);
         return cw.toByteArray();
     }
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index 9ad2e6e..7d80796 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -28,11 +28,8 @@
 import org.junit.Test;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.util.TraceClassVisitor;
 
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -178,24 +175,26 @@
             };
             cl2.testModifiedInstance();
 
-        } catch (Throwable t) {
-            // For debugging, dump the bytecode of the class in case of unexpected error.
-            StringWriter sw = new StringWriter();
-            PrintWriter pw = new PrintWriter(sw);
-            TraceClassVisitor tcv = new TraceClassVisitor(pw);
-
-            ClassReader cr2 = new ClassReader(bytes);
-            cr2.accept(tcv, 0 /* flags */);
-
-            String msg = "\n" + t.getClass().getCanonicalName();
-            if (t.getMessage() != null) {
-                msg += ": " + t.getMessage();
-            }
-            msg = msg + "\nBytecode dump:\n" + sw.toString();
-
-            // Re-throw exception with new message
-            RuntimeException ex = new RuntimeException(msg, t);
-            throw ex;
+        // This code block is useful for debugging. However to make it work you need to
+        // pull in the org.objectweb.asm.util.TraceClassVisitor class and associated
+        // utilities which are found in the ASM source jar.
+        //
+        // } catch (Throwable t) {
+        //     For debugging, dump the bytecode of the class in case of unexpected error.
+        //     StringWriter sw = new StringWriter();
+        //     PrintWriter pw = new PrintWriter(sw);
+        //     TraceClassVisitor tcv = new TraceClassVisitor(pw);
+        //     ClassReader cr2 = new ClassReader(bytes);
+        //     cr2.accept(tcv, 0 /* flags */);
+        //     String msg = "\n" + t.getClass().getCanonicalName();
+        //     if (t.getMessage() != null) {
+        //       msg += ": " + t.getMessage();
+        //     }
+        //     msg = msg + "\nBytecode dump:\n" + sw.toString();
+        //  // Re-throw exception with new message
+        //     RuntimeException ex = new RuntimeException(msg, t);
+        //     throw ex;
+        } finally {
         }
     }
 
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index d859c4c..0179748 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -18,10 +18,6 @@
 
 import android.content.Context;
 import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.media.ToneGenerator;
-import android.net.Uri;
 import android.net.rtp.AudioCodec;
 import android.net.rtp.AudioGroup;
 import android.net.rtp.AudioStream;
@@ -30,8 +26,6 @@
 import android.net.wifi.WifiManager;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.Vibrator;
-import android.provider.Settings;
 import android.util.Log;
 
 import java.io.IOException;
@@ -47,6 +41,16 @@
  * facilitates instantiating a {@code SipAudioCall} object for making/receiving
  * calls. See {@link SipManager#makeAudioCall} and
  * {@link SipManager#takeAudioCall}.
+ *
+ * <p>Requires permissions to use this class:
+ *   {@link android.Manifest.permission#INTERNET} and
+ *   {@link android.Manifest.permission#USE_SIP}.
+ * <br/>Requires permissions to {@link #startAudio}:
+ *   {@link android.Manifest.permission#RECORD_AUDIO},
+ *   {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
+ *   {@link android.Manifest.permission#WAKE_LOCK}.
+ * <br/>Requires permissions to {@link #setSpeakerMode}:
+ *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
  */
 public class SipAudioCall {
     private static final String TAG = SipAudioCall.class.getSimpleName();
@@ -175,11 +179,6 @@
     private boolean mMuted = false;
     private boolean mHold = false;
 
-    private boolean mRingbackToneEnabled = true;
-    private boolean mRingtoneEnabled = true;
-    private Ringtone mRingtone;
-    private ToneGenerator mRingbackTone;
-
     private SipProfile mPendingCallRequest;
     private WifiManager mWm;
     private WifiManager.WifiLock mWifiHighPerfLock;
@@ -285,8 +284,6 @@
 
     private synchronized void close(boolean closeRtp) {
         if (closeRtp) stopCall(RELEASE_SOCKET);
-        stopRingbackTone();
-        stopRinging();
 
         mInCall = false;
         mHold = false;
@@ -366,7 +363,6 @@
             @Override
             public void onRingingBack(SipSession session) {
                 Log.d(TAG, "sip call ringing back: " + session);
-                if (!mInCall) startRingbackTone();
                 Listener listener = mListener;
                 if (listener != null) {
                     try {
@@ -403,8 +399,6 @@
             @Override
             public void onCallEstablished(SipSession session,
                     String sessionDescription) {
-                stopRingbackTone();
-                stopRinging();
                 mPeerSd = sessionDescription;
                 Log.v(TAG, "onCallEstablished()" + mPeerSd);
 
@@ -533,10 +527,6 @@
             Log.v(TAG, "attachCall()" + mPeerSd);
             try {
                 session.setListener(createListener());
-
-                if (getState() == SipSession.State.INCOMING_CALL) {
-                    startRinging();
-                }
             } catch (Throwable e) {
                 Log.e(TAG, "attachCall()", e);
                 throwSipException(e);
@@ -580,7 +570,6 @@
      */
     public void endCall() throws SipException {
         synchronized (this) {
-            stopRinging();
             stopCall(RELEASE_SOCKET);
             mInCall = false;
 
@@ -625,7 +614,6 @@
      */
     public void answerCall(int timeout) throws SipException {
         synchronized (this) {
-            stopRinging();
             try {
                 mAudioStream = new AudioStream(InetAddress.getByName(
                         getLocalIp()));
@@ -798,7 +786,11 @@
         }
     }
 
-    /** Puts the device to speaker mode. */
+    /**
+     * Puts the device to speaker mode.
+     * <p>Requires permission:
+     *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
+     */
     public void setSpeakerMode(boolean speakerMode) {
         synchronized (this) {
             ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
@@ -898,6 +890,10 @@
     /**
      * Starts the audio for the established call. This method should be called
      * after {@link Listener#onCallEstablished} is called.
+     * <p>Requires permission:
+     *   {@link android.Manifest.permission#RECORD_AUDIO},
+     *   {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
+     *   {@link android.Manifest.permission#WAKE_LOCK}.
      */
     public void startAudio() {
         try {
@@ -1026,69 +1022,6 @@
         return mSipSession.getLocalIp();
     }
 
-
-    /**
-     * Enables/disables the ring-back tone.
-     *
-     * @param enabled true to enable; false to disable
-     */
-    public void setRingbackToneEnabled(boolean enabled) {
-        synchronized (this) {
-            mRingbackToneEnabled = enabled;
-        }
-    }
-
-    /**
-     * Enables/disables the ring tone.
-     *
-     * @param enabled true to enable; false to disable
-     */
-    public void setRingtoneEnabled(boolean enabled) {
-        synchronized (this) {
-            mRingtoneEnabled = enabled;
-        }
-    }
-
-    private void startRingbackTone() {
-        if (!mRingbackToneEnabled) return;
-        if (mRingbackTone == null) {
-            // The volume relative to other sounds in the stream
-            int toneVolume = 80;
-            mRingbackTone = new ToneGenerator(
-                    AudioManager.STREAM_VOICE_CALL, toneVolume);
-        }
-        mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L);
-    }
-
-    private void stopRingbackTone() {
-        if (mRingbackTone != null) {
-            mRingbackTone.stopTone();
-            mRingbackTone.release();
-            mRingbackTone = null;
-        }
-    }
-
-    private void startRinging() {
-        if (!mRingtoneEnabled) return;
-        ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
-                .vibrate(new long[] {0, 1000, 1000}, 1);
-        AudioManager am = (AudioManager)
-                mContext.getSystemService(Context.AUDIO_SERVICE);
-        if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) {
-            String ringtoneUri =
-                    Settings.System.DEFAULT_RINGTONE_URI.toString();
-            mRingtone = RingtoneManager.getRingtone(mContext,
-                    Uri.parse(ringtoneUri));
-            mRingtone.play();
-        }
-    }
-
-    private void stopRinging() {
-        ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
-                .cancel();
-        if (mRingtone != null) mRingtone.stop();
-    }
-
     private void throwSipException(Throwable throwable) throws SipException {
         if (throwable instanceof SipException) {
             throw (SipException) throwable;
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 2f03e34..38d2b0c 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -50,6 +50,9 @@
  * </ul>
  * {@code SipManager} can only be instantiated if SIP API is supported by the
  * device. (See {@link #isApiSupported}).
+ * <p>Requires permissions to use this class:
+ *   {@link android.Manifest.permission#INTERNET} and
+ *   {@link android.Manifest.permission#USE_SIP}.
  */
 public class SipManager {
     /**
@@ -351,17 +354,6 @@
     }
 
     /**
-     * The method calls {@code takeAudioCall(incomingCallIntent,
-     * listener, true}.
-     *
-     * @see #takeAudioCall(Intent, SipAudioCall.Listener, boolean)
-     */
-    public SipAudioCall takeAudioCall(Intent incomingCallIntent,
-            SipAudioCall.Listener listener) throws SipException {
-        return takeAudioCall(incomingCallIntent, listener, true);
-    }
-
-    /**
      * Creates a {@link SipAudioCall} to take an incoming call. Before the call
      * is returned, the listener will receive a
      * {@link SipAudioCall.Listener#onRinging}
@@ -374,8 +366,7 @@
      * @throws SipException if calling the SIP service results in an error
      */
     public SipAudioCall takeAudioCall(Intent incomingCallIntent,
-            SipAudioCall.Listener listener, boolean ringtoneEnabled)
-            throws SipException {
+            SipAudioCall.Listener listener) throws SipException {
         if (incomingCallIntent == null) return null;
 
         String callId = getCallId(incomingCallIntent);
@@ -394,7 +385,6 @@
             if (session == null) return null;
             SipAudioCall call = new SipAudioCall(
                     mContext, session.getLocalProfile());
-            call.setRingtoneEnabled(ringtoneEnabled);
             call.attachCall(new SipSession(session), offerSd);
             call.setListener(listener);
             return call;
diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java
index 2514262..13e6f14 100644
--- a/voip/java/com/android/server/sip/SipHelper.java
+++ b/voip/java/com/android/server/sip/SipHelper.java
@@ -365,6 +365,10 @@
             Response response = mMessageFactory.createResponse(
                     Response.BUSY_HERE, request);
 
+            if (inviteTransaction == null) {
+                inviteTransaction = getServerTransaction(event);
+            }
+
             if (inviteTransaction.getState() != TransactionState.COMPLETED) {
                 if (DEBUG) Log.d(TAG, "send BUSY HERE: " + response);
                 inviteTransaction.sendResponse(response);
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index 1df08c0..f480fec 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -68,7 +68,7 @@
 public final class SipService extends ISipService.Stub {
     static final String TAG = "SipService";
     static final boolean DEBUGV = false;
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
     private static final boolean DEBUG_TIMER = DEBUG && false;
     private static final int EXPIRY_TIME = 3600;
     private static final int SHORT_EXPIRY_TIME = 10;
@@ -79,6 +79,7 @@
     private String mNetworkType;
     private boolean mConnected;
     private WakeupTimer mTimer;
+    private WifiScanProcess mWifiScanProcess;
     private WifiManager.WifiLock mWifiLock;
     private boolean mWifiOnly;
 
@@ -104,7 +105,7 @@
         if (SipManager.isApiSupported(context)) {
             ServiceManager.addService("sip", new SipService(context));
             context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP));
-            Log.i(TAG, "SIP service started");
+            if (DEBUG) Log.i(TAG, "SIP service started");
         }
     }
 
@@ -222,7 +223,7 @@
         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
         if (group == null) return;
         if (!isCallerCreatorOrRadio(group)) {
-            Log.d(TAG, "only creator or radio can close this profile");
+            Log.w(TAG, "only creator or radio can close this profile");
             return;
         }
 
@@ -244,7 +245,7 @@
         if (isCallerCreatorOrRadio(group)) {
             return group.isOpened();
         } else {
-            Log.i(TAG, "only creator or radio can query on the profile");
+            Log.w(TAG, "only creator or radio can query on the profile");
             return false;
         }
     }
@@ -257,7 +258,7 @@
         if (isCallerCreatorOrRadio(group)) {
             return group.isRegistered();
         } else {
-            Log.i(TAG, "only creator or radio can query on the profile");
+            Log.w(TAG, "only creator or radio can query on the profile");
             return false;
         }
     }
@@ -271,7 +272,7 @@
         if (isCallerCreator(group)) {
             group.setListener(listener);
         } else {
-            Log.i(TAG, "only creator can set listener on the profile");
+            Log.w(TAG, "only creator can set listener on the profile");
         }
     }
 
@@ -285,7 +286,7 @@
             SipSessionGroupExt group = createGroup(localProfile);
             return group.createSession(listener);
         } catch (SipException e) {
-            Log.w(TAG, "createSession()", e);
+            if (DEBUG) Log.d(TAG, "createSession()", e);
             return null;
         }
     }
@@ -303,7 +304,7 @@
             s.connect(InetAddress.getByName("192.168.1.1"), 80);
             return s.getLocalAddress().getHostAddress();
         } catch (IOException e) {
-            Log.w(TAG, "determineLocalIp()", e);
+            if (DEBUG) Log.d(TAG, "determineLocalIp()", e);
             // dont do anything; there should be a connectivity change going
             return null;
         }
@@ -371,6 +372,7 @@
                     mContext.getSystemService(Context.WIFI_SERVICE))
                     .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
             mWifiLock.acquire();
+            if (!mConnected) startWifiScanner();
         }
     }
 
@@ -379,6 +381,20 @@
             if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ release wifi lock");
             mWifiLock.release();
             mWifiLock = null;
+            stopWifiScanner();
+        }
+    }
+
+    private synchronized void startWifiScanner() {
+        if (mWifiScanProcess == null) {
+            mWifiScanProcess = new WifiScanProcess();
+        }
+        mWifiScanProcess.start();
+    }
+
+    private synchronized void stopWifiScanner() {
+        if (mWifiScanProcess != null) {
+            mWifiScanProcess.stop();
         }
     }
 
@@ -413,8 +429,10 @@
                 for (SipSessionGroupExt group : mSipGroups.values()) {
                     group.onConnectivityChanged(true);
                 }
+                if (isWifi && (mWifiLock != null)) stopWifiScanner();
             } else {
                 mMyWakeLock.reset(); // in case there's a leak
+                if (isWifi && (mWifiLock != null)) startWifiScanner();
             }
         } catch (SipException e) {
             Log.e(TAG, "onConnectivityChanged()", e);
@@ -423,13 +441,41 @@
 
     private synchronized void addPendingSession(ISipSession session) {
         try {
+            cleanUpPendingSessions();
             mPendingSessions.put(session.getCallId(), session);
+            if (DEBUG) Log.d(TAG, "#pending sess=" + mPendingSessions.size());
         } catch (RemoteException e) {
             // should not happen with a local call
             Log.e(TAG, "addPendingSession()", e);
         }
     }
 
+    private void cleanUpPendingSessions() throws RemoteException {
+        Map.Entry<String, ISipSession>[] entries =
+                mPendingSessions.entrySet().toArray(
+                new Map.Entry[mPendingSessions.size()]);
+        for (Map.Entry<String, ISipSession> entry : entries) {
+            if (entry.getValue().getState() != SipSession.State.INCOMING_CALL) {
+                mPendingSessions.remove(entry.getKey());
+            }
+        }
+    }
+
+    private synchronized boolean callingSelf(SipSessionGroupExt ringingGroup,
+            SipSessionGroup.SipSessionImpl ringingSession) {
+        String callId = ringingSession.getCallId();
+        for (SipSessionGroupExt group : mSipGroups.values()) {
+            if ((group != ringingGroup) && group.containsSession(callId)) {
+                if (DEBUG) Log.d(TAG, "call self: "
+                        + ringingSession.getLocalProfile().getUriString()
+                        + " -> " + group.getLocalProfile().getUriString());
+                return true;
+            }
+        }
+        return false;
+    }
+
+
     private class SipSessionGroupExt extends SipSessionAdapter {
         private SipSessionGroup mSipGroup;
         private PendingIntent mIncomingCallPendingIntent;
@@ -452,6 +498,10 @@
             return mSipGroup.getLocalProfile();
         }
 
+        public boolean containsSession(String callId) {
+            return mSipGroup.containsSession(callId);
+        }
+
         // network connectivity is tricky because network can be disconnected
         // at any instant so need to deal with exceptions carefully even when
         // you think you are connected
@@ -467,7 +517,7 @@
                     return createSipSessionGroup(null, localProfile, password);
                 } else {
                     // recursive
-                    Log.wtf(TAG, "impossible!");
+                    Log.wtf(TAG, "impossible! recursive!");
                     throw new RuntimeException("createSipSessionGroup");
                 }
             }
@@ -551,7 +601,7 @@
                     (SipSessionGroup.SipSessionImpl) s;
             synchronized (SipService.this) {
                 try {
-                    if (!isRegistered()) {
+                    if (!isRegistered() || callingSelf(this, session)) {
                         session.endCall();
                         return;
                     }
@@ -592,6 +642,36 @@
         }
     }
 
+    private class WifiScanProcess implements Runnable {
+        private static final String TAG = "\\WIFI_SCAN/";
+        private static final int INTERVAL = 60;
+        private boolean mRunning = false;
+
+        private WifiManager mWifiManager;
+
+        public void start() {
+            if (mRunning) return;
+            mRunning = true;
+            mTimer.set(INTERVAL * 1000, this);
+        }
+
+        WifiScanProcess() {
+            mWifiManager = (WifiManager)
+                    mContext.getSystemService(Context.WIFI_SERVICE);
+        }
+
+        public void run() {
+            // scan and associate now
+            if (DEBUGV) Log.v(TAG, "just wake up here for wifi scanning...");
+            mWifiManager.startScanActive();
+        }
+
+        public void stop() {
+            mRunning = false;
+            mTimer.cancel(this);
+        }
+    }
+
     // KeepAliveProcess is controlled by AutoRegistrationProcess.
     // All methods will be invoked in sync with SipService.this.
     private class KeepAliveProcess implements Runnable {
@@ -1028,8 +1108,6 @@
         }
     }
 
-    // TODO: clean up pending SipSession(s) periodically
-
     /**
      * Timer that can schedule events to occur even when the device is in sleep.
      * Only used internally in this package.
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index bb246a6..50ce7dc 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -231,6 +231,10 @@
         }
     }
 
+    synchronized boolean containsSession(String callId) {
+        return mSessionMap.containsKey(callId);
+    }
+
     private synchronized SipSessionImpl getSipSession(EventObject event) {
         String key = SipHelper.getCallId(event);
         SipSessionImpl session = mSessionMap.get(key);
@@ -582,6 +586,7 @@
         }
 
         private void processCommand(EventObject command) throws SipException {
+            if (isLoggable(command)) Log.d(TAG, "process cmd: " + command);
             if (!process(command)) {
                 onError(SipErrorCode.IN_PROGRESS,
                         "cannot initiate a new transaction to execute: "
@@ -1050,6 +1055,13 @@
                 mSipHelper.sendCancel(mClientTransaction);
                 startSessionTimer(CANCEL_CALL_TIMER);
                 return true;
+            } else if (isRequestEvent(Request.INVITE, evt)) {
+                // Call self? Send BUSY HERE so server may redirect the call to
+                // voice mailbox.
+                RequestEvent event = (RequestEvent) evt;
+                mSipHelper.sendInviteBusyHere(event,
+                        event.getServerTransaction());
+                return true;
             }
             return false;
         }
@@ -1351,6 +1363,10 @@
         return DEBUG;
     }
 
+    private static boolean isLoggable(EventObject evt) {
+        return isLoggable(null, evt);
+    }
+
     private static boolean isLoggable(SipSessionImpl s, EventObject evt) {
         if (!isLoggable(s)) return false;
         if (evt == null) return false;
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index 9da560a..0c8a725 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -768,7 +768,7 @@
     LOGD("latency: output %d, input %d", track.latency(), record.latency());
 
     // Initialize echo canceler.
-    EchoSuppressor echo(sampleRate, sampleCount, sampleCount * 2 +
+    EchoSuppressor echo(sampleCount,
         (track.latency() + record.latency()) * sampleRate / 1000);
 
     // Give device socket a reasonable buffer size.
diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp
index fbeb315..4cff588 100644
--- a/voip/jni/rtp/EchoSuppressor.cpp
+++ b/voip/jni/rtp/EchoSuppressor.cpp
@@ -25,146 +25,163 @@
 
 #include "EchoSuppressor.h"
 
-EchoSuppressor::EchoSuppressor(int sampleRate, int sampleCount, int tailLength)
+// It is very difficult to do echo cancellation at this level due to the lack of
+// the timing information of the samples being played and recorded. Therefore,
+// for the first release only echo suppression is implemented.
+
+// The algorithm is derived from the "previous works" summarized in
+//   A new class of doubletalk detectors based on cross-correlation,
+//   J Benesty, DR Morgan, JH Cho, IEEE Trans. on Speech and Audio Processing.
+// The method proposed in that paper is not used because of its high complexity.
+
+// It is well known that cross-correlation can be computed using convolution,
+// but unfortunately not every mobile processor has a (fast enough) FPU. Thus
+// we use integer arithmetic as much as possible and do lots of bookkeeping.
+// Again, parameters and thresholds are chosen by experiments.
+
+EchoSuppressor::EchoSuppressor(int sampleCount, int tailLength)
 {
-    int scale = 1;
-    while (tailLength > 200 * scale) {
-        scale <<= 1;
-    }
-    if (scale > sampleCount) {
-        scale = sampleCount;
+    tailLength += sampleCount * 4;
+
+    int shift = 0;
+    while ((sampleCount >> shift) > 1 && (tailLength >> shift) > 256) {
+        ++shift;
     }
 
-    mScale = scale;
+    mShift = shift + 4;
+    mScale = 1 << shift;
     mSampleCount = sampleCount;
-    mWindowSize = sampleCount / scale;
-    mTailLength = (tailLength + scale - 1) / scale;
-    mRecordLength = (sampleRate + sampleCount - 1) / sampleCount;
+    mWindowSize = sampleCount >> shift;
+    mTailLength = tailLength >> shift;
+    mRecordLength = tailLength * 2 / sampleCount;
     mRecordOffset = 0;
 
-    mXs = new float[mTailLength + mWindowSize];
-    memset(mXs, 0, sizeof(float) * (mTailLength + mWindowSize));
-    mXYs = new float[mTailLength];
-    memset(mXYs, 0, sizeof(float) * mTailLength);
-    mXXs = new float[mTailLength];
-    memset(mXYs, 0, sizeof(float) * mTailLength);
-    mYY = 0;
+    mXs = new uint16_t[mTailLength + mWindowSize];
+    memset(mXs, 0, sizeof(*mXs) * (mTailLength + mWindowSize));
+    mXSums = new uint32_t[mTailLength];
+    memset(mXSums, 0, sizeof(*mXSums) * mTailLength);
+    mX2Sums = new uint32_t[mTailLength];
+    memset(mX2Sums, 0, sizeof(*mX2Sums) * mTailLength);
+    mXRecords = new uint16_t[mRecordLength * mWindowSize];
+    memset(mXRecords, 0, sizeof(*mXRecords) * mRecordLength * mWindowSize);
 
-    mXYRecords = new float[mRecordLength * mTailLength];
-    memset(mXYRecords, 0, sizeof(float) * mRecordLength * mTailLength);
-    mXXRecords = new float[mRecordLength * mWindowSize];
-    memset(mXXRecords, 0, sizeof(float) * mRecordLength * mWindowSize);
-    mYYRecords = new float[mRecordLength];
-    memset(mYYRecords, 0, sizeof(float) * mRecordLength);
+    mYSum = 0;
+    mY2Sum = 0;
+    mYRecords = new uint32_t[mRecordLength];
+    memset(mYRecords, 0, sizeof(*mYRecords) * mRecordLength);
+    mY2Records = new uint32_t[mRecordLength];
+    memset(mY2Records, 0, sizeof(*mY2Records) * mRecordLength);
+
+    mXYSums = new uint32_t[mTailLength];
+    memset(mXYSums, 0, sizeof(*mXYSums) * mTailLength);
+    mXYRecords = new uint32_t[mRecordLength * mTailLength];
+    memset(mXYRecords, 0, sizeof(*mXYRecords) * mRecordLength * mTailLength);
 
     mLastX = 0;
     mLastY = 0;
+    mWeight = 1.0f / (mRecordLength * mWindowSize);
 }
 
 EchoSuppressor::~EchoSuppressor()
 {
     delete [] mXs;
-    delete [] mXYs;
-    delete [] mXXs;
+    delete [] mXSums;
+    delete [] mX2Sums;
+    delete [] mXRecords;
+    delete [] mYRecords;
+    delete [] mY2Records;
+    delete [] mXYSums;
     delete [] mXYRecords;
-    delete [] mXXRecords;
-    delete [] mYYRecords;
 }
 
 void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded)
 {
-    float *records;
-
     // Update Xs.
-    for (int i = 0; i < mTailLength; ++i) {
-        mXs[i] = mXs[mWindowSize + i];
+    for (int i = mTailLength - 1; i >= 0; --i) {
+        mXs[i + mWindowSize] = mXs[i];
     }
-    for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) {
-        float sum = 0;
+    for (int i = mWindowSize - 1, j = 0; i >= 0; --i, j += mScale) {
+        uint32_t sum = 0;
         for (int k = 0; k < mScale; ++k) {
-            float x = playbacked[j + k] >> 8;
+            int32_t x = playbacked[j + k] << 15;
             mLastX += x;
-            sum += (mLastX >= 0) ? mLastX : -mLastX;
-            mLastX = 0.005f * mLastX - x;
+            sum += ((mLastX >= 0) ? mLastX : -mLastX) >> 15;
+            mLastX -= (mLastX >> 10) + x;
         }
-        mXs[mTailLength - 1 + i] = sum;
+        mXs[i] = sum >> mShift;
     }
 
-    // Update XXs and XXRecords.
-    for (int i = 0; i < mTailLength - mWindowSize; ++i) {
-        mXXs[i] = mXXs[mWindowSize + i];
+    // Update XSums, X2Sums, and XRecords.
+    for (int i = mTailLength - mWindowSize - 1; i >= 0; --i) {
+        mXSums[i + mWindowSize] = mXSums[i];
+        mX2Sums[i + mWindowSize] = mX2Sums[i];
     }
-    records = &mXXRecords[mRecordOffset * mWindowSize];
-    for (int i = 0, j = mTailLength - mWindowSize; i < mWindowSize; ++i, ++j) {
-        float xx = mXs[mTailLength - 1 + i] * mXs[mTailLength - 1 + i];
-        mXXs[j] = mXXs[j - 1] + xx - records[i];
-        records[i] = xx;
-        if (mXXs[j] < 0) {
-            mXXs[j] = 0;
-        }
+    uint16_t *xRecords = &mXRecords[mRecordOffset * mWindowSize];
+    for (int i = mWindowSize - 1; i >= 0; --i) {
+        uint16_t x = mXs[i];
+        mXSums[i] = mXSums[i + 1] + x - xRecords[i];
+        mX2Sums[i] = mX2Sums[i + 1] + x * x - xRecords[i] * xRecords[i];
+        xRecords[i] = x;
     }
 
     // Compute Ys.
-    float ys[mWindowSize];
-    for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) {
-        float sum = 0;
+    uint16_t ys[mWindowSize];
+    for (int i = mWindowSize - 1, j = 0; i >= 0; --i, j += mScale) {
+        uint32_t sum = 0;
         for (int k = 0; k < mScale; ++k) {
-            float y = recorded[j + k] >> 8;
+            int32_t y = recorded[j + k] << 15;
             mLastY += y;
-            sum += (mLastY >= 0) ? mLastY : -mLastY;
-            mLastY = 0.005f * mLastY - y;
+            sum += ((mLastY >= 0) ? mLastY : -mLastY) >> 15;
+            mLastY -= (mLastY >> 10) + y;
         }
-        ys[i] = sum;
+        ys[i] = sum >> mShift;
     }
 
-    // Update YY and YYRecords.
-    float yy = 0;
-    for (int i = 0; i < mWindowSize; ++i) {
-        yy += ys[i] * ys[i];
+    // Update YSum, Y2Sum, YRecords, and Y2Records.
+    uint32_t ySum = 0;
+    uint32_t y2Sum = 0;
+    for (int i = mWindowSize - 1; i >= 0; --i) {
+        ySum += ys[i];
+        y2Sum += ys[i] * ys[i];
     }
-    mYY += yy - mYYRecords[mRecordOffset];
-    mYYRecords[mRecordOffset] = yy;
-    if (mYY < 0) {
-        mYY = 0;
+    mYSum += ySum - mYRecords[mRecordOffset];
+    mY2Sum += y2Sum - mY2Records[mRecordOffset];
+    mYRecords[mRecordOffset] = ySum;
+    mY2Records[mRecordOffset] = y2Sum;
+
+    // Update XYSums and XYRecords.
+    uint32_t *xyRecords = &mXYRecords[mRecordOffset * mTailLength];
+    for (int i = mTailLength - 1; i >= 0; --i) {
+        uint32_t xySum = 0;
+        for (int j = mWindowSize - 1; j >= 0; --j) {
+            xySum += mXs[i + j] * ys[j];
+        }
+        mXYSums[i] += xySum - xyRecords[i];
+        xyRecords[i] = xySum;
     }
 
-    // Update XYs and XYRecords.
-    records = &mXYRecords[mRecordOffset * mTailLength];
-    for (int i = 0; i < mTailLength; ++i) {
-        float xy = 0;
-        for (int j = 0;j < mWindowSize; ++j) {
-            xy += mXs[i + j] * ys[j];
-        }
-        mXYs[i] += xy - records[i];
-        records[i] = xy;
-        if (mXYs[i] < 0) {
-            mXYs[i] = 0;
-        }
-    }
-
-    // Computes correlations from XYs, XXs, and YY.
-    float weight = 1.0f / (mYY + 1);
-    float correlation = 0;
+    // Compute correlations.
+    float corr2 = 0.0f;
     int latency = 0;
-    for (int i = 0; i < mTailLength; ++i) {
-        float c = mXYs[i] * mXYs[i] * weight / (mXXs[i] + 1);
-        if (c > correlation) {
-            correlation = c;
+    float varY = mY2Sum - mWeight * mYSum * mYSum;
+    for (int i = mTailLength - 1; i >= 0; --i) {
+        float varX = mX2Sums[i] - mWeight * mXSums[i] * mXSums[i];
+        float cov = mXYSums[i] - mWeight * mXSums[i] * mYSum;
+        float c2 = cov * cov / (varX * varY + 1);
+        if (c2 > corr2) {
+            corr2 = c2;
             latency = i;
         }
     }
+    //LOGI("correlation^2 = %.10f, latency = %d", corr2, latency * mScale);
 
-    correlation = sqrtf(correlation);
-    if (correlation > 0.3f) {
-        float factor = 1.0f - correlation;
-        factor *= factor;
-        factor /= 2.0; // suppress harder
+    // Do echo suppression.
+    if (corr2 > 0.1f) {
+        int factor = (corr2 > 1.0f) ? 0 : (1.0f - sqrtf(corr2)) * 4096;
         for (int i = 0; i < mSampleCount; ++i) {
-            recorded[i] *= factor;
+            recorded[i] = recorded[i] * factor >> 16;
         }
     }
-    //LOGI("latency %5d, correlation %.10f", latency, correlation);
-
 
     // Increase RecordOffset.
     ++mRecordOffset;
diff --git a/voip/jni/rtp/EchoSuppressor.h b/voip/jni/rtp/EchoSuppressor.h
index 85decf5..2f3b593 100644
--- a/voip/jni/rtp/EchoSuppressor.h
+++ b/voip/jni/rtp/EchoSuppressor.h
@@ -23,11 +23,12 @@
 {
 public:
     // The sampleCount must be power of 2.
-    EchoSuppressor(int sampleRate, int sampleCount, int tailLength);
+    EchoSuppressor(int sampleCount, int tailLength);
     ~EchoSuppressor();
     void run(int16_t *playbacked, int16_t *recorded);
 
 private:
+    int mShift;
     int mScale;
     int mSampleCount;
     int mWindowSize;
@@ -35,17 +36,23 @@
     int mRecordLength;
     int mRecordOffset;
 
-    float *mXs;
-    float *mXYs;
-    float *mXXs;
-    float mYY;
+    uint16_t *mXs;
+    uint32_t *mXSums;
+    uint32_t *mX2Sums;
+    uint16_t *mXRecords;
 
-    float *mXYRecords;
-    float *mXXRecords;
-    float *mYYRecords;
+    uint32_t mYSum;
+    uint32_t mY2Sum;
+    uint32_t *mYRecords;
+    uint32_t *mY2Records;
 
-    float mLastX;
-    float mLastY;
+    uint32_t *mXYSums;
+    uint32_t *mXYRecords;
+
+    int32_t mLastX;
+    int32_t mLastY;
+
+    float mWeight;
 };
 
 #endif
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 65b910b..e3deeb3 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -79,7 +79,6 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Pattern;
 
@@ -1297,14 +1296,14 @@
      */
     private boolean shouldDisableCoexistenceMode() {
         if (mBluetoothHeadset == null) return true;
-        Set<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
+        List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
         return (devices.size() != 0 ? false : true);
     }
 
     private void checkIsBluetoothPlaying() {
         boolean isBluetoothPlaying = false;
         if (mBluetoothA2dp != null) {
-            Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedDevices();
+            List<BluetoothDevice> connected = mBluetoothA2dp.getConnectedDevices();
 
             for (BluetoothDevice device : connected) {
                 if (mBluetoothA2dp.isA2dpPlaying(device)) {